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/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
- /** Consecutive failed attempts since the last successful open. `0` while open. */
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
- * @default 0.5
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
- * // Don't retry when the server rejects our credentials.
81
- * shouldReconnect: close => close.code !== 4001
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 () => true
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
- * @default Infinity
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
- * `false` drops them instead. `{ limit }` caps the buffer, dropping the oldest.
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
- /** Consecutive failed attempts since the last successful open. `0` while open. */
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
- * @default 0.5
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
- * // Don't retry when the server rejects our credentials.
81
- * shouldReconnect: close => close.code !== 4001
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 () => true
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
- * @default Infinity
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
- * `false` drops them instead. `{ limit }` caps the buffer, dropping the oldest.
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: .5
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" ? Math.max(0, options.queue.limit ?? Infinity) : Infinity;
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
- function stopHeartbeat() {
199
+ /** Stop the timers that belong to the current open socket. */
200
+ function stopSocketTimers() {
180
201
  clearInterval(heartbeatTimer);
181
202
  clearTimeout(pongTimer);
182
- heartbeatTimer = pongTimer = void 0;
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
- stopHeartbeat();
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 && snapshot.attempt < retry.maxAttempts && (retry.shouldReconnect?.(info) ?? true)) {
284
- const attempt = snapshot.attempt + 1;
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({ status: "closed" });
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
- stopHeartbeat();
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
- stopHeartbeat();
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