react-websocket 3.6.0 → 4.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +34 -5
  2. package/dist/ag-ui.cjs +421 -0
  3. package/dist/ag-ui.cjs.map +1 -0
  4. package/dist/ag-ui.d.cts +493 -0
  5. package/dist/ag-ui.d.ts +171 -0
  6. package/dist/ag-ui.js +418 -0
  7. package/dist/ag-ui.js.map +1 -0
  8. package/dist/core.cjs +727 -162
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +501 -62
  11. package/dist/core.d.ts +501 -62
  12. package/dist/core.js +723 -163
  13. package/dist/core.js.map +1 -1
  14. package/dist/devtools.cjs +186 -0
  15. package/dist/devtools.cjs.map +1 -0
  16. package/dist/devtools.d.cts +32 -0
  17. package/dist/devtools.d.ts +32 -0
  18. package/dist/devtools.js +185 -0
  19. package/dist/devtools.js.map +1 -0
  20. package/dist/index.cjs +214 -19
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +705 -56
  23. package/dist/index.d.ts +421 -30
  24. package/dist/index.js +211 -21
  25. package/dist/index.js.map +1 -1
  26. package/dist/outbox.cjs +222 -0
  27. package/dist/outbox.cjs.map +1 -0
  28. package/dist/outbox.d.cts +127 -0
  29. package/dist/outbox.d.ts +127 -0
  30. package/dist/outbox.js +219 -0
  31. package/dist/outbox.js.map +1 -0
  32. package/dist/replay.cjs +324 -0
  33. package/dist/replay.cjs.map +1 -0
  34. package/dist/replay.d.cts +124 -0
  35. package/dist/replay.d.ts +124 -0
  36. package/dist/replay.js +322 -0
  37. package/dist/replay.js.map +1 -0
  38. package/dist/tabs.cjs +336 -0
  39. package/dist/tabs.cjs.map +1 -0
  40. package/dist/tabs.d.cts +81 -0
  41. package/dist/tabs.d.ts +81 -0
  42. package/dist/tabs.js +335 -0
  43. package/dist/tabs.js.map +1 -0
  44. package/dist/timeline.d.cts +1 -213
  45. package/dist/types-BEecFqIB.d.cts +268 -0
  46. package/dist/webtransport.cjs +243 -0
  47. package/dist/webtransport.cjs.map +1 -0
  48. package/dist/webtransport.d.cts +67 -0
  49. package/dist/webtransport.d.ts +67 -0
  50. package/dist/webtransport.js +242 -0
  51. package/dist/webtransport.js.map +1 -0
  52. package/package.json +139 -3
@@ -0,0 +1,243 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/webtransport.ts
3
+ const CONNECTING = 0;
4
+ const OPEN = 1;
5
+ const CLOSING = 2;
6
+ const CLOSED = 3;
7
+ const TEXT = 0;
8
+ const BINARY = 1;
9
+ /** The method the client calls for `volatile` sends, where a socket has it. */
10
+ const DATAGRAM = Symbol.for("react-websocket.datagram");
11
+ function closeEvent(code, reason, wasClean) {
12
+ const init = {
13
+ code,
14
+ reason,
15
+ wasClean
16
+ };
17
+ return typeof CloseEvent === "function" ? new CloseEvent("close", init) : Object.assign(new Event("close"), init);
18
+ }
19
+ function bytes(data) {
20
+ if (typeof data === "string") return [TEXT, new TextEncoder().encode(data)];
21
+ if (data instanceof ArrayBuffer) return [BINARY, new Uint8Array(data)];
22
+ return [BINARY, new Uint8Array(data.buffer, data.byteOffset, data.byteLength)];
23
+ }
24
+ /**
25
+ * Connect over [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)
26
+ * behind the same client API. Pass the returned class as the `WebSocket` option: messages go on
27
+ * one reliable, ordered stream, and `volatile` sends go as datagrams, which are never queued or
28
+ * retransmitted, so stale input or cursor positions don't hold up what comes after them.
29
+ *
30
+ * Where WebTransport isn't available, or a session can't be established within `timeout`, it
31
+ * connects with a WebSocket to the client's own URL instead, and keeps doing so for later
32
+ * connections.
33
+ *
34
+ * The server speaks a small framing over WebTransport: see the guide.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import { createWebTransportSocket } from 'react-websocket/webtransport';
39
+ *
40
+ * export const game = createWebSocket('wss://game.example.com/play', {
41
+ * json: true,
42
+ * WebSocket: createWebTransportSocket()
43
+ * });
44
+ *
45
+ * game.send({ type: 'aim', angle }, { volatile: true }); // a datagram
46
+ * ```
47
+ */
48
+ function createWebTransportSocket(options = {}) {
49
+ const mapUrl = options.url ?? ((url) => url.replace(/^ws(s?):/, "https:"));
50
+ const timeout = options.timeout ?? 5e3;
51
+ let fallingBack = false;
52
+ class WebTransportSocket extends EventTarget {
53
+ static CONNECTING = CONNECTING;
54
+ static OPEN = OPEN;
55
+ static CLOSING = CLOSING;
56
+ static CLOSED = CLOSED;
57
+ CONNECTING = CONNECTING;
58
+ OPEN = OPEN;
59
+ CLOSING = CLOSING;
60
+ CLOSED = CLOSED;
61
+ url;
62
+ extensions = "";
63
+ onopen = null;
64
+ onmessage = null;
65
+ onerror = null;
66
+ onclose = null;
67
+ binaryType = "blob";
68
+ protocol = "";
69
+ #state = CONNECTING;
70
+ #transport;
71
+ #writer;
72
+ #datagrams;
73
+ #inner;
74
+ #buffered = 0;
75
+ #chain;
76
+ static get fallingBack() {
77
+ return fallingBack || typeof WebTransport !== "function";
78
+ }
79
+ constructor(url, protocols) {
80
+ super();
81
+ if (typeof window === "undefined") throw new Error("react-websocket/webtransport: no WebSocket here (server render?)");
82
+ for (const type of [
83
+ "open",
84
+ "message",
85
+ "error",
86
+ "close"
87
+ ]) this.addEventListener(type, (event) => this[`on${type}`]?.call(this, event));
88
+ this.url = String(url);
89
+ if (fallingBack || typeof WebTransport !== "function") {
90
+ this.#fallBack(protocols);
91
+ return;
92
+ }
93
+ this.#connect(protocols);
94
+ }
95
+ get readyState() {
96
+ return this.#state;
97
+ }
98
+ get bufferedAmount() {
99
+ return this.#inner?.bufferedAmount ?? this.#buffered;
100
+ }
101
+ send(data) {
102
+ if (this.#state === CONNECTING) throw new DOMException("WebSocket is still in CONNECTING state", "InvalidStateError");
103
+ if (this.#state !== OPEN) return;
104
+ if (this.#inner) return this.#inner.send(data);
105
+ if (data instanceof Blob || this.#chain) {
106
+ const chain = (this.#chain ?? Promise.resolve()).then(async () => {
107
+ this.#write(...bytes(data instanceof Blob ? await data.arrayBuffer() : data));
108
+ if (this.#chain === chain) this.#chain = void 0;
109
+ });
110
+ this.#chain = chain;
111
+ return;
112
+ }
113
+ this.#write(...bytes(data));
114
+ }
115
+ /** A `volatile` send from the client: a datagram, unless it's too big for one. */
116
+ [DATAGRAM](data) {
117
+ const transport = this.#transport;
118
+ if (options.datagrams === false || this.#inner || !transport || data instanceof Blob || this.#state !== OPEN) return this.send(data);
119
+ const [type, payload] = bytes(data);
120
+ if (payload.byteLength + 1 > transport.datagrams.maxDatagramSize) return this.send(data);
121
+ const datagram = new Uint8Array(payload.byteLength + 1);
122
+ datagram[0] = type;
123
+ datagram.set(payload, 1);
124
+ this.#datagrams.write(datagram).catch(() => {});
125
+ }
126
+ close(code = 1e3, reason = "") {
127
+ if (this.#state >= CLOSING) return;
128
+ const connecting = this.#state === CONNECTING;
129
+ if (this.#inner) this.#inner.close(code, reason);
130
+ this.#state = CLOSING;
131
+ if (this.#inner) return;
132
+ if (!connecting) return this.#transport.close({
133
+ closeCode: code,
134
+ reason
135
+ });
136
+ this.#transport?.close();
137
+ setTimeout(() => this.#end(code, reason, true), 0);
138
+ }
139
+ async #connect(protocols) {
140
+ let transport;
141
+ let timer;
142
+ try {
143
+ transport = this.#transport = new WebTransport(mapUrl(this.url), options.transport);
144
+ await Promise.race([transport.ready, new Promise((_, reject) => timer = setTimeout(reject, timeout, /* @__PURE__ */ new Error("timeout")))]);
145
+ const stream = await transport.createBidirectionalStream();
146
+ if (this.#state !== CONNECTING) return transport.close();
147
+ this.#writer = stream.writable.getWriter();
148
+ this.#datagrams = transport.datagrams.writable.getWriter();
149
+ this.#read(stream.readable.getReader());
150
+ this.#readDatagrams(transport.datagrams.readable.getReader());
151
+ } catch {
152
+ this.#transport?.close();
153
+ this.#transport = void 0;
154
+ if (this.#state !== CONNECTING) return;
155
+ if (options.fallback === false) {
156
+ this.dispatchEvent(new Event("error"));
157
+ return this.#end(1006, "", false);
158
+ }
159
+ fallingBack = true;
160
+ return this.#fallBack(protocols);
161
+ } finally {
162
+ clearTimeout(timer);
163
+ }
164
+ transport.closed.then((info) => this.#end(info.closeCode || 1005, info.reason ?? "", true), () => {
165
+ this.dispatchEvent(new Event("error"));
166
+ this.#end(1006, "", false);
167
+ });
168
+ this.#state = OPEN;
169
+ this.dispatchEvent(new Event("open"));
170
+ }
171
+ #fallBack(protocols) {
172
+ const Impl = options.fallback === false ? void 0 : options.fallback ?? globalThis.WebSocket;
173
+ if (!Impl) throw new Error("react-websocket/webtransport: no WebTransport, and no fallback");
174
+ const inner = this.#inner = new Impl(this.url, protocols);
175
+ inner.binaryType = "arraybuffer";
176
+ inner.onopen = () => {
177
+ this.protocol = inner.protocol;
178
+ this.#state = OPEN;
179
+ this.dispatchEvent(new Event("open"));
180
+ };
181
+ inner.onmessage = (event) => this.#message(event.data);
182
+ inner.onerror = () => this.dispatchEvent(new Event("error"));
183
+ inner.onclose = (event) => this.#end(event.code, event.reason, event.wasClean);
184
+ }
185
+ #write(type, payload) {
186
+ const frame = new Uint8Array(payload.byteLength + 5);
187
+ const view = new DataView(frame.buffer);
188
+ view.setUint8(0, type);
189
+ view.setUint32(1, payload.byteLength);
190
+ frame.set(payload, 5);
191
+ this.#buffered += frame.byteLength;
192
+ this.#writer.write(frame).then(() => this.#buffered -= frame.byteLength, () => {});
193
+ }
194
+ /** Reassemble frames from the stream, whatever its chunks. */
195
+ async #read(reader) {
196
+ let buffer = /* @__PURE__ */ new Uint8Array(0);
197
+ try {
198
+ for (;;) {
199
+ const { value, done } = await reader.read();
200
+ if (done) return;
201
+ const joined = new Uint8Array(buffer.byteLength + value.byteLength);
202
+ joined.set(buffer);
203
+ joined.set(value, buffer.byteLength);
204
+ buffer = joined;
205
+ while (buffer.byteLength >= 5) {
206
+ const view = new DataView(buffer.buffer, buffer.byteOffset);
207
+ const length = view.getUint32(1);
208
+ if (buffer.byteLength < 5 + length) break;
209
+ this.#frame(view.getUint8(0), buffer.slice(5, 5 + length));
210
+ buffer = buffer.slice(5 + length);
211
+ }
212
+ }
213
+ } catch {}
214
+ }
215
+ async #readDatagrams(reader) {
216
+ try {
217
+ for (;;) {
218
+ const { value, done } = await reader.read();
219
+ if (done) return;
220
+ if (value.byteLength) this.#frame(value[0], value.slice(1));
221
+ }
222
+ } catch {}
223
+ }
224
+ #frame(type, payload) {
225
+ this.#message(type === TEXT ? new TextDecoder().decode(payload) : payload.buffer);
226
+ }
227
+ #message(data) {
228
+ if (this.#state !== OPEN) return;
229
+ if (data instanceof ArrayBuffer && this.binaryType === "blob") data = new Blob([data]);
230
+ this.dispatchEvent(new MessageEvent("message", { data }));
231
+ }
232
+ #end(code, reason, wasClean) {
233
+ if (this.#state === CLOSED) return;
234
+ this.#state = CLOSED;
235
+ this.dispatchEvent(closeEvent(code, reason, wasClean));
236
+ }
237
+ }
238
+ return WebTransportSocket;
239
+ }
240
+ //#endregion
241
+ exports.createWebTransportSocket = createWebTransportSocket;
242
+
243
+ //# sourceMappingURL=webtransport.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webtransport.cjs","names":[],"sources":["../src/webtransport.ts"],"sourcesContent":["// An entry with no imports: it only implements the `WebSocket` interface.\n\n/** Options for {@link createWebTransportSocket}. */\nexport interface WebTransportSocketOptions {\n /**\n * The WebTransport URL for the client's WebSocket URL.\n * @default `ws:` and `wss:` become `https:`\n */\n url?: (url: string) => string;\n /** Options for the `WebTransport` constructor, e.g. `serverCertificateHashes` in development. */\n transport?: WebTransportOptions;\n /**\n * The `WebSocket` to connect with where WebTransport isn't available, or when a session can't be\n * established, to the client's own URL. `false` fails the connection instead.\n * @default the global WebSocket\n */\n fallback?: typeof WebSocket | false;\n /**\n * How long to wait for a WebTransport session before falling back, in ms. UDP is blocked on\n * some networks, where the attempt only fails after a long time.\n * @default 5000\n */\n timeout?: number;\n /**\n * Send `volatile` messages as datagrams, which can be lost even while connected. `false` sends\n * every message on the stream.\n * @default true\n */\n datagrams?: boolean;\n}\n\n/** The class from {@link createWebTransportSocket}. */\nexport interface WebTransportWebSocket {\n new (url: string | URL, protocols?: string | string[]): WebSocket;\n readonly prototype: WebSocket;\n readonly CONNECTING: 0;\n readonly OPEN: 1;\n readonly CLOSING: 2;\n readonly CLOSED: 3;\n /** Whether connections go over WebSocket now, because WebTransport is missing or failed. */\n readonly fallingBack: boolean;\n}\n\nconst CONNECTING = 0;\nconst OPEN = 1;\nconst CLOSING = 2;\nconst CLOSED = 3;\nconst TEXT = 0;\nconst BINARY = 1;\n\n/** The method the client calls for `volatile` sends, where a socket has it. */\nconst DATAGRAM = Symbol.for('react-websocket.datagram');\n\ntype Data = Parameters<WebSocket['send']>[0];\n\nfunction closeEvent(code: number, reason: string, wasClean: boolean): Event {\n const init = { code, reason, wasClean };\n // Node 22 has no CloseEvent.\n return typeof CloseEvent === 'function'\n ? new CloseEvent('close', init)\n : Object.assign(new Event('close'), init);\n}\n\nfunction bytes(data: Exclude<Data, Blob>): [number, Uint8Array] {\n if (typeof data === 'string') return [TEXT, new TextEncoder().encode(data)];\n if (data instanceof ArrayBuffer) return [BINARY, new Uint8Array(data)];\n return [BINARY, new Uint8Array(data.buffer, data.byteOffset, data.byteLength)];\n}\n\n/**\n * Connect over [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)\n * behind the same client API. Pass the returned class as the `WebSocket` option: messages go on\n * one reliable, ordered stream, and `volatile` sends go as datagrams, which are never queued or\n * retransmitted, so stale input or cursor positions don't hold up what comes after them.\n *\n * Where WebTransport isn't available, or a session can't be established within `timeout`, it\n * connects with a WebSocket to the client's own URL instead, and keeps doing so for later\n * connections.\n *\n * The server speaks a small framing over WebTransport: see the guide.\n *\n * @example\n * ```ts\n * import { createWebTransportSocket } from 'react-websocket/webtransport';\n *\n * export const game = createWebSocket('wss://game.example.com/play', {\n * json: true,\n * WebSocket: createWebTransportSocket()\n * });\n *\n * game.send({ type: 'aim', angle }, { volatile: true }); // a datagram\n * ```\n */\nexport function createWebTransportSocket(\n options: WebTransportSocketOptions = {}\n): WebTransportWebSocket {\n const mapUrl = options.url ?? ((url: string) => url.replace(/^ws(s?):/, 'https:'));\n const timeout = options.timeout ?? 5000;\n let fallingBack = false;\n\n class WebTransportSocket extends EventTarget implements WebSocket {\n static readonly CONNECTING = CONNECTING;\n static readonly OPEN = OPEN;\n static readonly CLOSING = CLOSING;\n static readonly CLOSED = CLOSED;\n readonly CONNECTING = CONNECTING;\n readonly OPEN = OPEN;\n readonly CLOSING = CLOSING;\n readonly CLOSED = CLOSED;\n\n readonly url: string;\n readonly extensions = '';\n onopen: WebSocket['onopen'] = null;\n onmessage: WebSocket['onmessage'] = null;\n onerror: WebSocket['onerror'] = null;\n onclose: WebSocket['onclose'] = null;\n binaryType: BinaryType = 'blob';\n protocol = '';\n\n #state: WebSocket['readyState'] = CONNECTING;\n #transport: WebTransport | undefined;\n #writer: WritableStreamDefaultWriter<Uint8Array> | undefined;\n #datagrams: WritableStreamDefaultWriter<Uint8Array> | undefined;\n #inner: WebSocket | undefined;\n // Bytes handed to the stream and not yet written, like `bufferedAmount`.\n #buffered = 0;\n // Sends waiting for a Blob to be read, so they keep their order.\n #chain: Promise<void> | undefined;\n\n static get fallingBack() {\n return fallingBack || typeof WebTransport !== 'function';\n }\n\n constructor(url: string | URL, protocols?: string | string[]) {\n super();\n if (typeof window === 'undefined') {\n throw new Error('react-websocket/webtransport: no WebSocket here (server render?)');\n }\n for (const type of ['open', 'message', 'error', 'close'] as const) {\n this.addEventListener(type, event =>\n (this[`on${type}`] as ((event: Event) => void) | null)?.call(this, event)\n );\n }\n this.url = String(url);\n if (fallingBack || typeof WebTransport !== 'function') {\n this.#fallBack(protocols);\n return;\n }\n void this.#connect(protocols);\n }\n\n get readyState() {\n return this.#state;\n }\n\n get bufferedAmount() {\n return this.#inner?.bufferedAmount ?? this.#buffered;\n }\n\n send(data: Data) {\n if (this.#state === CONNECTING) {\n throw new DOMException('WebSocket is still in CONNECTING state', 'InvalidStateError');\n }\n if (this.#state !== OPEN) return;\n if (this.#inner) return this.#inner.send(data);\n if (data instanceof Blob || this.#chain) {\n const chain = (this.#chain ?? Promise.resolve()).then(async () => {\n this.#write(...bytes(data instanceof Blob ? await data.arrayBuffer() : data));\n if (this.#chain === chain) this.#chain = undefined;\n });\n this.#chain = chain;\n return;\n }\n this.#write(...bytes(data));\n }\n\n /** A `volatile` send from the client: a datagram, unless it's too big for one. */\n [DATAGRAM](data: Data) {\n const transport = this.#transport;\n if (\n options.datagrams === false ||\n this.#inner ||\n !transport ||\n data instanceof Blob ||\n this.#state !== OPEN\n ) {\n return this.send(data);\n }\n const [type, payload] = bytes(data);\n if (payload.byteLength + 1 > transport.datagrams.maxDatagramSize) return this.send(data);\n const datagram = new Uint8Array(payload.byteLength + 1);\n datagram[0] = type;\n datagram.set(payload, 1);\n this.#datagrams!.write(datagram).catch(() => {});\n }\n\n close(code = 1000, reason = '') {\n if (this.#state >= CLOSING) return;\n const connecting = this.#state === CONNECTING;\n // First, so that a code the socket rejects throws before anything has changed.\n if (this.#inner) this.#inner.close(code, reason);\n this.#state = CLOSING;\n if (this.#inner) return;\n if (!connecting) return this.#transport!.close({ closeCode: code, reason });\n // Not open yet: give up on the session, and report this close rather than its failure.\n this.#transport?.close();\n setTimeout(() => this.#end(code, reason, true), 0);\n }\n\n async #connect(protocols: string | string[] | undefined) {\n let transport: WebTransport;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n transport = this.#transport = new WebTransport(mapUrl(this.url), options.transport);\n await Promise.race([\n transport.ready,\n new Promise((_, reject) => (timer = setTimeout(reject, timeout, new Error('timeout'))))\n ]);\n const stream = await transport.createBidirectionalStream();\n if (this.#state !== CONNECTING) return transport.close();\n this.#writer = stream.writable.getWriter();\n this.#datagrams = transport.datagrams.writable.getWriter();\n void this.#read(stream.readable.getReader());\n void this.#readDatagrams(transport.datagrams.readable.getReader());\n } catch {\n // No session: fall back to a WebSocket for this connection and the ones after it.\n this.#transport?.close();\n this.#transport = undefined;\n // Closed while connecting: `close()` reports its own code.\n if (this.#state !== CONNECTING) return;\n if (options.fallback === false) {\n this.dispatchEvent(new Event('error'));\n return this.#end(1006, '', false);\n }\n fallingBack = true;\n return this.#fallBack(protocols);\n } finally {\n clearTimeout(timer);\n }\n transport.closed.then(\n // A session ended without a code reports 0: like a WebSocket closed without a status.\n info => this.#end(info.closeCode || 1005, info.reason ?? '', true),\n () => {\n this.dispatchEvent(new Event('error'));\n this.#end(1006, '', false);\n }\n );\n this.#state = OPEN;\n this.dispatchEvent(new Event('open'));\n }\n\n #fallBack(protocols: string | string[] | undefined) {\n const Impl =\n options.fallback === false ? undefined : (options.fallback ?? globalThis.WebSocket);\n if (!Impl) {\n throw new Error('react-websocket/webtransport: no WebTransport, and no fallback');\n }\n const inner = (this.#inner = new Impl(this.url, protocols));\n inner.binaryType = 'arraybuffer';\n inner.onopen = () => {\n this.protocol = inner.protocol;\n this.#state = OPEN;\n this.dispatchEvent(new Event('open'));\n };\n inner.onmessage = event => this.#message(event.data);\n inner.onerror = () => this.dispatchEvent(new Event('error'));\n inner.onclose = event => this.#end(event.code, event.reason, event.wasClean);\n }\n\n #write(type: number, payload: Uint8Array) {\n const frame = new Uint8Array(payload.byteLength + 5);\n const view = new DataView(frame.buffer);\n view.setUint8(0, type);\n view.setUint32(1, payload.byteLength);\n frame.set(payload, 5);\n this.#buffered += frame.byteLength;\n this.#writer!.write(frame).then(\n () => (this.#buffered -= frame.byteLength),\n () => {}\n );\n }\n\n /** Reassemble frames from the stream, whatever its chunks. */\n async #read(reader: ReadableStreamDefaultReader<Uint8Array>) {\n let buffer = new Uint8Array(0);\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) return;\n const joined = new Uint8Array(buffer.byteLength + value.byteLength);\n joined.set(buffer);\n joined.set(value, buffer.byteLength);\n buffer = joined;\n while (buffer.byteLength >= 5) {\n const view = new DataView(buffer.buffer, buffer.byteOffset);\n const length = view.getUint32(1);\n if (buffer.byteLength < 5 + length) break;\n this.#frame(view.getUint8(0), buffer.slice(5, 5 + length));\n buffer = buffer.slice(5 + length);\n }\n }\n } catch {\n // The session closed; `closed` reports it.\n }\n }\n\n async #readDatagrams(reader: ReadableStreamDefaultReader<Uint8Array>) {\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) return;\n if (value.byteLength) this.#frame(value[0]!, value.slice(1));\n }\n } catch {\n // The session closed; `closed` reports it.\n }\n }\n\n #frame(type: number, payload: Uint8Array) {\n this.#message(type === TEXT ? new TextDecoder().decode(payload) : payload.buffer);\n }\n\n #message(data: unknown) {\n if (this.#state !== OPEN) return;\n if (data instanceof ArrayBuffer && this.binaryType === 'blob') data = new Blob([data]);\n this.dispatchEvent(new MessageEvent('message', { data }));\n }\n\n #end(code: number, reason: string, wasClean: boolean) {\n if (this.#state === CLOSED) return;\n this.#state = CLOSED;\n this.dispatchEvent(closeEvent(code, reason, wasClean));\n }\n }\n\n return WebTransportSocket as unknown as WebTransportWebSocket;\n}\n"],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ //#region src/webtransport.d.ts
2
+ /** Options for {@link createWebTransportSocket}. */
3
+ export interface WebTransportSocketOptions {
4
+ /**
5
+ * The WebTransport URL for the client's WebSocket URL.
6
+ * @default `ws:` and `wss:` become `https:`
7
+ */
8
+ url?: (url: string) => string;
9
+ /** Options for the `WebTransport` constructor, e.g. `serverCertificateHashes` in development. */
10
+ transport?: WebTransportOptions;
11
+ /**
12
+ * The `WebSocket` to connect with where WebTransport isn't available, or when a session can't be
13
+ * established, to the client's own URL. `false` fails the connection instead.
14
+ * @default the global WebSocket
15
+ */
16
+ fallback?: typeof WebSocket | false;
17
+ /**
18
+ * How long to wait for a WebTransport session before falling back, in ms. UDP is blocked on
19
+ * some networks, where the attempt only fails after a long time.
20
+ * @default 5000
21
+ */
22
+ timeout?: number;
23
+ /**
24
+ * Send `volatile` messages as datagrams, which can be lost even while connected. `false` sends
25
+ * every message on the stream.
26
+ * @default true
27
+ */
28
+ datagrams?: boolean;
29
+ }
30
+ /** The class from {@link createWebTransportSocket}. */
31
+ export interface WebTransportWebSocket {
32
+ new (url: string | URL, protocols?: string | string[]): WebSocket;
33
+ readonly prototype: WebSocket;
34
+ readonly CONNECTING: 0;
35
+ readonly OPEN: 1;
36
+ readonly CLOSING: 2;
37
+ readonly CLOSED: 3;
38
+ /** Whether connections go over WebSocket now, because WebTransport is missing or failed. */
39
+ readonly fallingBack: boolean;
40
+ }
41
+ /**
42
+ * Connect over [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)
43
+ * behind the same client API. Pass the returned class as the `WebSocket` option: messages go on
44
+ * one reliable, ordered stream, and `volatile` sends go as datagrams, which are never queued or
45
+ * retransmitted, so stale input or cursor positions don't hold up what comes after them.
46
+ *
47
+ * Where WebTransport isn't available, or a session can't be established within `timeout`, it
48
+ * connects with a WebSocket to the client's own URL instead, and keeps doing so for later
49
+ * connections.
50
+ *
51
+ * The server speaks a small framing over WebTransport: see the guide.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { createWebTransportSocket } from 'react-websocket/webtransport';
56
+ *
57
+ * export const game = createWebSocket('wss://game.example.com/play', {
58
+ * json: true,
59
+ * WebSocket: createWebTransportSocket()
60
+ * });
61
+ *
62
+ * game.send({ type: 'aim', angle }, { volatile: true }); // a datagram
63
+ * ```
64
+ */
65
+ export declare function createWebTransportSocket(options?: WebTransportSocketOptions): WebTransportWebSocket;
66
+ //#endregion
67
+ //# sourceMappingURL=webtransport.d.cts.map
@@ -0,0 +1,67 @@
1
+ //#region src/webtransport.d.ts
2
+ /** Options for {@link createWebTransportSocket}. */
3
+ export interface WebTransportSocketOptions {
4
+ /**
5
+ * The WebTransport URL for the client's WebSocket URL.
6
+ * @default `ws:` and `wss:` become `https:`
7
+ */
8
+ url?: (url: string) => string;
9
+ /** Options for the `WebTransport` constructor, e.g. `serverCertificateHashes` in development. */
10
+ transport?: WebTransportOptions;
11
+ /**
12
+ * The `WebSocket` to connect with where WebTransport isn't available, or when a session can't be
13
+ * established, to the client's own URL. `false` fails the connection instead.
14
+ * @default the global WebSocket
15
+ */
16
+ fallback?: typeof WebSocket | false;
17
+ /**
18
+ * How long to wait for a WebTransport session before falling back, in ms. UDP is blocked on
19
+ * some networks, where the attempt only fails after a long time.
20
+ * @default 5000
21
+ */
22
+ timeout?: number;
23
+ /**
24
+ * Send `volatile` messages as datagrams, which can be lost even while connected. `false` sends
25
+ * every message on the stream.
26
+ * @default true
27
+ */
28
+ datagrams?: boolean;
29
+ }
30
+ /** The class from {@link createWebTransportSocket}. */
31
+ export interface WebTransportWebSocket {
32
+ new (url: string | URL, protocols?: string | string[]): WebSocket;
33
+ readonly prototype: WebSocket;
34
+ readonly CONNECTING: 0;
35
+ readonly OPEN: 1;
36
+ readonly CLOSING: 2;
37
+ readonly CLOSED: 3;
38
+ /** Whether connections go over WebSocket now, because WebTransport is missing or failed. */
39
+ readonly fallingBack: boolean;
40
+ }
41
+ /**
42
+ * Connect over [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)
43
+ * behind the same client API. Pass the returned class as the `WebSocket` option: messages go on
44
+ * one reliable, ordered stream, and `volatile` sends go as datagrams, which are never queued or
45
+ * retransmitted, so stale input or cursor positions don't hold up what comes after them.
46
+ *
47
+ * Where WebTransport isn't available, or a session can't be established within `timeout`, it
48
+ * connects with a WebSocket to the client's own URL instead, and keeps doing so for later
49
+ * connections.
50
+ *
51
+ * The server speaks a small framing over WebTransport: see the guide.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { createWebTransportSocket } from 'react-websocket/webtransport';
56
+ *
57
+ * export const game = createWebSocket('wss://game.example.com/play', {
58
+ * json: true,
59
+ * WebSocket: createWebTransportSocket()
60
+ * });
61
+ *
62
+ * game.send({ type: 'aim', angle }, { volatile: true }); // a datagram
63
+ * ```
64
+ */
65
+ export declare function createWebTransportSocket(options?: WebTransportSocketOptions): WebTransportWebSocket;
66
+ //#endregion
67
+ //# sourceMappingURL=webtransport.d.ts.map
@@ -0,0 +1,242 @@
1
+ //#region src/webtransport.ts
2
+ const CONNECTING = 0;
3
+ const OPEN = 1;
4
+ const CLOSING = 2;
5
+ const CLOSED = 3;
6
+ const TEXT = 0;
7
+ const BINARY = 1;
8
+ /** The method the client calls for `volatile` sends, where a socket has it. */
9
+ const DATAGRAM = Symbol.for("react-websocket.datagram");
10
+ function closeEvent(code, reason, wasClean) {
11
+ const init = {
12
+ code,
13
+ reason,
14
+ wasClean
15
+ };
16
+ return typeof CloseEvent === "function" ? new CloseEvent("close", init) : Object.assign(new Event("close"), init);
17
+ }
18
+ function bytes(data) {
19
+ if (typeof data === "string") return [TEXT, new TextEncoder().encode(data)];
20
+ if (data instanceof ArrayBuffer) return [BINARY, new Uint8Array(data)];
21
+ return [BINARY, new Uint8Array(data.buffer, data.byteOffset, data.byteLength)];
22
+ }
23
+ /**
24
+ * Connect over [WebTransport](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)
25
+ * behind the same client API. Pass the returned class as the `WebSocket` option: messages go on
26
+ * one reliable, ordered stream, and `volatile` sends go as datagrams, which are never queued or
27
+ * retransmitted, so stale input or cursor positions don't hold up what comes after them.
28
+ *
29
+ * Where WebTransport isn't available, or a session can't be established within `timeout`, it
30
+ * connects with a WebSocket to the client's own URL instead, and keeps doing so for later
31
+ * connections.
32
+ *
33
+ * The server speaks a small framing over WebTransport: see the guide.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { createWebTransportSocket } from 'react-websocket/webtransport';
38
+ *
39
+ * export const game = createWebSocket('wss://game.example.com/play', {
40
+ * json: true,
41
+ * WebSocket: createWebTransportSocket()
42
+ * });
43
+ *
44
+ * game.send({ type: 'aim', angle }, { volatile: true }); // a datagram
45
+ * ```
46
+ */
47
+ function createWebTransportSocket(options = {}) {
48
+ const mapUrl = options.url ?? ((url) => url.replace(/^ws(s?):/, "https:"));
49
+ const timeout = options.timeout ?? 5e3;
50
+ let fallingBack = false;
51
+ class WebTransportSocket extends EventTarget {
52
+ static CONNECTING = CONNECTING;
53
+ static OPEN = OPEN;
54
+ static CLOSING = CLOSING;
55
+ static CLOSED = CLOSED;
56
+ CONNECTING = CONNECTING;
57
+ OPEN = OPEN;
58
+ CLOSING = CLOSING;
59
+ CLOSED = CLOSED;
60
+ url;
61
+ extensions = "";
62
+ onopen = null;
63
+ onmessage = null;
64
+ onerror = null;
65
+ onclose = null;
66
+ binaryType = "blob";
67
+ protocol = "";
68
+ #state = CONNECTING;
69
+ #transport;
70
+ #writer;
71
+ #datagrams;
72
+ #inner;
73
+ #buffered = 0;
74
+ #chain;
75
+ static get fallingBack() {
76
+ return fallingBack || typeof WebTransport !== "function";
77
+ }
78
+ constructor(url, protocols) {
79
+ super();
80
+ if (typeof window === "undefined") throw new Error("react-websocket/webtransport: no WebSocket here (server render?)");
81
+ for (const type of [
82
+ "open",
83
+ "message",
84
+ "error",
85
+ "close"
86
+ ]) this.addEventListener(type, (event) => this[`on${type}`]?.call(this, event));
87
+ this.url = String(url);
88
+ if (fallingBack || typeof WebTransport !== "function") {
89
+ this.#fallBack(protocols);
90
+ return;
91
+ }
92
+ this.#connect(protocols);
93
+ }
94
+ get readyState() {
95
+ return this.#state;
96
+ }
97
+ get bufferedAmount() {
98
+ return this.#inner?.bufferedAmount ?? this.#buffered;
99
+ }
100
+ send(data) {
101
+ if (this.#state === CONNECTING) throw new DOMException("WebSocket is still in CONNECTING state", "InvalidStateError");
102
+ if (this.#state !== OPEN) return;
103
+ if (this.#inner) return this.#inner.send(data);
104
+ if (data instanceof Blob || this.#chain) {
105
+ const chain = (this.#chain ?? Promise.resolve()).then(async () => {
106
+ this.#write(...bytes(data instanceof Blob ? await data.arrayBuffer() : data));
107
+ if (this.#chain === chain) this.#chain = void 0;
108
+ });
109
+ this.#chain = chain;
110
+ return;
111
+ }
112
+ this.#write(...bytes(data));
113
+ }
114
+ /** A `volatile` send from the client: a datagram, unless it's too big for one. */
115
+ [DATAGRAM](data) {
116
+ const transport = this.#transport;
117
+ if (options.datagrams === false || this.#inner || !transport || data instanceof Blob || this.#state !== OPEN) return this.send(data);
118
+ const [type, payload] = bytes(data);
119
+ if (payload.byteLength + 1 > transport.datagrams.maxDatagramSize) return this.send(data);
120
+ const datagram = new Uint8Array(payload.byteLength + 1);
121
+ datagram[0] = type;
122
+ datagram.set(payload, 1);
123
+ this.#datagrams.write(datagram).catch(() => {});
124
+ }
125
+ close(code = 1e3, reason = "") {
126
+ if (this.#state >= CLOSING) return;
127
+ const connecting = this.#state === CONNECTING;
128
+ if (this.#inner) this.#inner.close(code, reason);
129
+ this.#state = CLOSING;
130
+ if (this.#inner) return;
131
+ if (!connecting) return this.#transport.close({
132
+ closeCode: code,
133
+ reason
134
+ });
135
+ this.#transport?.close();
136
+ setTimeout(() => this.#end(code, reason, true), 0);
137
+ }
138
+ async #connect(protocols) {
139
+ let transport;
140
+ let timer;
141
+ try {
142
+ transport = this.#transport = new WebTransport(mapUrl(this.url), options.transport);
143
+ await Promise.race([transport.ready, new Promise((_, reject) => timer = setTimeout(reject, timeout, /* @__PURE__ */ new Error("timeout")))]);
144
+ const stream = await transport.createBidirectionalStream();
145
+ if (this.#state !== CONNECTING) return transport.close();
146
+ this.#writer = stream.writable.getWriter();
147
+ this.#datagrams = transport.datagrams.writable.getWriter();
148
+ this.#read(stream.readable.getReader());
149
+ this.#readDatagrams(transport.datagrams.readable.getReader());
150
+ } catch {
151
+ this.#transport?.close();
152
+ this.#transport = void 0;
153
+ if (this.#state !== CONNECTING) return;
154
+ if (options.fallback === false) {
155
+ this.dispatchEvent(new Event("error"));
156
+ return this.#end(1006, "", false);
157
+ }
158
+ fallingBack = true;
159
+ return this.#fallBack(protocols);
160
+ } finally {
161
+ clearTimeout(timer);
162
+ }
163
+ transport.closed.then((info) => this.#end(info.closeCode || 1005, info.reason ?? "", true), () => {
164
+ this.dispatchEvent(new Event("error"));
165
+ this.#end(1006, "", false);
166
+ });
167
+ this.#state = OPEN;
168
+ this.dispatchEvent(new Event("open"));
169
+ }
170
+ #fallBack(protocols) {
171
+ const Impl = options.fallback === false ? void 0 : options.fallback ?? globalThis.WebSocket;
172
+ if (!Impl) throw new Error("react-websocket/webtransport: no WebTransport, and no fallback");
173
+ const inner = this.#inner = new Impl(this.url, protocols);
174
+ inner.binaryType = "arraybuffer";
175
+ inner.onopen = () => {
176
+ this.protocol = inner.protocol;
177
+ this.#state = OPEN;
178
+ this.dispatchEvent(new Event("open"));
179
+ };
180
+ inner.onmessage = (event) => this.#message(event.data);
181
+ inner.onerror = () => this.dispatchEvent(new Event("error"));
182
+ inner.onclose = (event) => this.#end(event.code, event.reason, event.wasClean);
183
+ }
184
+ #write(type, payload) {
185
+ const frame = new Uint8Array(payload.byteLength + 5);
186
+ const view = new DataView(frame.buffer);
187
+ view.setUint8(0, type);
188
+ view.setUint32(1, payload.byteLength);
189
+ frame.set(payload, 5);
190
+ this.#buffered += frame.byteLength;
191
+ this.#writer.write(frame).then(() => this.#buffered -= frame.byteLength, () => {});
192
+ }
193
+ /** Reassemble frames from the stream, whatever its chunks. */
194
+ async #read(reader) {
195
+ let buffer = /* @__PURE__ */ new Uint8Array(0);
196
+ try {
197
+ for (;;) {
198
+ const { value, done } = await reader.read();
199
+ if (done) return;
200
+ const joined = new Uint8Array(buffer.byteLength + value.byteLength);
201
+ joined.set(buffer);
202
+ joined.set(value, buffer.byteLength);
203
+ buffer = joined;
204
+ while (buffer.byteLength >= 5) {
205
+ const view = new DataView(buffer.buffer, buffer.byteOffset);
206
+ const length = view.getUint32(1);
207
+ if (buffer.byteLength < 5 + length) break;
208
+ this.#frame(view.getUint8(0), buffer.slice(5, 5 + length));
209
+ buffer = buffer.slice(5 + length);
210
+ }
211
+ }
212
+ } catch {}
213
+ }
214
+ async #readDatagrams(reader) {
215
+ try {
216
+ for (;;) {
217
+ const { value, done } = await reader.read();
218
+ if (done) return;
219
+ if (value.byteLength) this.#frame(value[0], value.slice(1));
220
+ }
221
+ } catch {}
222
+ }
223
+ #frame(type, payload) {
224
+ this.#message(type === TEXT ? new TextDecoder().decode(payload) : payload.buffer);
225
+ }
226
+ #message(data) {
227
+ if (this.#state !== OPEN) return;
228
+ if (data instanceof ArrayBuffer && this.binaryType === "blob") data = new Blob([data]);
229
+ this.dispatchEvent(new MessageEvent("message", { data }));
230
+ }
231
+ #end(code, reason, wasClean) {
232
+ if (this.#state === CLOSED) return;
233
+ this.#state = CLOSED;
234
+ this.dispatchEvent(closeEvent(code, reason, wasClean));
235
+ }
236
+ }
237
+ return WebTransportSocket;
238
+ }
239
+ //#endregion
240
+ export { createWebTransportSocket };
241
+
242
+ //# sourceMappingURL=webtransport.js.map