react-websocket 3.5.0 → 4.0.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.
- package/README.md +12 -6
- package/dist/core.cjs +150 -66
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +83 -15
- package/dist/core.d.ts +83 -15
- package/dist/core.js +150 -67
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +103 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -17
- package/dist/index.d.ts +136 -11
- package/dist/index.js +104 -5
- package/dist/index.js.map +1 -1
- package/dist/timeline.d.cts +4 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
**[Documentation](https://mehmetkose.github.io/react-websocket/)** · [Getting started](https://mehmetkose.github.io/react-websocket/guide/getting-started) · [API](https://mehmetkose.github.io/react-websocket/api/) · [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Changelog](CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
- **Shared connections.** Components using the same URL share one socket. StrictMode, `<Activity>` and route transitions never open duplicates.
|
|
15
|
-
- **Auto-reconnect.** Exponential backoff with jitter, plus an immediate retry when the browser comes back online or the tab becomes visible. Optional heartbeats detect dead links.
|
|
15
|
+
- **Auto-reconnect.** Exponential backoff with full jitter, plus an immediate retry when the browser comes back online or the tab becomes visible. It backs off from a server that closes right after accepting, and stops on close codes that retrying can't fix. Optional heartbeats detect dead links.
|
|
16
16
|
- **Send queue.** `send()` before the socket is open just works. The message goes out on connect. Mark input `volatile` so it's never replayed late, or `key` it so only the newest is sent. Put control messages first with `priority`, and drop stream data on a slow link with `maxBuffered`.
|
|
17
17
|
- **Typed messages.** `json: true`, your own codec, or validation with any [Standard Schema](https://standardschema.dev) library (zod, valibot, arktype), with inferred types.
|
|
18
18
|
- **Suspense.** `use(client.ready)` waits for the first connection, and reconnects never flash a fallback.
|
|
19
19
|
- **Topic subscriptions.** Reference-counted `subscribe`/`unsubscribe` messages that are sent again after every reconnect, so feeds don't silently stop.
|
|
20
20
|
- **Message state.** Reduce messages into a store, and let each component re-render only when its slice changes, at most once per frame if you like.
|
|
21
|
-
- **Requests and latency.** Await
|
|
21
|
+
- **Requests, streams and latency.** Await a reply, or iterate over streamed ones such as an LLM's tokens, with timeouts and a cancel that tells the server to stop. Measure round-trip time and the server's clock for ping displays and synchronized countdowns.
|
|
22
22
|
- **Games.** Snapshot interpolation and client-side prediction with server reconciliation, from `react-websocket/timeline`, so other players move smoothly and your own controls respond at once. A send rate that adapts to the connection keeps players on a poor link from building up lag.
|
|
23
23
|
- **Server requests.** Answer requests from the server, such as an AI agent's, in code for browser-only tools, or from a dialog for human approval. Every request gets exactly one reply, and none is sent to the wrong connection.
|
|
24
24
|
- **SSR-safe.** Never connects during a server render, hydrates cleanly, and ships a `'use client'` entry for Next.js and other Server Components frameworks.
|
|
@@ -117,14 +117,16 @@ function Room() {
|
|
|
117
117
|
| `useWebSocketState(store, selector?)` | Read state reduced from messages; re-renders only when the selection changes |
|
|
118
118
|
| `useWebSocketLatency(latency)` | Round-trip time and server clock offset |
|
|
119
119
|
| `useAgentApproval(responder, method)` | Requests from the server waiting for a person to answer |
|
|
120
|
+
| `useWebSocketStream(rpc, options?)` | Stream the replies to a request into a value, e.g. tokens into an answer; returns `{ value, status, error, start, abort }` |
|
|
120
121
|
| `createWebSocket(url, options?)` | A shareable client: `send`, `on`, `connect`, `close`, `reconnect`, `ready`, `getSnapshot`, `subscribe` |
|
|
121
122
|
| `createWebSocketTopics(client, { subscribe, unsubscribe })` | Topic subscription manager for `useWebSocketTopic` |
|
|
122
123
|
| `createWebSocketState(client, { initialState, reduce })` | Message-state store for `useWebSocketState` |
|
|
123
|
-
| `createWebSocketRequests(client, { send, reply })` | `request(message)` returns a promise of the reply, with timeouts |
|
|
124
|
+
| `createWebSocketRequests(client, { send, reply })` | `request(message)` returns a promise of the reply, and `stream(message)` an async iterator of replies, with timeouts and `cancel` |
|
|
124
125
|
| `createWebSocketLatency(client, { ping, pong })` | Ping tracker for `useWebSocketLatency`, plus `serverNow()` |
|
|
125
126
|
| `createWebSocketSendRate(client, options?)` | `send(message, key)` coalesced per key at a rate that drops on a congested link |
|
|
126
127
|
| `createWebSocketResponder(client, { request, respond, error })` | Answer the server's requests: `onRequest(method, handler)`, or a person through `useAgentApproval` |
|
|
127
128
|
| `MessageError` | Reported to `onError` when a message fails `parse` or `schema` |
|
|
129
|
+
| `retryableClose(close)` | The default `reconnect.shouldReconnect`: retries every close except `1002`, `1003` and `1008` |
|
|
128
130
|
| `createWebSocketTimeline(client, { snapshot, interpolate, input })` | From `react-websocket/timeline`: interpolated server snapshots (`at()`) and predicted inputs (`predicted()`, `input()`) for multiplayer games |
|
|
129
131
|
| `createChaosWebSocket(options)` | From `react-websocket/chaos`: a `WebSocket` class that adds latency, lost messages and dropped connections in development |
|
|
130
132
|
|
|
@@ -135,9 +137,9 @@ function Room() {
|
|
|
135
137
|
| `json` | `false` | `JSON.parse` incoming and `JSON.stringify` outgoing messages |
|
|
136
138
|
| `parse(data)` / `serialize(message)` | | Custom codec; takes precedence over `json` |
|
|
137
139
|
| `schema` | | Standard Schema validation; types are inferred |
|
|
138
|
-
| `reconnect` | `true` | `false`, or `{ maxAttempts, minDelay, maxDelay, factor, jitter, shouldReconnect }` |
|
|
140
|
+
| `reconnect` | `true` | `false`, or `{ maxAttempts, minDelay, maxDelay, factor, jitter, minUptime, shouldReconnect }` |
|
|
139
141
|
| `heartbeat` | | `{ interval, message, pong, timeout }` |
|
|
140
|
-
| `queue` | `true` | `false`, or `{ limit }` |
|
|
142
|
+
| `queue` | `true` (100 messages) | `false`, or `{ limit }` |
|
|
141
143
|
| `logger` | | e.g. `console.debug` |
|
|
142
144
|
| `WebSocket` | global | Custom implementation for tests, Node, or chaos testing |
|
|
143
145
|
|
|
@@ -148,7 +150,7 @@ function Room() {
|
|
|
148
150
|
- [Shared clients](https://mehmetkose.github.io/react-websocket/guide/shared-clients) · [Messages and types](https://mehmetkose.github.io/react-websocket/guide/messages) · [Reconnecting and heartbeats](https://mehmetkose.github.io/react-websocket/guide/reconnecting)
|
|
149
151
|
- [Suspense](https://mehmetkose.github.io/react-websocket/guide/suspense) · [Server rendering and Next.js](https://mehmetkose.github.io/react-websocket/guide/ssr) · [StrictMode, Activity and lifecycle](https://mehmetkose.github.io/react-websocket/guide/lifecycle)
|
|
150
152
|
- [Authentication](https://mehmetkose.github.io/react-websocket/guide/authentication) · [Performance](https://mehmetkose.github.io/react-websocket/guide/performance) · [Real-time games](https://mehmetkose.github.io/react-websocket/guide/games) · [AI agents](https://mehmetkose.github.io/react-websocket/guide/agents) · [Testing](https://mehmetkose.github.io/react-websocket/guide/testing) · [Chaos testing](https://mehmetkose.github.io/react-websocket/guide/chaos) · [Without React](https://mehmetkose.github.io/react-websocket/guide/without-react)
|
|
151
|
-
- [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Troubleshooting](https://mehmetkose.github.io/react-websocket/troubleshooting) · [Migrating from v2](https://mehmetkose.github.io/react-websocket/migration)
|
|
153
|
+
- [Recipes](https://mehmetkose.github.io/react-websocket/recipes) · [Troubleshooting](https://mehmetkose.github.io/react-websocket/troubleshooting) · [Migrating from v3](https://mehmetkose.github.io/react-websocket/migration-v3) · [Migrating from v2](https://mehmetkose.github.io/react-websocket/migration)
|
|
152
154
|
|
|
153
155
|
The docs source lives in [`docs/`](docs). Run `npm run docs:dev` to browse it locally.
|
|
154
156
|
|
|
@@ -160,6 +162,10 @@ The docs source lives in [`docs/`](docs). Run `npm run docs:dev` to browse it lo
|
|
|
160
162
|
| Runtimes | Modern browsers; Node via the `WebSocket` option |
|
|
161
163
|
| Frameworks | Vite, Next.js (App and Pages Router), Remix/React Router, Astro islands, and any React setup |
|
|
162
164
|
|
|
165
|
+
## Migrating from v3
|
|
166
|
+
|
|
167
|
+
v4 changes no APIs, only four defaults: the backoff resets once a connection has stayed open for 5 seconds, close codes `1002`, `1003` and `1008` aren't retried, jitter is full, and the send queue holds 100 messages. Each has a one-line opt-out. See [Migrating from v3](https://mehmetkose.github.io/react-websocket/migration-v3).
|
|
168
|
+
|
|
163
169
|
## Migrating from v2
|
|
164
170
|
|
|
165
171
|
v2 was a `<Websocket>` class component. v3 replaces it with hooks: `<Websocket url onMessage />` becomes `useWebSocket(url, { onMessage })`, and `ref.sendMessage(msg)` becomes `send(msg)`. See the [migration guide](https://mehmetkose.github.io/react-websocket/migration) for the full mapping.
|
package/dist/core.cjs
CHANGED
|
@@ -31,8 +31,26 @@ const RECONNECT_DEFAULTS = {
|
|
|
31
31
|
minDelay: 1e3,
|
|
32
32
|
maxDelay: 3e4,
|
|
33
33
|
factor: 2,
|
|
34
|
-
jitter:
|
|
34
|
+
jitter: 1,
|
|
35
|
+
minUptime: 5e3
|
|
35
36
|
};
|
|
37
|
+
const QUEUE_LIMIT = 100;
|
|
38
|
+
/**
|
|
39
|
+
* The default `reconnect.shouldReconnect`: retry every close except `1002` (protocol error),
|
|
40
|
+
* `1003` (unsupported data) and `1008` (policy violation, often a rejected login), which
|
|
41
|
+
* retrying the same way can't fix. Call it from your own `shouldReconnect` to keep that rule.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* createWebSocket(url, {
|
|
46
|
+
* // Also stop on our server's "invalid token" code.
|
|
47
|
+
* reconnect: { shouldReconnect: close => close.code !== 4001 && retryableClose(close) }
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function retryableClose(close) {
|
|
52
|
+
return close.code !== 1002 && close.code !== 1003 && close.code !== 1008;
|
|
53
|
+
}
|
|
36
54
|
const READY_GRACE = 5e3;
|
|
37
55
|
function createDeferred() {
|
|
38
56
|
let resolve;
|
|
@@ -84,7 +102,7 @@ function createClient(url, options = {}) {
|
|
|
84
102
|
...RECONNECT_DEFAULTS,
|
|
85
103
|
...typeof options.reconnect === "object" ? options.reconnect : {}
|
|
86
104
|
};
|
|
87
|
-
const queueLimit = options.queue === false ? 0 : typeof options.queue === "object" ?
|
|
105
|
+
const queueLimit = options.queue === false ? 0 : Math.max(0, (typeof options.queue === "object" ? options.queue.limit : void 0) ?? QUEUE_LIMIT);
|
|
88
106
|
let snapshot = {
|
|
89
107
|
status: "idle",
|
|
90
108
|
lastMessage: void 0,
|
|
@@ -109,8 +127,10 @@ function createClient(url, options = {}) {
|
|
|
109
127
|
let inbox = null;
|
|
110
128
|
let generation = 0;
|
|
111
129
|
let windowBound = false;
|
|
130
|
+
let failures = 0;
|
|
112
131
|
let retryTimer;
|
|
113
132
|
let teardownTimer;
|
|
133
|
+
let stableTimer;
|
|
114
134
|
let heartbeatTimer;
|
|
115
135
|
let pongTimer;
|
|
116
136
|
function set(patch) {
|
|
@@ -177,10 +197,12 @@ function createClient(url, options = {}) {
|
|
|
177
197
|
}, heartbeat.timeout);
|
|
178
198
|
}, heartbeat.interval);
|
|
179
199
|
}
|
|
180
|
-
|
|
200
|
+
/** Stop the timers that belong to the current open socket. */
|
|
201
|
+
function stopSocketTimers() {
|
|
181
202
|
clearInterval(heartbeatTimer);
|
|
182
203
|
clearTimeout(pongTimer);
|
|
183
|
-
|
|
204
|
+
clearTimeout(stableTimer);
|
|
205
|
+
heartbeatTimer = pongTimer = stableTimer = void 0;
|
|
184
206
|
}
|
|
185
207
|
/** Stop tracking `socket`. Its eventual close event is still reported unless `reportClose` is false. */
|
|
186
208
|
function detach(socket, reportClose) {
|
|
@@ -251,6 +273,7 @@ function createClient(url, options = {}) {
|
|
|
251
273
|
status: "open",
|
|
252
274
|
attempt: 0
|
|
253
275
|
});
|
|
276
|
+
if (retry) stableTimer = setTimeout(() => failures = 0, retry.minUptime);
|
|
254
277
|
startHeartbeat(socket);
|
|
255
278
|
ready?.resolve();
|
|
256
279
|
if (refs === 0 && !pinned) {
|
|
@@ -275,14 +298,14 @@ function createClient(url, options = {}) {
|
|
|
275
298
|
};
|
|
276
299
|
}
|
|
277
300
|
function handleClose(info) {
|
|
278
|
-
|
|
301
|
+
stopSocketTimers();
|
|
279
302
|
log("closed", info);
|
|
280
303
|
retryOrFail(info);
|
|
281
304
|
emit("close", info);
|
|
282
305
|
}
|
|
283
306
|
function retryOrFail(info, error) {
|
|
284
|
-
if (wanted && retry &&
|
|
285
|
-
const attempt =
|
|
307
|
+
if (wanted && retry && failures < retry.maxAttempts && (retry.shouldReconnect ?? retryableClose)(info)) {
|
|
308
|
+
const attempt = ++failures;
|
|
286
309
|
const delay = backoff(attempt);
|
|
287
310
|
set({
|
|
288
311
|
status: "reconnecting",
|
|
@@ -297,7 +320,10 @@ function createClient(url, options = {}) {
|
|
|
297
320
|
opened = false;
|
|
298
321
|
unbindWindow();
|
|
299
322
|
pending = [];
|
|
300
|
-
set({
|
|
323
|
+
set({
|
|
324
|
+
status: "closed",
|
|
325
|
+
attempt: failures
|
|
326
|
+
});
|
|
301
327
|
rejectReady(reason);
|
|
302
328
|
}
|
|
303
329
|
function receive(event, socket) {
|
|
@@ -402,6 +428,7 @@ function createClient(url, options = {}) {
|
|
|
402
428
|
opened = false;
|
|
403
429
|
if (ready?.settled) ready = null;
|
|
404
430
|
}
|
|
431
|
+
failures = 0;
|
|
405
432
|
snapshot = {
|
|
406
433
|
...snapshot,
|
|
407
434
|
attempt: 0
|
|
@@ -423,7 +450,8 @@ function createClient(url, options = {}) {
|
|
|
423
450
|
generation++;
|
|
424
451
|
clearTimeout(retryTimer);
|
|
425
452
|
retryTimer = void 0;
|
|
426
|
-
|
|
453
|
+
failures = 0;
|
|
454
|
+
stopSocketTimers();
|
|
427
455
|
unbindWindow();
|
|
428
456
|
pending = [];
|
|
429
457
|
if (ws) {
|
|
@@ -446,7 +474,7 @@ function createClient(url, options = {}) {
|
|
|
446
474
|
if (!getImpl()) return;
|
|
447
475
|
clearTimeout(retryTimer);
|
|
448
476
|
retryTimer = void 0;
|
|
449
|
-
|
|
477
|
+
stopSocketTimers();
|
|
450
478
|
if (ws) {
|
|
451
479
|
const socket = ws;
|
|
452
480
|
detach(socket, true);
|
|
@@ -660,6 +688,8 @@ function createWebSocketLatency(client, options) {
|
|
|
660
688
|
//#endregion
|
|
661
689
|
//#region src/requests.ts
|
|
662
690
|
const lastIds = /* @__PURE__ */ new WeakMap();
|
|
691
|
+
const VOLATILE$2 = { volatile: true };
|
|
692
|
+
const noop = () => {};
|
|
663
693
|
/**
|
|
664
694
|
* Send requests and await their replies, matched by an id the library generates.
|
|
665
695
|
*
|
|
@@ -680,8 +710,8 @@ const lastIds = /* @__PURE__ */ new WeakMap();
|
|
|
680
710
|
*/
|
|
681
711
|
function createWebSocketRequests(client, options) {
|
|
682
712
|
const waiting = /* @__PURE__ */ new Map();
|
|
683
|
-
function
|
|
684
|
-
for (const entry of [...waiting.values()]) if (test(entry)) entry.
|
|
713
|
+
function endWhere(test, reason, cancel) {
|
|
714
|
+
for (const entry of [...waiting.values()]) if (test(entry)) entry.end(true, reason(), cancel);
|
|
685
715
|
}
|
|
686
716
|
const offs = [
|
|
687
717
|
client.on("message", (message) => {
|
|
@@ -689,78 +719,131 @@ function createWebSocketRequests(client, options) {
|
|
|
689
719
|
const entry = id == null ? void 0 : waiting.get(String(id));
|
|
690
720
|
if (!entry) return;
|
|
691
721
|
const error = options.error?.(message);
|
|
692
|
-
if (error === void 0) entry.
|
|
693
|
-
else entry.
|
|
722
|
+
if (error === void 0) entry.receive(message);
|
|
723
|
+
else entry.end(true, error);
|
|
694
724
|
}),
|
|
695
725
|
client.on("open", () => {
|
|
696
726
|
for (const entry of waiting.values()) entry.socket ??= client.socket;
|
|
697
727
|
}),
|
|
698
728
|
client.subscribe(() => {
|
|
699
729
|
const { status } = client.getSnapshot();
|
|
700
|
-
if (status === "closed" || status === "idle")
|
|
701
|
-
else
|
|
730
|
+
if (status === "closed" || status === "idle") endWhere(() => true, () => /* @__PURE__ */ new Error("WebSocket was closed before the request was answered"));
|
|
731
|
+
else endWhere((entry) => entry.socket !== null && entry.socket !== client.socket, () => /* @__PURE__ */ new Error("WebSocket connection was lost before the request was answered"));
|
|
702
732
|
})
|
|
703
733
|
];
|
|
734
|
+
/**
|
|
735
|
+
* Send `message` as a new request. `next` gets each reply that isn't an error and returns
|
|
736
|
+
* whether it was the last; `done` runs once, when the request ends. Returns a function that
|
|
737
|
+
* ends it from this side and tells the server.
|
|
738
|
+
*/
|
|
739
|
+
function start(message, requestOptions, next, done) {
|
|
740
|
+
const { signal, priority } = requestOptions;
|
|
741
|
+
if (signal?.aborted) {
|
|
742
|
+
done(true, signal.reason);
|
|
743
|
+
return noop;
|
|
744
|
+
}
|
|
745
|
+
const id = (lastIds.get(client) ?? 0) + 1;
|
|
746
|
+
lastIds.set(client, id);
|
|
747
|
+
const key = String(id);
|
|
748
|
+
const timeout = requestOptions.timeout ?? options.timeout ?? 1e4;
|
|
749
|
+
const release = client.acquire();
|
|
750
|
+
let timer;
|
|
751
|
+
const onAbort = () => entry.end(true, signal.reason, true);
|
|
752
|
+
function arm() {
|
|
753
|
+
clearTimeout(timer);
|
|
754
|
+
if (timeout > 0 && timeout < Infinity) timer = setTimeout(() => entry.end(true, new DOMException(`Request ${id} timed out after ${timeout}ms`, "TimeoutError"), true), timeout);
|
|
755
|
+
}
|
|
756
|
+
const entry = {
|
|
757
|
+
socket: null,
|
|
758
|
+
receive(reply) {
|
|
759
|
+
if (next(reply)) entry.end(false);
|
|
760
|
+
else arm();
|
|
761
|
+
},
|
|
762
|
+
end(failed, reason, cancel) {
|
|
763
|
+
if (!waiting.delete(key)) return;
|
|
764
|
+
clearTimeout(timer);
|
|
765
|
+
signal?.removeEventListener("abort", onAbort);
|
|
766
|
+
release();
|
|
767
|
+
done(failed, reason);
|
|
768
|
+
if (cancel && options.cancel) try {
|
|
769
|
+
client.send(options.cancel(id), entry.socket ? VOLATILE$2 : { priority });
|
|
770
|
+
} catch (error) {
|
|
771
|
+
queueMicrotask(() => {
|
|
772
|
+
throw error;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
waiting.set(key, entry);
|
|
778
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
779
|
+
arm();
|
|
780
|
+
const { status } = client.getSnapshot();
|
|
781
|
+
if (status === "closed" || status === "idle") entry.end(true, /* @__PURE__ */ new Error(`WebSocket is ${status}: request not sent`));
|
|
782
|
+
else try {
|
|
783
|
+
if (client.send(options.send(message, id), {
|
|
784
|
+
...requestOptions,
|
|
785
|
+
onDrop() {
|
|
786
|
+
entry.end(true, /* @__PURE__ */ new Error("Request was dropped from the send queue before it was sent"));
|
|
787
|
+
requestOptions.onDrop?.();
|
|
788
|
+
}
|
|
789
|
+
})) entry.socket = client.socket;
|
|
790
|
+
else if (client.socket?.readyState === 1) entry.end(true, /* @__PURE__ */ new Error("WebSocket send buffer is full: request dropped"));
|
|
791
|
+
else if (requestOptions.volatile) entry.end(true, /* @__PURE__ */ new Error("WebSocket is not open: request dropped"));
|
|
792
|
+
} catch (error) {
|
|
793
|
+
entry.end(true, error);
|
|
794
|
+
}
|
|
795
|
+
return () => entry.end(false, void 0, true);
|
|
796
|
+
}
|
|
704
797
|
return {
|
|
705
798
|
client,
|
|
706
799
|
request(message, requestOptions = {}) {
|
|
707
|
-
const { signal } = requestOptions;
|
|
708
|
-
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
709
|
-
const id = (lastIds.get(client) ?? 0) + 1;
|
|
710
|
-
lastIds.set(client, id);
|
|
711
|
-
const key = String(id);
|
|
712
|
-
const timeout = requestOptions.timeout ?? options.timeout ?? 1e4;
|
|
713
800
|
return new Promise((resolve, reject) => {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (status === "closed" || status === "idle") {
|
|
739
|
-
entry.reject(/* @__PURE__ */ new Error(`WebSocket is ${status}: request not sent`));
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
let sent;
|
|
801
|
+
start(message, requestOptions, (reply) => {
|
|
802
|
+
resolve(reply);
|
|
803
|
+
return true;
|
|
804
|
+
}, (failed, reason) => {
|
|
805
|
+
if (failed) reject(reason);
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
},
|
|
809
|
+
stream(message, requestOptions = {}) {
|
|
810
|
+
const last = options.done;
|
|
811
|
+
if (!last) throw new TypeError("stream() needs the `done` option");
|
|
812
|
+
const replies = [];
|
|
813
|
+
let ended;
|
|
814
|
+
let wake = noop;
|
|
815
|
+
const stop = start(message, requestOptions, (reply) => {
|
|
816
|
+
replies.push(reply);
|
|
817
|
+
wake();
|
|
818
|
+
return last(reply);
|
|
819
|
+
}, (failed, reason) => {
|
|
820
|
+
if (requestOptions.signal?.aborted) replies.length = 0;
|
|
821
|
+
ended = [failed, reason];
|
|
822
|
+
wake();
|
|
823
|
+
});
|
|
824
|
+
async function* read() {
|
|
743
825
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} catch (error) {
|
|
752
|
-
entry.reject(error);
|
|
753
|
-
return;
|
|
826
|
+
for (;;) if (replies.length) yield replies.shift();
|
|
827
|
+
else if (ended) {
|
|
828
|
+
if (ended[0]) throw ended[1];
|
|
829
|
+
return;
|
|
830
|
+
} else await new Promise((resolve) => wake = resolve);
|
|
831
|
+
} finally {
|
|
832
|
+
stop();
|
|
754
833
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
834
|
+
}
|
|
835
|
+
const iterator = read();
|
|
836
|
+
const close = iterator.return;
|
|
837
|
+
iterator.return = (value) => {
|
|
838
|
+
stop();
|
|
839
|
+
return close.call(iterator, value);
|
|
840
|
+
};
|
|
841
|
+
return iterator;
|
|
759
842
|
},
|
|
760
843
|
pending: () => waiting.size,
|
|
761
844
|
dispose() {
|
|
762
845
|
offs.forEach((off) => off());
|
|
763
|
-
|
|
846
|
+
endWhere(() => true, () => /* @__PURE__ */ new Error("Requests were disposed"), true);
|
|
764
847
|
}
|
|
765
848
|
};
|
|
766
849
|
}
|
|
@@ -1177,5 +1260,6 @@ exports.createWebSocketResponder = createWebSocketResponder;
|
|
|
1177
1260
|
exports.createWebSocketSendRate = createWebSocketSendRate;
|
|
1178
1261
|
exports.createWebSocketState = createWebSocketState;
|
|
1179
1262
|
exports.createWebSocketTopics = createWebSocketTopics;
|
|
1263
|
+
exports.retryableClose = retryableClose;
|
|
1180
1264
|
|
|
1181
1265
|
//# sourceMappingURL=core.cjs.map
|