react-realtime-hooks 1.3.4 → 1.4.1

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/index.d.cts CHANGED
@@ -168,11 +168,39 @@ interface UseWebSocketHeartbeatOptions<TOutgoing = unknown, TIncoming = TOutgoin
168
168
  timeoutAction?: WebSocketHeartbeatAction;
169
169
  errorAction?: WebSocketHeartbeatAction;
170
170
  }
171
+ /**
172
+ * Controls how the hook surfaces the live `WebSocket.bufferedAmount`
173
+ * value through `state.bufferedAmount`.
174
+ *
175
+ * The native `WebSocket` does not emit any event when buffered bytes
176
+ * drain to the network, so by default `state.bufferedAmount` is only
177
+ * refreshed when the consumer calls `send(...)`, when a message
178
+ * arrives, or when the socket transitions to `open`. For UIs that need
179
+ * to display real-time backpressure (e.g. an outbound queue meter,
180
+ * a "flushing..." indicator, or a flow-control gauge), enable polling.
181
+ *
182
+ * - `false` (default): no polling. `state.bufferedAmount` updates only
183
+ * on the natural lifecycle events listed above.
184
+ * - `true`: poll once every 100ms while the socket is `open`.
185
+ * - `"raf"`: poll on every animation frame while the socket is `open`.
186
+ * Recommended for visual gauges that are tied to a render loop.
187
+ * - `{ intervalMs: N }`: poll every `N` milliseconds. `N` must be a
188
+ * positive integer; values <= 0 disable polling.
189
+ *
190
+ * Polling is automatically suspended when the socket is not in the
191
+ * `open` state and resumed when it returns to `open`. The hook diffs
192
+ * the polled value against the last committed one, so identical
193
+ * readings do not trigger React re-renders.
194
+ */
195
+ type BufferedAmountPolling = false | true | "raf" | {
196
+ intervalMs: number;
197
+ };
171
198
  interface UseWebSocketOptions<TIncoming = unknown, TOutgoing = TIncoming> {
172
199
  url: UrlProvider;
173
200
  protocols?: string | string[];
174
201
  connect?: boolean;
175
202
  binaryType?: BinaryType;
203
+ bufferedAmountPolling?: BufferedAmountPolling;
176
204
  parseMessage?: MessageParser<TIncoming>;
177
205
  serializeMessage?: MessageSerializer<TOutgoing>;
178
206
  reconnect?: false | UseReconnectOptions;
@@ -274,4 +302,4 @@ declare class RealtimeErrorEvent extends Event {
274
302
  constructor(type: string, init: RealtimeErrorEventInit);
275
303
  }
276
304
 
277
- export { type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, RealtimeErrorEvent, type RealtimeErrorEventInit, type RealtimeErrorKind, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseConnectionGateHook, type UseConnectionGateOptions, type UseConnectionGateResult, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UsePageVisibilityHook, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
305
+ export { type BufferedAmountPolling, type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, RealtimeErrorEvent, type RealtimeErrorEventInit, type RealtimeErrorKind, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseConnectionGateHook, type UseConnectionGateOptions, type UseConnectionGateResult, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UsePageVisibilityHook, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
package/dist/index.d.ts CHANGED
@@ -168,11 +168,39 @@ interface UseWebSocketHeartbeatOptions<TOutgoing = unknown, TIncoming = TOutgoin
168
168
  timeoutAction?: WebSocketHeartbeatAction;
169
169
  errorAction?: WebSocketHeartbeatAction;
170
170
  }
171
+ /**
172
+ * Controls how the hook surfaces the live `WebSocket.bufferedAmount`
173
+ * value through `state.bufferedAmount`.
174
+ *
175
+ * The native `WebSocket` does not emit any event when buffered bytes
176
+ * drain to the network, so by default `state.bufferedAmount` is only
177
+ * refreshed when the consumer calls `send(...)`, when a message
178
+ * arrives, or when the socket transitions to `open`. For UIs that need
179
+ * to display real-time backpressure (e.g. an outbound queue meter,
180
+ * a "flushing..." indicator, or a flow-control gauge), enable polling.
181
+ *
182
+ * - `false` (default): no polling. `state.bufferedAmount` updates only
183
+ * on the natural lifecycle events listed above.
184
+ * - `true`: poll once every 100ms while the socket is `open`.
185
+ * - `"raf"`: poll on every animation frame while the socket is `open`.
186
+ * Recommended for visual gauges that are tied to a render loop.
187
+ * - `{ intervalMs: N }`: poll every `N` milliseconds. `N` must be a
188
+ * positive integer; values <= 0 disable polling.
189
+ *
190
+ * Polling is automatically suspended when the socket is not in the
191
+ * `open` state and resumed when it returns to `open`. The hook diffs
192
+ * the polled value against the last committed one, so identical
193
+ * readings do not trigger React re-renders.
194
+ */
195
+ type BufferedAmountPolling = false | true | "raf" | {
196
+ intervalMs: number;
197
+ };
171
198
  interface UseWebSocketOptions<TIncoming = unknown, TOutgoing = TIncoming> {
172
199
  url: UrlProvider;
173
200
  protocols?: string | string[];
174
201
  connect?: boolean;
175
202
  binaryType?: BinaryType;
203
+ bufferedAmountPolling?: BufferedAmountPolling;
176
204
  parseMessage?: MessageParser<TIncoming>;
177
205
  serializeMessage?: MessageSerializer<TOutgoing>;
178
206
  reconnect?: false | UseReconnectOptions;
@@ -274,4 +302,4 @@ declare class RealtimeErrorEvent extends Event {
274
302
  constructor(type: string, init: RealtimeErrorEventInit);
275
303
  }
276
304
 
277
- export { type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, RealtimeErrorEvent, type RealtimeErrorEventInit, type RealtimeErrorKind, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseConnectionGateHook, type UseConnectionGateOptions, type UseConnectionGateResult, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UsePageVisibilityHook, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
305
+ export { type BufferedAmountPolling, type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, RealtimeErrorEvent, type RealtimeErrorEventInit, type RealtimeErrorKind, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseConnectionGateHook, type UseConnectionGateOptions, type UseConnectionGateResult, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UsePageVisibilityHook, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
package/dist/index.js CHANGED
@@ -803,9 +803,95 @@ var resolveUrlProvider = (url) => {
803
803
  const resolved = typeof url === "function" ? url() : url;
804
804
  return normalizeResolvedUrl(resolved ?? null);
805
805
  };
806
+ var createInitialState3 = () => ({
807
+ manualClose: false,
808
+ manualOpen: false,
809
+ pendingCloseAction: null,
810
+ reconnectSuppressed: false,
811
+ skipNextCloseReconnect: false,
812
+ terminalError: null
813
+ });
814
+ var useWebSocketController = () => {
815
+ const ref = useRef(null);
816
+ if (ref.current === null) {
817
+ ref.current = createInitialState3();
818
+ }
819
+ const state = ref.current;
820
+ const controllerRef = useRef(null);
821
+ if (controllerRef.current === null) {
822
+ controllerRef.current = {
823
+ clearReconnectSuppression: () => {
824
+ state.reconnectSuppressed = false;
825
+ },
826
+ consumePendingCloseAction: () => {
827
+ const action = state.pendingCloseAction;
828
+ state.pendingCloseAction = null;
829
+ return action;
830
+ },
831
+ consumeSkipNextCloseReconnect: () => {
832
+ const skip = state.skipNextCloseReconnect;
833
+ state.skipNextCloseReconnect = false;
834
+ return skip;
835
+ },
836
+ hasManualCloseRequested: () => state.manualClose,
837
+ hasManualOpenRequested: () => state.manualOpen,
838
+ isReconnectSuppressed: () => state.reconnectSuppressed,
839
+ noteEffectInitiatedClose: () => {
840
+ state.reconnectSuppressed = true;
841
+ },
842
+ noteHeartbeatActiveSocketClose: (input) => {
843
+ state.manualOpen = false;
844
+ state.terminalError = input.reconnectTrigger === null ? input.error : null;
845
+ state.pendingCloseAction = input;
846
+ state.skipNextCloseReconnect = true;
847
+ state.reconnectSuppressed = true;
848
+ },
849
+ noteHeartbeatNoActiveSocket: ({ shouldReconnect, error }) => {
850
+ state.manualOpen = false;
851
+ state.terminalError = shouldReconnect ? null : error;
852
+ },
853
+ noteParseError: (error) => {
854
+ state.terminalError = error;
855
+ state.manualOpen = false;
856
+ state.skipNextCloseReconnect = true;
857
+ state.reconnectSuppressed = true;
858
+ },
859
+ noteSocketOpened: () => {
860
+ state.manualClose = false;
861
+ state.manualOpen = false;
862
+ state.reconnectSuppressed = false;
863
+ state.terminalError = null;
864
+ },
865
+ noteUserCloseRequested: () => {
866
+ state.manualClose = true;
867
+ state.manualOpen = false;
868
+ state.reconnectSuppressed = true;
869
+ state.terminalError = null;
870
+ },
871
+ noteUserOpenRequested: () => {
872
+ state.manualClose = false;
873
+ state.manualOpen = true;
874
+ state.reconnectSuppressed = false;
875
+ state.terminalError = null;
876
+ },
877
+ noteUserReconnectClosed: () => {
878
+ state.reconnectSuppressed = false;
879
+ },
880
+ noteUserReconnectRequested: () => {
881
+ state.manualClose = false;
882
+ state.manualOpen = true;
883
+ state.skipNextCloseReconnect = true;
884
+ state.reconnectSuppressed = true;
885
+ state.terminalError = null;
886
+ },
887
+ peekTerminalError: () => state.terminalError
888
+ };
889
+ }
890
+ return controllerRef.current;
891
+ };
806
892
 
807
893
  // src/hooks/useWebSocket.ts
808
- var createInitialState3 = (status = "idle") => ({
894
+ var createInitialState4 = (status = "idle") => ({
809
895
  bufferedAmount: 0,
810
896
  lastChangedAt: null,
811
897
  lastCloseEvent: null,
@@ -848,15 +934,10 @@ var useWebSocket = (options) => {
848
934
  const activeSocketEpochRef = useRef(null);
849
935
  const closingSocketEpochRef = useRef(null);
850
936
  const nextSocketEpochRef = useRef(0);
851
- const manualCloseRef = useRef(false);
852
- const manualOpenRef = useRef(false);
853
- const skipCloseReconnectRef = useRef(false);
854
- const suppressReconnectRef = useRef(false);
855
- const pendingCloseActionRef = useRef(null);
856
- const terminalErrorRef = useRef(null);
937
+ const controller = useWebSocketController();
857
938
  const [openNonce, setOpenNonce] = useState(0);
858
939
  const [state, setState] = useState(
859
- () => createInitialState3(connect ? "connecting" : "idle")
940
+ () => createInitialState4(connect ? "connecting" : "idle")
860
941
  );
861
942
  const stateRef = useRef(state);
862
943
  stateRef.current = state;
@@ -976,10 +1057,9 @@ var useWebSocket = (options) => {
976
1057
  return;
977
1058
  }
978
1059
  const shouldReconnect = action === "reconnect" && reconnectEnabled && (options.shouldReconnect?.(error) ?? true);
979
- manualOpenRef.current = false;
980
- terminalErrorRef.current = shouldReconnect ? null : error;
981
1060
  const socket2 = socketRef.current;
982
1061
  if (socket2 === null || !isSocketActive(socket2)) {
1062
+ controller.noteHeartbeatNoActiveSocket({ error, shouldReconnect });
983
1063
  commitState((current) => ({
984
1064
  ...current,
985
1065
  lastChangedAt: Date.now(),
@@ -991,12 +1071,10 @@ var useWebSocket = (options) => {
991
1071
  }
992
1072
  return;
993
1073
  }
994
- pendingCloseActionRef.current = {
1074
+ controller.noteHeartbeatActiveSocketClose({
995
1075
  error,
996
1076
  reconnectTrigger: shouldReconnect ? reconnectTrigger : null
997
- };
998
- skipCloseReconnectRef.current = true;
999
- suppressReconnectRef.current = true;
1077
+ });
1000
1078
  closeSocket({ trackClose: true });
1001
1079
  }
1002
1080
  );
@@ -1011,10 +1089,7 @@ var useWebSocket = (options) => {
1011
1089
  }));
1012
1090
  });
1013
1091
  const handleOpen = useStableCallback((event, socket2) => {
1014
- manualCloseRef.current = false;
1015
- manualOpenRef.current = false;
1016
- suppressReconnectRef.current = false;
1017
- terminalErrorRef.current = null;
1092
+ controller.noteSocketOpened();
1018
1093
  reconnect.markConnected();
1019
1094
  heartbeat.start();
1020
1095
  commitState((current) => ({
@@ -1041,10 +1116,7 @@ var useWebSocket = (options) => {
1041
1116
  cause: error,
1042
1117
  kind: "parse-error"
1043
1118
  });
1044
- terminalErrorRef.current = parseError;
1045
- manualOpenRef.current = false;
1046
- skipCloseReconnectRef.current = true;
1047
- suppressReconnectRef.current = true;
1119
+ controller.noteParseError(parseError);
1048
1120
  reconnect.cancel();
1049
1121
  heartbeat.stop();
1050
1122
  options.onError?.(parseError);
@@ -1088,13 +1160,11 @@ var useWebSocket = (options) => {
1088
1160
  }
1089
1161
  heartbeat.stop();
1090
1162
  updateBufferedAmount();
1091
- const pendingCloseAction = pendingCloseActionRef.current;
1092
- pendingCloseActionRef.current = null;
1093
- const terminalError = terminalErrorRef.current;
1094
- const skipCloseReconnect = skipCloseReconnectRef.current;
1095
- skipCloseReconnectRef.current = false;
1163
+ const pendingCloseAction = controller.consumePendingCloseAction();
1164
+ const terminalError = controller.peekTerminalError();
1165
+ const skipCloseReconnect = controller.consumeSkipNextCloseReconnect();
1096
1166
  if (pendingCloseAction !== null) {
1097
- suppressReconnectRef.current = false;
1167
+ controller.clearReconnectSuppression();
1098
1168
  commitState((current) => ({
1099
1169
  ...current,
1100
1170
  lastChangedAt: Date.now(),
@@ -1109,7 +1179,7 @@ var useWebSocket = (options) => {
1109
1179
  return;
1110
1180
  }
1111
1181
  if (terminalError !== null) {
1112
- suppressReconnectRef.current = false;
1182
+ controller.clearReconnectSuppression();
1113
1183
  commitState((current) => ({
1114
1184
  ...current,
1115
1185
  lastChangedAt: Date.now(),
@@ -1120,7 +1190,7 @@ var useWebSocket = (options) => {
1120
1190
  options.onClose?.(event);
1121
1191
  return;
1122
1192
  }
1123
- const shouldReconnect = !suppressReconnectRef.current && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
1193
+ const shouldReconnect = !controller.isReconnectSuppressed() && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
1124
1194
  commitState((current) => ({
1125
1195
  ...current,
1126
1196
  lastChangedAt: Date.now(),
@@ -1131,33 +1201,23 @@ var useWebSocket = (options) => {
1131
1201
  if (shouldReconnect) {
1132
1202
  reconnect.schedule("close");
1133
1203
  } else {
1134
- suppressReconnectRef.current = false;
1204
+ controller.clearReconnectSuppression();
1135
1205
  }
1136
1206
  });
1137
1207
  const open = useStableCallback(() => {
1138
- manualCloseRef.current = false;
1139
- manualOpenRef.current = true;
1140
- suppressReconnectRef.current = false;
1141
- terminalErrorRef.current = null;
1208
+ controller.noteUserOpenRequested();
1142
1209
  reconnect.cancel();
1143
1210
  setOpenNonce((current) => current + 1);
1144
1211
  });
1145
1212
  const reconnectNow = useStableCallback(() => {
1146
- manualCloseRef.current = false;
1147
- manualOpenRef.current = true;
1148
- skipCloseReconnectRef.current = true;
1149
- suppressReconnectRef.current = true;
1150
- terminalErrorRef.current = null;
1213
+ controller.noteUserReconnectRequested();
1151
1214
  heartbeat.stop();
1152
1215
  closeSocket();
1153
- suppressReconnectRef.current = false;
1216
+ controller.noteUserReconnectClosed();
1154
1217
  reconnect.schedule("manual");
1155
1218
  });
1156
1219
  const close = useStableCallback((code, reason) => {
1157
- manualCloseRef.current = true;
1158
- manualOpenRef.current = false;
1159
- suppressReconnectRef.current = true;
1160
- terminalErrorRef.current = null;
1220
+ controller.noteUserCloseRequested();
1161
1221
  reconnect.cancel();
1162
1222
  heartbeat.stop();
1163
1223
  commitState((current) => ({
@@ -1199,22 +1259,22 @@ var useWebSocket = (options) => {
1199
1259
  }));
1200
1260
  return;
1201
1261
  }
1202
- const shouldConnect = terminalErrorRef.current === null && (connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running");
1262
+ const shouldConnect = controller.peekTerminalError() === null && (connect && !controller.hasManualCloseRequested() || controller.hasManualOpenRequested() || reconnect.status === "running");
1203
1263
  const nextSocketKey = `${resolvedUrl}::${protocolsDependency}::${options.binaryType ?? "blob"}`;
1204
1264
  if (!shouldConnect) {
1205
1265
  if (socketRef.current !== null) {
1206
- suppressReconnectRef.current = true;
1266
+ controller.noteEffectInitiatedClose();
1207
1267
  closeSocket();
1208
1268
  }
1209
1269
  socketKeyRef.current = null;
1210
1270
  commitState((current) => ({
1211
1271
  ...current,
1212
- status: terminalErrorRef.current !== null ? "error" : manualCloseRef.current ? "closed" : "idle"
1272
+ status: controller.peekTerminalError() !== null ? "error" : controller.hasManualCloseRequested() ? "closed" : "idle"
1213
1273
  }));
1214
1274
  return;
1215
1275
  }
1216
1276
  if (socketRef.current !== null && socketKeyRef.current !== nextSocketKey) {
1217
- suppressReconnectRef.current = true;
1277
+ controller.noteEffectInitiatedClose();
1218
1278
  closeSocket();
1219
1279
  }
1220
1280
  if (socketRef.current !== null) {
@@ -1281,6 +1341,7 @@ var useWebSocket = (options) => {
1281
1341
  closeSocket,
1282
1342
  commitState,
1283
1343
  connect,
1344
+ controller,
1284
1345
  handleClose,
1285
1346
  handleError,
1286
1347
  handleMessage,
@@ -1295,11 +1356,10 @@ var useWebSocket = (options) => {
1295
1356
  supported
1296
1357
  ]);
1297
1358
  useEffect(() => () => {
1298
- suppressReconnectRef.current = true;
1359
+ controller.noteEffectInitiatedClose();
1299
1360
  socketKeyRef.current = null;
1300
1361
  activeSocketEpochRef.current = null;
1301
1362
  closingSocketEpochRef.current = null;
1302
- terminalErrorRef.current = null;
1303
1363
  const socket2 = socketRef.current;
1304
1364
  socketRef.current = null;
1305
1365
  if (socket2 === null) {
@@ -1308,13 +1368,65 @@ var useWebSocket = (options) => {
1308
1368
  if (isSocketActive(socket2)) {
1309
1369
  socket2.close();
1310
1370
  }
1311
- }, []);
1371
+ }, [controller]);
1312
1372
  const stopHeartbeat = heartbeat.stop;
1313
1373
  useEffect(() => {
1314
1374
  if (state.status !== "open") {
1315
1375
  stopHeartbeat();
1316
1376
  }
1317
1377
  }, [state.status, stopHeartbeat]);
1378
+ const rawBufferedAmountPolling = options.bufferedAmountPolling;
1379
+ const bufferedAmountPollingMode = useMemo(() => {
1380
+ if (rawBufferedAmountPolling === void 0 || rawBufferedAmountPolling === false) {
1381
+ return null;
1382
+ }
1383
+ if (rawBufferedAmountPolling === true) {
1384
+ return 100;
1385
+ }
1386
+ if (rawBufferedAmountPolling === "raf") {
1387
+ return "raf";
1388
+ }
1389
+ const intervalMs = rawBufferedAmountPolling.intervalMs;
1390
+ return Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : null;
1391
+ }, [rawBufferedAmountPolling]);
1392
+ const pollBufferedAmount = useStableCallback(() => {
1393
+ const socket2 = socketRef.current;
1394
+ if (socket2 === null) {
1395
+ return;
1396
+ }
1397
+ const next = socket2.bufferedAmount;
1398
+ if (stateRef.current.bufferedAmount === next) {
1399
+ return;
1400
+ }
1401
+ commitState((current) => ({
1402
+ ...current,
1403
+ bufferedAmount: next
1404
+ }));
1405
+ });
1406
+ useEffect(() => {
1407
+ if (bufferedAmountPollingMode === null) {
1408
+ return;
1409
+ }
1410
+ if (state.status !== "open") {
1411
+ return;
1412
+ }
1413
+ if (bufferedAmountPollingMode === "raf") {
1414
+ if (typeof requestAnimationFrame !== "function") {
1415
+ return;
1416
+ }
1417
+ let frame = requestAnimationFrame(function loop() {
1418
+ pollBufferedAmount();
1419
+ frame = requestAnimationFrame(loop);
1420
+ });
1421
+ return () => {
1422
+ cancelAnimationFrame(frame);
1423
+ };
1424
+ }
1425
+ const intervalId = setInterval(pollBufferedAmount, bufferedAmountPollingMode);
1426
+ return () => {
1427
+ clearInterval(intervalId);
1428
+ };
1429
+ }, [bufferedAmountPollingMode, state.status, pollBufferedAmount]);
1318
1430
  const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
1319
1431
  const snapshot = createConnectionStateSnapshot(status, {
1320
1432
  isSupported: supported,
@@ -1361,7 +1473,7 @@ var useWebSocket = (options) => {
1361
1473
  socket
1362
1474
  };
1363
1475
  };
1364
- var createInitialState4 = (status = "idle") => ({
1476
+ var createInitialState5 = (status = "idle") => ({
1365
1477
  lastChangedAt: null,
1366
1478
  lastError: null,
1367
1479
  lastEventName: null,
@@ -1398,7 +1510,7 @@ var useEventSource = (options) => {
1398
1510
  const terminalErrorRef = useRef(null);
1399
1511
  const [openNonce, setOpenNonce] = useState(0);
1400
1512
  const [state, setState] = useState(
1401
- () => createInitialState4(connect ? "connecting" : "idle")
1513
+ () => createInitialState5(connect ? "connecting" : "idle")
1402
1514
  );
1403
1515
  const stateRef = useRef(state);
1404
1516
  stateRef.current = state;