react-websocket 3.6.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 +9 -4
- package/dist/core.cjs +39 -10
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +42 -9
- package/dist/core.d.ts +42 -9
- package/dist/core.js +39 -11
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +39 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -10
- package/dist/index.d.ts +30 -10
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/dist/timeline.d.cts +4 -1
- package/package.json +2 -2
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
|
*
|
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
|
*
|
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);
|
|
@@ -1223,6 +1251,6 @@ function createWebSocketTopics(client, options) {
|
|
|
1223
1251
|
};
|
|
1224
1252
|
}
|
|
1225
1253
|
//#endregion
|
|
1226
|
-
export { MessageError, createWebSocket, createWebSocketLatency, createWebSocketRequests, createWebSocketResponder, createWebSocketSendRate, createWebSocketState, createWebSocketTopics };
|
|
1254
|
+
export { MessageError, createWebSocket, createWebSocketLatency, createWebSocketRequests, createWebSocketResponder, createWebSocketSendRate, createWebSocketState, createWebSocketTopics, retryableClose };
|
|
1227
1255
|
|
|
1228
1256
|
//# sourceMappingURL=core.js.map
|