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/README.md +14 -0
- package/dist/index.cjs +126 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +127 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useSyncExternalStore, useRef, useState, useEffect,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
() =>
|
|
944
|
+
() => createInitialState4(connect ? "connecting" : "idle")
|
|
860
945
|
);
|
|
861
946
|
const stateRef = useRef(state);
|
|
862
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1092
|
-
|
|
1093
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 = !
|
|
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
|
-
|
|
1210
|
+
controller.clearReconnectSuppression();
|
|
1135
1211
|
}
|
|
1136
1212
|
});
|
|
1137
1213
|
const open = useStableCallback(() => {
|
|
1138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1222
|
+
controller.noteUserReconnectClosed();
|
|
1154
1223
|
reconnect.schedule("manual");
|
|
1155
1224
|
});
|
|
1156
1225
|
const close = useStableCallback((code, reason) => {
|
|
1157
|
-
|
|
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 =
|
|
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
|
-
|
|
1272
|
+
controller.noteEffectInitiatedClose();
|
|
1207
1273
|
closeSocket();
|
|
1208
1274
|
}
|
|
1209
1275
|
socketKeyRef.current = null;
|
|
1210
1276
|
commitState((current) => ({
|
|
1211
1277
|
...current,
|
|
1212
|
-
status:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
() =>
|
|
1519
|
+
() => createInitialState5(connect ? "connecting" : "idle")
|
|
1454
1520
|
);
|
|
1455
1521
|
const stateRef = useRef(state);
|
|
1456
|
-
|
|
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 } : {
|