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/dist/core.d.cts
CHANGED
|
@@ -29,7 +29,10 @@ interface Snapshot<In> {
|
|
|
29
29
|
readonly status: Status;
|
|
30
30
|
/** Most recent message (after `parse`/`json` and `schema`), kept across reconnects. */
|
|
31
31
|
readonly lastMessage: In | undefined;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Consecutive failed attempts since the last stable connection. A connection that closes
|
|
34
|
+
* within `reconnect.minUptime` of opening counts as failed. `0` while open.
|
|
35
|
+
*/
|
|
33
36
|
readonly attempt: number;
|
|
34
37
|
}
|
|
35
38
|
/** Details of a closed connection, from the browser's `CloseEvent`. */
|
|
@@ -51,6 +54,14 @@ interface ReconnectOptions {
|
|
|
51
54
|
* @default Infinity
|
|
52
55
|
*/
|
|
53
56
|
maxAttempts?: number;
|
|
57
|
+
/**
|
|
58
|
+
* How long a connection must stay open, in ms, before the backoff resets. One that closes
|
|
59
|
+
* sooner counts as a failed attempt, so a server that accepts and then closes right away
|
|
60
|
+
* (e.g. rejecting the credentials sent on open) is retried with a growing delay, and
|
|
61
|
+
* `maxAttempts` still applies. `0` resets on every open.
|
|
62
|
+
* @default 5000
|
|
63
|
+
*/
|
|
64
|
+
minUptime?: number;
|
|
54
65
|
/**
|
|
55
66
|
* Delay before the first retry, in ms.
|
|
56
67
|
* @default 1000
|
|
@@ -68,19 +79,26 @@ interface ReconnectOptions {
|
|
|
68
79
|
factor?: number;
|
|
69
80
|
/**
|
|
70
81
|
* Randomly shave up to this fraction (0–1) off each delay, so that many clients
|
|
71
|
-
* dropped at once don't all reconnect at the same moment.
|
|
72
|
-
*
|
|
82
|
+
* dropped at once don't all reconnect at the same moment. The default `1` picks each
|
|
83
|
+
* delay at random between 0 and the backoff ("full jitter").
|
|
84
|
+
* @default 1
|
|
73
85
|
*/
|
|
74
86
|
jitter?: number;
|
|
75
87
|
/**
|
|
76
88
|
* Called on every unexpected close. Return `false` to stop reconnecting.
|
|
77
89
|
*
|
|
90
|
+
* The default, `retryableClose`, retries everything except `1002` (protocol error), `1003`
|
|
91
|
+
* (unsupported data) and `1008` (policy violation). Your own function replaces it; call
|
|
92
|
+
* `retryableClose` from it to keep that rule, or return `true` to retry every close.
|
|
93
|
+
*
|
|
78
94
|
* @example
|
|
79
95
|
* ```ts
|
|
80
|
-
*
|
|
81
|
-
*
|
|
96
|
+
* import { retryableClose } from 'react-websocket';
|
|
97
|
+
*
|
|
98
|
+
* // Also stop when our server rejects the credentials.
|
|
99
|
+
* shouldReconnect: close => close.code !== 4001 && retryableClose(close)
|
|
82
100
|
* ```
|
|
83
|
-
* @default
|
|
101
|
+
* @default retryableClose
|
|
84
102
|
*/
|
|
85
103
|
shouldReconnect?: (close: CloseInfo) => boolean;
|
|
86
104
|
}
|
|
@@ -155,7 +173,8 @@ interface QueueOptions {
|
|
|
155
173
|
/**
|
|
156
174
|
* Maximum number of messages held while not connected. When full, the oldest
|
|
157
175
|
* message is dropped, skipping `priority` messages unless nothing else is queued.
|
|
158
|
-
*
|
|
176
|
+
* `Infinity` keeps every message.
|
|
177
|
+
* @default 100
|
|
159
178
|
*/
|
|
160
179
|
limit?: number;
|
|
161
180
|
}
|
|
@@ -231,8 +250,8 @@ interface WebSocketOptions<In = unknown, Out = unknown> {
|
|
|
231
250
|
heartbeat?: HeartbeatOptions;
|
|
232
251
|
/**
|
|
233
252
|
* Buffer `send()` calls made while not connected and send them, in order, on open
|
|
234
|
-
* (right after `open` listeners run).
|
|
235
|
-
* `
|
|
253
|
+
* (right after `open` listeners run). It holds up to 100 messages, dropping the oldest;
|
|
254
|
+
* `{ limit }` changes that. `false` drops messages instead of queueing them.
|
|
236
255
|
* The queue is cleared by `close()` or when the client gives up.
|
|
237
256
|
* @default true
|
|
238
257
|
*/
|
|
@@ -374,6 +393,20 @@ interface WebSocketClient<In = unknown, Out = unknown> {
|
|
|
374
393
|
}
|
|
375
394
|
//#endregion
|
|
376
395
|
//#region src/client.d.ts
|
|
396
|
+
/**
|
|
397
|
+
* The default `reconnect.shouldReconnect`: retry every close except `1002` (protocol error),
|
|
398
|
+
* `1003` (unsupported data) and `1008` (policy violation, often a rejected login), which
|
|
399
|
+
* retrying the same way can't fix. Call it from your own `shouldReconnect` to keep that rule.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* createWebSocket(url, {
|
|
404
|
+
* // Also stop on our server's "invalid token" code.
|
|
405
|
+
* reconnect: { shouldReconnect: close => close.code !== 4001 && retryableClose(close) }
|
|
406
|
+
* });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
export declare function retryableClose(close: CloseInfo): boolean;
|
|
377
410
|
/**
|
|
378
411
|
* Create a WebSocket client, usually at module level, to share across components.
|
|
379
412
|
*
|
|
@@ -487,20 +520,36 @@ interface WebSocketRequestsOptions<Req, In, Out> {
|
|
|
487
520
|
reply: (message: In) => number | string | null | undefined;
|
|
488
521
|
/**
|
|
489
522
|
* Called on a reply. Return something other than `undefined` to reject the request with
|
|
490
|
-
* it instead of resolving, e.g. for a server-side error reply.
|
|
523
|
+
* it instead of resolving, e.g. for a server-side error reply. A stream throws it.
|
|
491
524
|
*/
|
|
492
525
|
error?: (message: In) => unknown;
|
|
493
526
|
/**
|
|
494
|
-
*
|
|
527
|
+
* Whether a reply is the last one of a stream. `stream()` needs it. That reply is yielded
|
|
528
|
+
* too, then the stream ends.
|
|
529
|
+
*/
|
|
530
|
+
done?: (message: In) => boolean;
|
|
531
|
+
/**
|
|
532
|
+
* Build the message that tells the server to stop working on request `id`. Sent when this
|
|
533
|
+
* side gives up: the request's `signal` aborts, it times out, a loop over a stream exits
|
|
534
|
+
* early, or the manager is disposed. Not sent after the last reply, an error reply, a lost
|
|
535
|
+
* connection or `close()`.
|
|
536
|
+
*/
|
|
537
|
+
cancel?: (id: number) => Out;
|
|
538
|
+
/**
|
|
539
|
+
* Default time to wait for a reply, in ms. For a stream, the time to wait for each reply,
|
|
540
|
+
* including the first. `Infinity` waits forever.
|
|
495
541
|
* @default 10000
|
|
496
542
|
*/
|
|
497
543
|
timeout?: number;
|
|
498
544
|
}
|
|
499
|
-
/** Per-call options for {@link WebSocketRequests.request}. */
|
|
545
|
+
/** Per-call options for {@link WebSocketRequests.request} and {@link WebSocketRequests.stream}. */
|
|
500
546
|
interface RequestOptions extends SendOptions {
|
|
501
|
-
/**
|
|
547
|
+
/**
|
|
548
|
+
* Time to wait for a reply (for a stream, for each reply), in ms. Overrides the manager's
|
|
549
|
+
* `timeout`.
|
|
550
|
+
*/
|
|
502
551
|
timeout?: number;
|
|
503
|
-
/** Abort the request. It rejects with `signal.reason`. A late reply is ignored. */
|
|
552
|
+
/** Abort the request. It rejects (a stream throws) with `signal.reason`. A late reply is ignored. */
|
|
504
553
|
signal?: AbortSignal;
|
|
505
554
|
}
|
|
506
555
|
/** Request/response over one client. Create with {@link createWebSocketRequests}. */
|
|
@@ -517,7 +566,26 @@ interface WebSocketRequests<Req, In> {
|
|
|
517
566
|
* queue (replaced by a newer request with the same `key`, or evicted by `queue.limit`).
|
|
518
567
|
*/
|
|
519
568
|
request(message: Req, options?: RequestOptions): Promise<In>;
|
|
520
|
-
/**
|
|
569
|
+
/**
|
|
570
|
+
* Send a request and iterate over its replies, up to and including the one `done` matches.
|
|
571
|
+
* Holds the connection until the stream ends. Needs the `done` option.
|
|
572
|
+
*
|
|
573
|
+
* `timeout` is the time to wait for each reply. Replies that arrive before the loop reads
|
|
574
|
+
* them are buffered. The stream is one-shot: iterate it once.
|
|
575
|
+
*
|
|
576
|
+
* Fails where `request()` rejects, with the same errors, after yielding the replies already
|
|
577
|
+
* received. Aborting `signal` throws `signal.reason` right away and discards them. Leaving
|
|
578
|
+
* the loop early, aborting and timing out send `cancel`.
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```ts
|
|
582
|
+
* for await (const reply of rpc.stream({ type: 'ask', prompt }, { signal })) {
|
|
583
|
+
* text += reply.delta;
|
|
584
|
+
* }
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
stream(message: Req, options?: RequestOptions): AsyncIterableIterator<In>;
|
|
588
|
+
/** Number of requests and streams waiting for a reply. */
|
|
521
589
|
pending(): number;
|
|
522
590
|
/** Reject every pending request and stop listening. Only needed for managers created dynamically. */
|
|
523
591
|
dispose(): void;
|
package/dist/core.d.ts
CHANGED
|
@@ -29,7 +29,10 @@ interface Snapshot<In> {
|
|
|
29
29
|
readonly status: Status;
|
|
30
30
|
/** Most recent message (after `parse`/`json` and `schema`), kept across reconnects. */
|
|
31
31
|
readonly lastMessage: In | undefined;
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Consecutive failed attempts since the last stable connection. A connection that closes
|
|
34
|
+
* within `reconnect.minUptime` of opening counts as failed. `0` while open.
|
|
35
|
+
*/
|
|
33
36
|
readonly attempt: number;
|
|
34
37
|
}
|
|
35
38
|
/** Details of a closed connection, from the browser's `CloseEvent`. */
|
|
@@ -51,6 +54,14 @@ interface ReconnectOptions {
|
|
|
51
54
|
* @default Infinity
|
|
52
55
|
*/
|
|
53
56
|
maxAttempts?: number;
|
|
57
|
+
/**
|
|
58
|
+
* How long a connection must stay open, in ms, before the backoff resets. One that closes
|
|
59
|
+
* sooner counts as a failed attempt, so a server that accepts and then closes right away
|
|
60
|
+
* (e.g. rejecting the credentials sent on open) is retried with a growing delay, and
|
|
61
|
+
* `maxAttempts` still applies. `0` resets on every open.
|
|
62
|
+
* @default 5000
|
|
63
|
+
*/
|
|
64
|
+
minUptime?: number;
|
|
54
65
|
/**
|
|
55
66
|
* Delay before the first retry, in ms.
|
|
56
67
|
* @default 1000
|
|
@@ -68,19 +79,26 @@ interface ReconnectOptions {
|
|
|
68
79
|
factor?: number;
|
|
69
80
|
/**
|
|
70
81
|
* Randomly shave up to this fraction (0–1) off each delay, so that many clients
|
|
71
|
-
* dropped at once don't all reconnect at the same moment.
|
|
72
|
-
*
|
|
82
|
+
* dropped at once don't all reconnect at the same moment. The default `1` picks each
|
|
83
|
+
* delay at random between 0 and the backoff ("full jitter").
|
|
84
|
+
* @default 1
|
|
73
85
|
*/
|
|
74
86
|
jitter?: number;
|
|
75
87
|
/**
|
|
76
88
|
* Called on every unexpected close. Return `false` to stop reconnecting.
|
|
77
89
|
*
|
|
90
|
+
* The default, `retryableClose`, retries everything except `1002` (protocol error), `1003`
|
|
91
|
+
* (unsupported data) and `1008` (policy violation). Your own function replaces it; call
|
|
92
|
+
* `retryableClose` from it to keep that rule, or return `true` to retry every close.
|
|
93
|
+
*
|
|
78
94
|
* @example
|
|
79
95
|
* ```ts
|
|
80
|
-
*
|
|
81
|
-
*
|
|
96
|
+
* import { retryableClose } from 'react-websocket';
|
|
97
|
+
*
|
|
98
|
+
* // Also stop when our server rejects the credentials.
|
|
99
|
+
* shouldReconnect: close => close.code !== 4001 && retryableClose(close)
|
|
82
100
|
* ```
|
|
83
|
-
* @default
|
|
101
|
+
* @default retryableClose
|
|
84
102
|
*/
|
|
85
103
|
shouldReconnect?: (close: CloseInfo) => boolean;
|
|
86
104
|
}
|
|
@@ -155,7 +173,8 @@ interface QueueOptions {
|
|
|
155
173
|
/**
|
|
156
174
|
* Maximum number of messages held while not connected. When full, the oldest
|
|
157
175
|
* message is dropped, skipping `priority` messages unless nothing else is queued.
|
|
158
|
-
*
|
|
176
|
+
* `Infinity` keeps every message.
|
|
177
|
+
* @default 100
|
|
159
178
|
*/
|
|
160
179
|
limit?: number;
|
|
161
180
|
}
|
|
@@ -231,8 +250,8 @@ interface WebSocketOptions<In = unknown, Out = unknown> {
|
|
|
231
250
|
heartbeat?: HeartbeatOptions;
|
|
232
251
|
/**
|
|
233
252
|
* Buffer `send()` calls made while not connected and send them, in order, on open
|
|
234
|
-
* (right after `open` listeners run).
|
|
235
|
-
* `
|
|
253
|
+
* (right after `open` listeners run). It holds up to 100 messages, dropping the oldest;
|
|
254
|
+
* `{ limit }` changes that. `false` drops messages instead of queueing them.
|
|
236
255
|
* The queue is cleared by `close()` or when the client gives up.
|
|
237
256
|
* @default true
|
|
238
257
|
*/
|
|
@@ -374,6 +393,20 @@ interface WebSocketClient<In = unknown, Out = unknown> {
|
|
|
374
393
|
}
|
|
375
394
|
//#endregion
|
|
376
395
|
//#region src/client.d.ts
|
|
396
|
+
/**
|
|
397
|
+
* The default `reconnect.shouldReconnect`: retry every close except `1002` (protocol error),
|
|
398
|
+
* `1003` (unsupported data) and `1008` (policy violation, often a rejected login), which
|
|
399
|
+
* retrying the same way can't fix. Call it from your own `shouldReconnect` to keep that rule.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* createWebSocket(url, {
|
|
404
|
+
* // Also stop on our server's "invalid token" code.
|
|
405
|
+
* reconnect: { shouldReconnect: close => close.code !== 4001 && retryableClose(close) }
|
|
406
|
+
* });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
export declare function retryableClose(close: CloseInfo): boolean;
|
|
377
410
|
/**
|
|
378
411
|
* Create a WebSocket client, usually at module level, to share across components.
|
|
379
412
|
*
|
|
@@ -487,20 +520,36 @@ interface WebSocketRequestsOptions<Req, In, Out> {
|
|
|
487
520
|
reply: (message: In) => number | string | null | undefined;
|
|
488
521
|
/**
|
|
489
522
|
* Called on a reply. Return something other than `undefined` to reject the request with
|
|
490
|
-
* it instead of resolving, e.g. for a server-side error reply.
|
|
523
|
+
* it instead of resolving, e.g. for a server-side error reply. A stream throws it.
|
|
491
524
|
*/
|
|
492
525
|
error?: (message: In) => unknown;
|
|
493
526
|
/**
|
|
494
|
-
*
|
|
527
|
+
* Whether a reply is the last one of a stream. `stream()` needs it. That reply is yielded
|
|
528
|
+
* too, then the stream ends.
|
|
529
|
+
*/
|
|
530
|
+
done?: (message: In) => boolean;
|
|
531
|
+
/**
|
|
532
|
+
* Build the message that tells the server to stop working on request `id`. Sent when this
|
|
533
|
+
* side gives up: the request's `signal` aborts, it times out, a loop over a stream exits
|
|
534
|
+
* early, or the manager is disposed. Not sent after the last reply, an error reply, a lost
|
|
535
|
+
* connection or `close()`.
|
|
536
|
+
*/
|
|
537
|
+
cancel?: (id: number) => Out;
|
|
538
|
+
/**
|
|
539
|
+
* Default time to wait for a reply, in ms. For a stream, the time to wait for each reply,
|
|
540
|
+
* including the first. `Infinity` waits forever.
|
|
495
541
|
* @default 10000
|
|
496
542
|
*/
|
|
497
543
|
timeout?: number;
|
|
498
544
|
}
|
|
499
|
-
/** Per-call options for {@link WebSocketRequests.request}. */
|
|
545
|
+
/** Per-call options for {@link WebSocketRequests.request} and {@link WebSocketRequests.stream}. */
|
|
500
546
|
interface RequestOptions extends SendOptions {
|
|
501
|
-
/**
|
|
547
|
+
/**
|
|
548
|
+
* Time to wait for a reply (for a stream, for each reply), in ms. Overrides the manager's
|
|
549
|
+
* `timeout`.
|
|
550
|
+
*/
|
|
502
551
|
timeout?: number;
|
|
503
|
-
/** Abort the request. It rejects with `signal.reason`. A late reply is ignored. */
|
|
552
|
+
/** Abort the request. It rejects (a stream throws) with `signal.reason`. A late reply is ignored. */
|
|
504
553
|
signal?: AbortSignal;
|
|
505
554
|
}
|
|
506
555
|
/** Request/response over one client. Create with {@link createWebSocketRequests}. */
|
|
@@ -517,7 +566,26 @@ interface WebSocketRequests<Req, In> {
|
|
|
517
566
|
* queue (replaced by a newer request with the same `key`, or evicted by `queue.limit`).
|
|
518
567
|
*/
|
|
519
568
|
request(message: Req, options?: RequestOptions): Promise<In>;
|
|
520
|
-
/**
|
|
569
|
+
/**
|
|
570
|
+
* Send a request and iterate over its replies, up to and including the one `done` matches.
|
|
571
|
+
* Holds the connection until the stream ends. Needs the `done` option.
|
|
572
|
+
*
|
|
573
|
+
* `timeout` is the time to wait for each reply. Replies that arrive before the loop reads
|
|
574
|
+
* them are buffered. The stream is one-shot: iterate it once.
|
|
575
|
+
*
|
|
576
|
+
* Fails where `request()` rejects, with the same errors, after yielding the replies already
|
|
577
|
+
* received. Aborting `signal` throws `signal.reason` right away and discards them. Leaving
|
|
578
|
+
* the loop early, aborting and timing out send `cancel`.
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```ts
|
|
582
|
+
* for await (const reply of rpc.stream({ type: 'ask', prompt }, { signal })) {
|
|
583
|
+
* text += reply.delta;
|
|
584
|
+
* }
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
stream(message: Req, options?: RequestOptions): AsyncIterableIterator<In>;
|
|
588
|
+
/** Number of requests and streams waiting for a reply. */
|
|
521
589
|
pending(): number;
|
|
522
590
|
/** Reject every pending request and stop listening. Only needed for managers created dynamically. */
|
|
523
591
|
dispose(): void;
|
package/dist/core.js
CHANGED
|
@@ -30,8 +30,26 @@ const RECONNECT_DEFAULTS = {
|
|
|
30
30
|
minDelay: 1e3,
|
|
31
31
|
maxDelay: 3e4,
|
|
32
32
|
factor: 2,
|
|
33
|
-
jitter:
|
|
33
|
+
jitter: 1,
|
|
34
|
+
minUptime: 5e3
|
|
34
35
|
};
|
|
36
|
+
const QUEUE_LIMIT = 100;
|
|
37
|
+
/**
|
|
38
|
+
* The default `reconnect.shouldReconnect`: retry every close except `1002` (protocol error),
|
|
39
|
+
* `1003` (unsupported data) and `1008` (policy violation, often a rejected login), which
|
|
40
|
+
* retrying the same way can't fix. Call it from your own `shouldReconnect` to keep that rule.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* createWebSocket(url, {
|
|
45
|
+
* // Also stop on our server's "invalid token" code.
|
|
46
|
+
* reconnect: { shouldReconnect: close => close.code !== 4001 && retryableClose(close) }
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function retryableClose(close) {
|
|
51
|
+
return close.code !== 1002 && close.code !== 1003 && close.code !== 1008;
|
|
52
|
+
}
|
|
35
53
|
const READY_GRACE = 5e3;
|
|
36
54
|
function createDeferred() {
|
|
37
55
|
let resolve;
|
|
@@ -83,7 +101,7 @@ function createClient(url, options = {}) {
|
|
|
83
101
|
...RECONNECT_DEFAULTS,
|
|
84
102
|
...typeof options.reconnect === "object" ? options.reconnect : {}
|
|
85
103
|
};
|
|
86
|
-
const queueLimit = options.queue === false ? 0 : typeof options.queue === "object" ?
|
|
104
|
+
const queueLimit = options.queue === false ? 0 : Math.max(0, (typeof options.queue === "object" ? options.queue.limit : void 0) ?? QUEUE_LIMIT);
|
|
87
105
|
let snapshot = {
|
|
88
106
|
status: "idle",
|
|
89
107
|
lastMessage: void 0,
|
|
@@ -108,8 +126,10 @@ function createClient(url, options = {}) {
|
|
|
108
126
|
let inbox = null;
|
|
109
127
|
let generation = 0;
|
|
110
128
|
let windowBound = false;
|
|
129
|
+
let failures = 0;
|
|
111
130
|
let retryTimer;
|
|
112
131
|
let teardownTimer;
|
|
132
|
+
let stableTimer;
|
|
113
133
|
let heartbeatTimer;
|
|
114
134
|
let pongTimer;
|
|
115
135
|
function set(patch) {
|
|
@@ -176,10 +196,12 @@ function createClient(url, options = {}) {
|
|
|
176
196
|
}, heartbeat.timeout);
|
|
177
197
|
}, heartbeat.interval);
|
|
178
198
|
}
|
|
179
|
-
|
|
199
|
+
/** Stop the timers that belong to the current open socket. */
|
|
200
|
+
function stopSocketTimers() {
|
|
180
201
|
clearInterval(heartbeatTimer);
|
|
181
202
|
clearTimeout(pongTimer);
|
|
182
|
-
|
|
203
|
+
clearTimeout(stableTimer);
|
|
204
|
+
heartbeatTimer = pongTimer = stableTimer = void 0;
|
|
183
205
|
}
|
|
184
206
|
/** Stop tracking `socket`. Its eventual close event is still reported unless `reportClose` is false. */
|
|
185
207
|
function detach(socket, reportClose) {
|
|
@@ -250,6 +272,7 @@ function createClient(url, options = {}) {
|
|
|
250
272
|
status: "open",
|
|
251
273
|
attempt: 0
|
|
252
274
|
});
|
|
275
|
+
if (retry) stableTimer = setTimeout(() => failures = 0, retry.minUptime);
|
|
253
276
|
startHeartbeat(socket);
|
|
254
277
|
ready?.resolve();
|
|
255
278
|
if (refs === 0 && !pinned) {
|
|
@@ -274,14 +297,14 @@ function createClient(url, options = {}) {
|
|
|
274
297
|
};
|
|
275
298
|
}
|
|
276
299
|
function handleClose(info) {
|
|
277
|
-
|
|
300
|
+
stopSocketTimers();
|
|
278
301
|
log("closed", info);
|
|
279
302
|
retryOrFail(info);
|
|
280
303
|
emit("close", info);
|
|
281
304
|
}
|
|
282
305
|
function retryOrFail(info, error) {
|
|
283
|
-
if (wanted && retry &&
|
|
284
|
-
const attempt =
|
|
306
|
+
if (wanted && retry && failures < retry.maxAttempts && (retry.shouldReconnect ?? retryableClose)(info)) {
|
|
307
|
+
const attempt = ++failures;
|
|
285
308
|
const delay = backoff(attempt);
|
|
286
309
|
set({
|
|
287
310
|
status: "reconnecting",
|
|
@@ -296,7 +319,10 @@ function createClient(url, options = {}) {
|
|
|
296
319
|
opened = false;
|
|
297
320
|
unbindWindow();
|
|
298
321
|
pending = [];
|
|
299
|
-
set({
|
|
322
|
+
set({
|
|
323
|
+
status: "closed",
|
|
324
|
+
attempt: failures
|
|
325
|
+
});
|
|
300
326
|
rejectReady(reason);
|
|
301
327
|
}
|
|
302
328
|
function receive(event, socket) {
|
|
@@ -401,6 +427,7 @@ function createClient(url, options = {}) {
|
|
|
401
427
|
opened = false;
|
|
402
428
|
if (ready?.settled) ready = null;
|
|
403
429
|
}
|
|
430
|
+
failures = 0;
|
|
404
431
|
snapshot = {
|
|
405
432
|
...snapshot,
|
|
406
433
|
attempt: 0
|
|
@@ -422,7 +449,8 @@ function createClient(url, options = {}) {
|
|
|
422
449
|
generation++;
|
|
423
450
|
clearTimeout(retryTimer);
|
|
424
451
|
retryTimer = void 0;
|
|
425
|
-
|
|
452
|
+
failures = 0;
|
|
453
|
+
stopSocketTimers();
|
|
426
454
|
unbindWindow();
|
|
427
455
|
pending = [];
|
|
428
456
|
if (ws) {
|
|
@@ -445,7 +473,7 @@ function createClient(url, options = {}) {
|
|
|
445
473
|
if (!getImpl()) return;
|
|
446
474
|
clearTimeout(retryTimer);
|
|
447
475
|
retryTimer = void 0;
|
|
448
|
-
|
|
476
|
+
stopSocketTimers();
|
|
449
477
|
if (ws) {
|
|
450
478
|
const socket = ws;
|
|
451
479
|
detach(socket, true);
|
|
@@ -659,6 +687,8 @@ function createWebSocketLatency(client, options) {
|
|
|
659
687
|
//#endregion
|
|
660
688
|
//#region src/requests.ts
|
|
661
689
|
const lastIds = /* @__PURE__ */ new WeakMap();
|
|
690
|
+
const VOLATILE$2 = { volatile: true };
|
|
691
|
+
const noop = () => {};
|
|
662
692
|
/**
|
|
663
693
|
* Send requests and await their replies, matched by an id the library generates.
|
|
664
694
|
*
|
|
@@ -679,8 +709,8 @@ const lastIds = /* @__PURE__ */ new WeakMap();
|
|
|
679
709
|
*/
|
|
680
710
|
function createWebSocketRequests(client, options) {
|
|
681
711
|
const waiting = /* @__PURE__ */ new Map();
|
|
682
|
-
function
|
|
683
|
-
for (const entry of [...waiting.values()]) if (test(entry)) entry.
|
|
712
|
+
function endWhere(test, reason, cancel) {
|
|
713
|
+
for (const entry of [...waiting.values()]) if (test(entry)) entry.end(true, reason(), cancel);
|
|
684
714
|
}
|
|
685
715
|
const offs = [
|
|
686
716
|
client.on("message", (message) => {
|
|
@@ -688,78 +718,131 @@ function createWebSocketRequests(client, options) {
|
|
|
688
718
|
const entry = id == null ? void 0 : waiting.get(String(id));
|
|
689
719
|
if (!entry) return;
|
|
690
720
|
const error = options.error?.(message);
|
|
691
|
-
if (error === void 0) entry.
|
|
692
|
-
else entry.
|
|
721
|
+
if (error === void 0) entry.receive(message);
|
|
722
|
+
else entry.end(true, error);
|
|
693
723
|
}),
|
|
694
724
|
client.on("open", () => {
|
|
695
725
|
for (const entry of waiting.values()) entry.socket ??= client.socket;
|
|
696
726
|
}),
|
|
697
727
|
client.subscribe(() => {
|
|
698
728
|
const { status } = client.getSnapshot();
|
|
699
|
-
if (status === "closed" || status === "idle")
|
|
700
|
-
else
|
|
729
|
+
if (status === "closed" || status === "idle") endWhere(() => true, () => /* @__PURE__ */ new Error("WebSocket was closed before the request was answered"));
|
|
730
|
+
else endWhere((entry) => entry.socket !== null && entry.socket !== client.socket, () => /* @__PURE__ */ new Error("WebSocket connection was lost before the request was answered"));
|
|
701
731
|
})
|
|
702
732
|
];
|
|
733
|
+
/**
|
|
734
|
+
* Send `message` as a new request. `next` gets each reply that isn't an error and returns
|
|
735
|
+
* whether it was the last; `done` runs once, when the request ends. Returns a function that
|
|
736
|
+
* ends it from this side and tells the server.
|
|
737
|
+
*/
|
|
738
|
+
function start(message, requestOptions, next, done) {
|
|
739
|
+
const { signal, priority } = requestOptions;
|
|
740
|
+
if (signal?.aborted) {
|
|
741
|
+
done(true, signal.reason);
|
|
742
|
+
return noop;
|
|
743
|
+
}
|
|
744
|
+
const id = (lastIds.get(client) ?? 0) + 1;
|
|
745
|
+
lastIds.set(client, id);
|
|
746
|
+
const key = String(id);
|
|
747
|
+
const timeout = requestOptions.timeout ?? options.timeout ?? 1e4;
|
|
748
|
+
const release = client.acquire();
|
|
749
|
+
let timer;
|
|
750
|
+
const onAbort = () => entry.end(true, signal.reason, true);
|
|
751
|
+
function arm() {
|
|
752
|
+
clearTimeout(timer);
|
|
753
|
+
if (timeout > 0 && timeout < Infinity) timer = setTimeout(() => entry.end(true, new DOMException(`Request ${id} timed out after ${timeout}ms`, "TimeoutError"), true), timeout);
|
|
754
|
+
}
|
|
755
|
+
const entry = {
|
|
756
|
+
socket: null,
|
|
757
|
+
receive(reply) {
|
|
758
|
+
if (next(reply)) entry.end(false);
|
|
759
|
+
else arm();
|
|
760
|
+
},
|
|
761
|
+
end(failed, reason, cancel) {
|
|
762
|
+
if (!waiting.delete(key)) return;
|
|
763
|
+
clearTimeout(timer);
|
|
764
|
+
signal?.removeEventListener("abort", onAbort);
|
|
765
|
+
release();
|
|
766
|
+
done(failed, reason);
|
|
767
|
+
if (cancel && options.cancel) try {
|
|
768
|
+
client.send(options.cancel(id), entry.socket ? VOLATILE$2 : { priority });
|
|
769
|
+
} catch (error) {
|
|
770
|
+
queueMicrotask(() => {
|
|
771
|
+
throw error;
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
waiting.set(key, entry);
|
|
777
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
778
|
+
arm();
|
|
779
|
+
const { status } = client.getSnapshot();
|
|
780
|
+
if (status === "closed" || status === "idle") entry.end(true, /* @__PURE__ */ new Error(`WebSocket is ${status}: request not sent`));
|
|
781
|
+
else try {
|
|
782
|
+
if (client.send(options.send(message, id), {
|
|
783
|
+
...requestOptions,
|
|
784
|
+
onDrop() {
|
|
785
|
+
entry.end(true, /* @__PURE__ */ new Error("Request was dropped from the send queue before it was sent"));
|
|
786
|
+
requestOptions.onDrop?.();
|
|
787
|
+
}
|
|
788
|
+
})) entry.socket = client.socket;
|
|
789
|
+
else if (client.socket?.readyState === 1) entry.end(true, /* @__PURE__ */ new Error("WebSocket send buffer is full: request dropped"));
|
|
790
|
+
else if (requestOptions.volatile) entry.end(true, /* @__PURE__ */ new Error("WebSocket is not open: request dropped"));
|
|
791
|
+
} catch (error) {
|
|
792
|
+
entry.end(true, error);
|
|
793
|
+
}
|
|
794
|
+
return () => entry.end(false, void 0, true);
|
|
795
|
+
}
|
|
703
796
|
return {
|
|
704
797
|
client,
|
|
705
798
|
request(message, requestOptions = {}) {
|
|
706
|
-
const { signal } = requestOptions;
|
|
707
|
-
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
708
|
-
const id = (lastIds.get(client) ?? 0) + 1;
|
|
709
|
-
lastIds.set(client, id);
|
|
710
|
-
const key = String(id);
|
|
711
|
-
const timeout = requestOptions.timeout ?? options.timeout ?? 1e4;
|
|
712
799
|
return new Promise((resolve, reject) => {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (status === "closed" || status === "idle") {
|
|
738
|
-
entry.reject(/* @__PURE__ */ new Error(`WebSocket is ${status}: request not sent`));
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
let sent;
|
|
800
|
+
start(message, requestOptions, (reply) => {
|
|
801
|
+
resolve(reply);
|
|
802
|
+
return true;
|
|
803
|
+
}, (failed, reason) => {
|
|
804
|
+
if (failed) reject(reason);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
},
|
|
808
|
+
stream(message, requestOptions = {}) {
|
|
809
|
+
const last = options.done;
|
|
810
|
+
if (!last) throw new TypeError("stream() needs the `done` option");
|
|
811
|
+
const replies = [];
|
|
812
|
+
let ended;
|
|
813
|
+
let wake = noop;
|
|
814
|
+
const stop = start(message, requestOptions, (reply) => {
|
|
815
|
+
replies.push(reply);
|
|
816
|
+
wake();
|
|
817
|
+
return last(reply);
|
|
818
|
+
}, (failed, reason) => {
|
|
819
|
+
if (requestOptions.signal?.aborted) replies.length = 0;
|
|
820
|
+
ended = [failed, reason];
|
|
821
|
+
wake();
|
|
822
|
+
});
|
|
823
|
+
async function* read() {
|
|
742
824
|
try {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
} catch (error) {
|
|
751
|
-
entry.reject(error);
|
|
752
|
-
return;
|
|
825
|
+
for (;;) if (replies.length) yield replies.shift();
|
|
826
|
+
else if (ended) {
|
|
827
|
+
if (ended[0]) throw ended[1];
|
|
828
|
+
return;
|
|
829
|
+
} else await new Promise((resolve) => wake = resolve);
|
|
830
|
+
} finally {
|
|
831
|
+
stop();
|
|
753
832
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
833
|
+
}
|
|
834
|
+
const iterator = read();
|
|
835
|
+
const close = iterator.return;
|
|
836
|
+
iterator.return = (value) => {
|
|
837
|
+
stop();
|
|
838
|
+
return close.call(iterator, value);
|
|
839
|
+
};
|
|
840
|
+
return iterator;
|
|
758
841
|
},
|
|
759
842
|
pending: () => waiting.size,
|
|
760
843
|
dispose() {
|
|
761
844
|
offs.forEach((off) => off());
|
|
762
|
-
|
|
845
|
+
endWhere(() => true, () => /* @__PURE__ */ new Error("Requests were disposed"), true);
|
|
763
846
|
}
|
|
764
847
|
};
|
|
765
848
|
}
|
|
@@ -1168,6 +1251,6 @@ function createWebSocketTopics(client, options) {
|
|
|
1168
1251
|
};
|
|
1169
1252
|
}
|
|
1170
1253
|
//#endregion
|
|
1171
|
-
export { MessageError, createWebSocket, createWebSocketLatency, createWebSocketRequests, createWebSocketResponder, createWebSocketSendRate, createWebSocketState, createWebSocketTopics };
|
|
1254
|
+
export { MessageError, createWebSocket, createWebSocketLatency, createWebSocketRequests, createWebSocketResponder, createWebSocketSendRate, createWebSocketState, createWebSocketTopics, retryableClose };
|
|
1172
1255
|
|
|
1173
1256
|
//# sourceMappingURL=core.js.map
|