react-realtime-hooks 1.4.0 → 1.4.2

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSyncExternalStore, useRef, useState, useEffect, useMemo, useInsertionEffect, useCallback } from 'react';
1
+ import { useSyncExternalStore, useRef, useState, useEffect, useInsertionEffect, useMemo, useCallback } from 'react';
2
2
 
3
3
  // src/hooks/useOnlineStatus.ts
4
4
 
@@ -464,7 +464,9 @@ var useReconnect = (options = {}) => {
464
464
  () => createInitialState(normalizedOptions.enabled)
465
465
  );
466
466
  const stateRef = useRef(state);
467
- stateRef.current = state;
467
+ useInsertionEffect(() => {
468
+ stateRef.current = state;
469
+ });
468
470
  const commitState = (next) => {
469
471
  const resolved = typeof next === "function" ? next(stateRef.current) : next;
470
472
  stateRef.current = resolved;
@@ -597,7 +599,9 @@ var useHeartbeat = (options) => {
597
599
  () => createInitialState2(enabled && startOnMount)
598
600
  );
599
601
  const stateRef = useRef(state);
600
- stateRef.current = state;
602
+ useInsertionEffect(() => {
603
+ stateRef.current = state;
604
+ });
601
605
  const commitState = (next) => {
602
606
  const resolved = typeof next === "function" ? next(stateRef.current) : next;
603
607
  stateRef.current = resolved;
@@ -803,9 +807,95 @@ var resolveUrlProvider = (url) => {
803
807
  const resolved = typeof url === "function" ? url() : url;
804
808
  return normalizeResolvedUrl(resolved ?? null);
805
809
  };
810
+ var createInitialState3 = () => ({
811
+ manualClose: false,
812
+ manualOpen: false,
813
+ pendingCloseAction: null,
814
+ reconnectSuppressed: false,
815
+ skipNextCloseReconnect: false,
816
+ terminalError: null
817
+ });
818
+ var useWebSocketController = () => {
819
+ const ref = useRef(null);
820
+ if (ref.current === null) {
821
+ ref.current = createInitialState3();
822
+ }
823
+ const state = ref.current;
824
+ const controllerRef = useRef(null);
825
+ if (controllerRef.current === null) {
826
+ controllerRef.current = {
827
+ clearReconnectSuppression: () => {
828
+ state.reconnectSuppressed = false;
829
+ },
830
+ consumePendingCloseAction: () => {
831
+ const action = state.pendingCloseAction;
832
+ state.pendingCloseAction = null;
833
+ return action;
834
+ },
835
+ consumeSkipNextCloseReconnect: () => {
836
+ const skip = state.skipNextCloseReconnect;
837
+ state.skipNextCloseReconnect = false;
838
+ return skip;
839
+ },
840
+ hasManualCloseRequested: () => state.manualClose,
841
+ hasManualOpenRequested: () => state.manualOpen,
842
+ isReconnectSuppressed: () => state.reconnectSuppressed,
843
+ noteEffectInitiatedClose: () => {
844
+ state.reconnectSuppressed = true;
845
+ },
846
+ noteHeartbeatActiveSocketClose: (input) => {
847
+ state.manualOpen = false;
848
+ state.terminalError = input.reconnectTrigger === null ? input.error : null;
849
+ state.pendingCloseAction = input;
850
+ state.skipNextCloseReconnect = true;
851
+ state.reconnectSuppressed = true;
852
+ },
853
+ noteHeartbeatNoActiveSocket: ({ shouldReconnect, error }) => {
854
+ state.manualOpen = false;
855
+ state.terminalError = shouldReconnect ? null : error;
856
+ },
857
+ noteParseError: (error) => {
858
+ state.terminalError = error;
859
+ state.manualOpen = false;
860
+ state.skipNextCloseReconnect = true;
861
+ state.reconnectSuppressed = true;
862
+ },
863
+ noteSocketOpened: () => {
864
+ state.manualClose = false;
865
+ state.manualOpen = false;
866
+ state.reconnectSuppressed = false;
867
+ state.terminalError = null;
868
+ },
869
+ noteUserCloseRequested: () => {
870
+ state.manualClose = true;
871
+ state.manualOpen = false;
872
+ state.reconnectSuppressed = true;
873
+ state.terminalError = null;
874
+ },
875
+ noteUserOpenRequested: () => {
876
+ state.manualClose = false;
877
+ state.manualOpen = true;
878
+ state.reconnectSuppressed = false;
879
+ state.terminalError = null;
880
+ },
881
+ noteUserReconnectClosed: () => {
882
+ state.reconnectSuppressed = false;
883
+ },
884
+ noteUserReconnectRequested: () => {
885
+ state.manualClose = false;
886
+ state.manualOpen = true;
887
+ state.skipNextCloseReconnect = true;
888
+ state.reconnectSuppressed = true;
889
+ state.terminalError = null;
890
+ },
891
+ peekTerminalError: () => state.terminalError
892
+ };
893
+ }
894
+ return controllerRef.current;
895
+ };
806
896
 
807
897
  // src/hooks/useWebSocket.ts
808
- var createInitialState3 = (status = "idle") => ({
898
+ var createInitialState4 = (status = "idle") => ({
809
899
  bufferedAmount: 0,
810
900
  lastChangedAt: null,
811
901
  lastCloseEvent: null,
@@ -848,18 +938,15 @@ var useWebSocket = (options) => {
848
938
  const activeSocketEpochRef = useRef(null);
849
939
  const closingSocketEpochRef = useRef(null);
850
940
  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);
941
+ const controller = useWebSocketController();
857
942
  const [openNonce, setOpenNonce] = useState(0);
858
943
  const [state, setState] = useState(
859
- () => createInitialState3(connect ? "connecting" : "idle")
944
+ () => createInitialState4(connect ? "connecting" : "idle")
860
945
  );
861
946
  const stateRef = useRef(state);
862
- stateRef.current = state;
947
+ useInsertionEffect(() => {
948
+ stateRef.current = state;
949
+ });
863
950
  const reconnectEnabled = options.reconnect !== false && supported && resolvedUrl !== null;
864
951
  const reconnect = useReconnect(
865
952
  options.reconnect === false ? { enabled: false } : {
@@ -976,10 +1063,9 @@ var useWebSocket = (options) => {
976
1063
  return;
977
1064
  }
978
1065
  const shouldReconnect = action === "reconnect" && reconnectEnabled && (options.shouldReconnect?.(error) ?? true);
979
- manualOpenRef.current = false;
980
- terminalErrorRef.current = shouldReconnect ? null : error;
981
1066
  const socket2 = socketRef.current;
982
1067
  if (socket2 === null || !isSocketActive(socket2)) {
1068
+ controller.noteHeartbeatNoActiveSocket({ error, shouldReconnect });
983
1069
  commitState((current) => ({
984
1070
  ...current,
985
1071
  lastChangedAt: Date.now(),
@@ -991,12 +1077,10 @@ var useWebSocket = (options) => {
991
1077
  }
992
1078
  return;
993
1079
  }
994
- pendingCloseActionRef.current = {
1080
+ controller.noteHeartbeatActiveSocketClose({
995
1081
  error,
996
1082
  reconnectTrigger: shouldReconnect ? reconnectTrigger : null
997
- };
998
- skipCloseReconnectRef.current = true;
999
- suppressReconnectRef.current = true;
1083
+ });
1000
1084
  closeSocket({ trackClose: true });
1001
1085
  }
1002
1086
  );
@@ -1011,10 +1095,7 @@ var useWebSocket = (options) => {
1011
1095
  }));
1012
1096
  });
1013
1097
  const handleOpen = useStableCallback((event, socket2) => {
1014
- manualCloseRef.current = false;
1015
- manualOpenRef.current = false;
1016
- suppressReconnectRef.current = false;
1017
- terminalErrorRef.current = null;
1098
+ controller.noteSocketOpened();
1018
1099
  reconnect.markConnected();
1019
1100
  heartbeat.start();
1020
1101
  commitState((current) => ({
@@ -1041,10 +1122,7 @@ var useWebSocket = (options) => {
1041
1122
  cause: error,
1042
1123
  kind: "parse-error"
1043
1124
  });
1044
- terminalErrorRef.current = parseError;
1045
- manualOpenRef.current = false;
1046
- skipCloseReconnectRef.current = true;
1047
- suppressReconnectRef.current = true;
1125
+ controller.noteParseError(parseError);
1048
1126
  reconnect.cancel();
1049
1127
  heartbeat.stop();
1050
1128
  options.onError?.(parseError);
@@ -1088,13 +1166,11 @@ var useWebSocket = (options) => {
1088
1166
  }
1089
1167
  heartbeat.stop();
1090
1168
  updateBufferedAmount();
1091
- const pendingCloseAction = pendingCloseActionRef.current;
1092
- pendingCloseActionRef.current = null;
1093
- const terminalError = terminalErrorRef.current;
1094
- const skipCloseReconnect = skipCloseReconnectRef.current;
1095
- skipCloseReconnectRef.current = false;
1169
+ const pendingCloseAction = controller.consumePendingCloseAction();
1170
+ const terminalError = controller.peekTerminalError();
1171
+ const skipCloseReconnect = controller.consumeSkipNextCloseReconnect();
1096
1172
  if (pendingCloseAction !== null) {
1097
- suppressReconnectRef.current = false;
1173
+ controller.clearReconnectSuppression();
1098
1174
  commitState((current) => ({
1099
1175
  ...current,
1100
1176
  lastChangedAt: Date.now(),
@@ -1109,7 +1185,7 @@ var useWebSocket = (options) => {
1109
1185
  return;
1110
1186
  }
1111
1187
  if (terminalError !== null) {
1112
- suppressReconnectRef.current = false;
1188
+ controller.clearReconnectSuppression();
1113
1189
  commitState((current) => ({
1114
1190
  ...current,
1115
1191
  lastChangedAt: Date.now(),
@@ -1120,7 +1196,7 @@ var useWebSocket = (options) => {
1120
1196
  options.onClose?.(event);
1121
1197
  return;
1122
1198
  }
1123
- const shouldReconnect = !suppressReconnectRef.current && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
1199
+ const shouldReconnect = !controller.isReconnectSuppressed() && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
1124
1200
  commitState((current) => ({
1125
1201
  ...current,
1126
1202
  lastChangedAt: Date.now(),
@@ -1131,33 +1207,23 @@ var useWebSocket = (options) => {
1131
1207
  if (shouldReconnect) {
1132
1208
  reconnect.schedule("close");
1133
1209
  } else {
1134
- suppressReconnectRef.current = false;
1210
+ controller.clearReconnectSuppression();
1135
1211
  }
1136
1212
  });
1137
1213
  const open = useStableCallback(() => {
1138
- manualCloseRef.current = false;
1139
- manualOpenRef.current = true;
1140
- suppressReconnectRef.current = false;
1141
- terminalErrorRef.current = null;
1214
+ controller.noteUserOpenRequested();
1142
1215
  reconnect.cancel();
1143
1216
  setOpenNonce((current) => current + 1);
1144
1217
  });
1145
1218
  const reconnectNow = useStableCallback(() => {
1146
- manualCloseRef.current = false;
1147
- manualOpenRef.current = true;
1148
- skipCloseReconnectRef.current = true;
1149
- suppressReconnectRef.current = true;
1150
- terminalErrorRef.current = null;
1219
+ controller.noteUserReconnectRequested();
1151
1220
  heartbeat.stop();
1152
1221
  closeSocket();
1153
- suppressReconnectRef.current = false;
1222
+ controller.noteUserReconnectClosed();
1154
1223
  reconnect.schedule("manual");
1155
1224
  });
1156
1225
  const close = useStableCallback((code, reason) => {
1157
- manualCloseRef.current = true;
1158
- manualOpenRef.current = false;
1159
- suppressReconnectRef.current = true;
1160
- terminalErrorRef.current = null;
1226
+ controller.noteUserCloseRequested();
1161
1227
  reconnect.cancel();
1162
1228
  heartbeat.stop();
1163
1229
  commitState((current) => ({
@@ -1199,22 +1265,22 @@ var useWebSocket = (options) => {
1199
1265
  }));
1200
1266
  return;
1201
1267
  }
1202
- const shouldConnect = terminalErrorRef.current === null && (connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running");
1268
+ const shouldConnect = controller.peekTerminalError() === null && (connect && !controller.hasManualCloseRequested() || controller.hasManualOpenRequested() || reconnect.status === "running");
1203
1269
  const nextSocketKey = `${resolvedUrl}::${protocolsDependency}::${options.binaryType ?? "blob"}`;
1204
1270
  if (!shouldConnect) {
1205
1271
  if (socketRef.current !== null) {
1206
- suppressReconnectRef.current = true;
1272
+ controller.noteEffectInitiatedClose();
1207
1273
  closeSocket();
1208
1274
  }
1209
1275
  socketKeyRef.current = null;
1210
1276
  commitState((current) => ({
1211
1277
  ...current,
1212
- status: terminalErrorRef.current !== null ? "error" : manualCloseRef.current ? "closed" : "idle"
1278
+ status: controller.peekTerminalError() !== null ? "error" : controller.hasManualCloseRequested() ? "closed" : "idle"
1213
1279
  }));
1214
1280
  return;
1215
1281
  }
1216
1282
  if (socketRef.current !== null && socketKeyRef.current !== nextSocketKey) {
1217
- suppressReconnectRef.current = true;
1283
+ controller.noteEffectInitiatedClose();
1218
1284
  closeSocket();
1219
1285
  }
1220
1286
  if (socketRef.current !== null) {
@@ -1281,6 +1347,7 @@ var useWebSocket = (options) => {
1281
1347
  closeSocket,
1282
1348
  commitState,
1283
1349
  connect,
1350
+ controller,
1284
1351
  handleClose,
1285
1352
  handleError,
1286
1353
  handleMessage,
@@ -1295,11 +1362,10 @@ var useWebSocket = (options) => {
1295
1362
  supported
1296
1363
  ]);
1297
1364
  useEffect(() => () => {
1298
- suppressReconnectRef.current = true;
1365
+ controller.noteEffectInitiatedClose();
1299
1366
  socketKeyRef.current = null;
1300
1367
  activeSocketEpochRef.current = null;
1301
1368
  closingSocketEpochRef.current = null;
1302
- terminalErrorRef.current = null;
1303
1369
  const socket2 = socketRef.current;
1304
1370
  socketRef.current = null;
1305
1371
  if (socket2 === null) {
@@ -1308,7 +1374,7 @@ var useWebSocket = (options) => {
1308
1374
  if (isSocketActive(socket2)) {
1309
1375
  socket2.close();
1310
1376
  }
1311
- }, []);
1377
+ }, [controller]);
1312
1378
  const stopHeartbeat = heartbeat.stop;
1313
1379
  useEffect(() => {
1314
1380
  if (state.status !== "open") {
@@ -1413,7 +1479,7 @@ var useWebSocket = (options) => {
1413
1479
  socket
1414
1480
  };
1415
1481
  };
1416
- var createInitialState4 = (status = "idle") => ({
1482
+ var createInitialState5 = (status = "idle") => ({
1417
1483
  lastChangedAt: null,
1418
1484
  lastError: null,
1419
1485
  lastEventName: null,
@@ -1450,10 +1516,12 @@ var useEventSource = (options) => {
1450
1516
  const terminalErrorRef = useRef(null);
1451
1517
  const [openNonce, setOpenNonce] = useState(0);
1452
1518
  const [state, setState] = useState(
1453
- () => createInitialState4(connect ? "connecting" : "idle")
1519
+ () => createInitialState5(connect ? "connecting" : "idle")
1454
1520
  );
1455
1521
  const stateRef = useRef(state);
1456
- stateRef.current = state;
1522
+ useInsertionEffect(() => {
1523
+ stateRef.current = state;
1524
+ });
1457
1525
  const reconnectEnabled = options.reconnect !== false && supported && resolvedUrl !== null;
1458
1526
  const reconnect = useReconnect(
1459
1527
  options.reconnect === false ? { enabled: false } : {