1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//! One client-side WebSocket interface, whatever is underneath.
//!
//! `plaza_session` covers the server and is tokio/actix by construction, so it
//! cannot help a client, and least of all a browser one. This crate is the other
//! half: the socket a *client* holds, with the same shape on a desktop, in a
//! browser, and in-process.
//!
//! # It is built for a frame loop, not for an async runtime
//!
//! [`Socket::poll`] is non-blocking and drains into a caller-owned buffer. That
//! is the whole ergonomic decision, and it is made for macroquad-style
//! applications, which have a synchronous `loop { ...; next_frame().await }` and
//! nowhere to put a future. An `async fn recv()` would be the natural Rust API
//! and would be unusable there. Reusing the buffer also keeps a per-frame call
//! allocation-free, matching how `plaza_server_utils` hands back its results.
//!
//! ```no_run
//! # use plaza_ws::{Socket, Event};
//! # fn demo(socket: &mut impl Socket) {
//! let mut events = Vec::new();
//! // once per frame
//! socket.poll(&mut events);
//! for event in events.drain(..) {
//! match event {
//! Event::Open => println!("connected"),
//! Event::Message(bytes) => println!("{} bytes", bytes.len()),
//! Event::Text(text) => println!("{text}"),
//! Event::Closed(reason) => println!("gone: {reason:?}"),
//! }
//! }
//! # }
//! ```
//!
//! # Backends
//!
//! Each is a feature, and they compose: a native host that also plays enables
//! `native` *and* `loopback`, and talks to both over the same trait.
//!
//! | feature | where | underneath |
//! |---|---|---|
//! | `loopback` | anywhere | in-process channels, no dependencies |
//! | `native` | desktop | `tungstenite` on a worker thread |
//! | `miniquad` | browser, under macroquad | our own JS, registered as a miniquad plugin |
//!
//! ## Why the browser backend is ours rather than a crate
//!
//! Because the constraint is the *host page's loader*, not the platform.
//!
//! `web-sys` (and so `gloo-net`, and so `tokio-tungstenite-wasm`) needs
//! `wasm-bindgen`, which rewrites the module with `wasm-bindgen-cli` and ships
//! its own JS to instantiate it. miniquad's `mq_js_bundle.js` builds its own
//! import object, lets plugins extend it, and instantiates the raw module
//! itself. Both want to own instantiation, so under macroquad the wasm-bindgen
//! route does not work, and it fails in the worst way available: miniquad stubs
//! out imports nothing provides, so such a build loads happily and then silently
//! does nothing.
//!
//! So the `miniquad` backend is a few `extern "C"` declarations against our own
//! JS plugin (`js/plaza_ws.js`), which needs no crate at all. The two crates
//! that *do* use miniquad's plugin mechanism, `sapp-jsutils` and `quad-net`, are
//! barely maintained, and the mechanism is small enough not to need them.
//!
//! An application that is **not** built on macroquad has the opposite problem
//! and wants the wasm-bindgen route. That is a natural fourth backend
//! (`tokio-tungstenite-wasm` behind a `web` feature) and is deliberately absent
//! until something needs it, rather than shipped untested.
use fmt;
pub use ;
/// Something that arrived, in order.
/// Why a socket ended.
/// Where a socket is in its lifecycle. Monotonic: a socket never returns to an
/// earlier state, so [`Closed`](State::Closed) is terminal and reconnecting
/// means a new socket.
/// A client-side WebSocket.
///
/// Deliberately small. Anything an application can do itself (reconnection
/// policy, backoff, heartbeats, framing of its own messages) is left to it,
/// because those are decisions and this is a pipe.
/// Sending a value as JSON text, so call sites are not full of
/// `serde_json::to_string`.
///
/// An extension trait rather than a method on [`Socket`], because a generic
/// method cannot be called through a trait object, and holding the socket as
/// `Box<dyn Socket>` is exactly what an application does when the transport is
/// chosen by feature flag. Implemented for every socket, sized or not.
///
/// Text rather than binary, deliberately. A WebSocket text frame arrives in a
/// browser as a string that `JSON.parse` accepts directly, while a binary frame
/// arrives as a `Blob` or `ArrayBuffer` that a JS client has to decode itself,
/// having first remembered to set `binaryType`.
///
/// Send a **bare message, never an envelope**. A server attaches who a message
/// came from, because identity is the server's fact and not the client's claim,
/// and a client that could name itself could name somebody else.
/// Connects using whichever real transport this build has.
///
/// Present only when exactly one real backend is enabled, so that a build cannot
/// silently pick a transport the author did not intend. With several, name the
/// one you want: `native::connect` or `miniquad::connect`. [`loopback`] is
/// never chosen here because it connects to a peer rather than to a URL.