react-realtime-hooks 1.0.1 → 1.0.3
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 +7 -2
- package/dist/index.cjs +168 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +168 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -402,6 +402,7 @@ var useHeartbeat = (options) => {
|
|
|
402
402
|
const startOnMount = options.startOnMount ?? true;
|
|
403
403
|
const intervalRef = useRef(createManagedInterval());
|
|
404
404
|
const timeoutRef = useRef(createManagedTimeout());
|
|
405
|
+
const generationRef = useRef(0);
|
|
405
406
|
const [state, setState] = useState(
|
|
406
407
|
() => createInitialState2(enabled && startOnMount)
|
|
407
408
|
);
|
|
@@ -428,8 +429,7 @@ var useHeartbeat = (options) => {
|
|
|
428
429
|
handleTimeout();
|
|
429
430
|
}, options.timeoutMs);
|
|
430
431
|
});
|
|
431
|
-
const
|
|
432
|
-
const performedAt = Date.now();
|
|
432
|
+
const handleBeatSuccess = useEffectEvent((performedAt) => {
|
|
433
433
|
commitState((current) => ({
|
|
434
434
|
...current,
|
|
435
435
|
hasTimedOut: false,
|
|
@@ -437,7 +437,35 @@ var useHeartbeat = (options) => {
|
|
|
437
437
|
}));
|
|
438
438
|
scheduleTimeout();
|
|
439
439
|
options.onBeat?.();
|
|
440
|
-
|
|
440
|
+
});
|
|
441
|
+
const handleBeatError = useEffectEvent((error) => {
|
|
442
|
+
timeoutRef.current.cancel();
|
|
443
|
+
options.onError?.(error);
|
|
444
|
+
});
|
|
445
|
+
const runBeat = useEffectEvent(() => {
|
|
446
|
+
const generation = generationRef.current;
|
|
447
|
+
const completeBeat = (result) => {
|
|
448
|
+
if (generation !== generationRef.current || result === false) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
handleBeatSuccess(Date.now());
|
|
452
|
+
};
|
|
453
|
+
const failBeat = (error) => {
|
|
454
|
+
if (generation !== generationRef.current) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
handleBeatError(error);
|
|
458
|
+
};
|
|
459
|
+
try {
|
|
460
|
+
const result = options.beat?.();
|
|
461
|
+
if (result !== null && typeof result === "object" && "then" in result) {
|
|
462
|
+
void Promise.resolve(result).then(completeBeat, failBeat);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
completeBeat(result);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
failBeat(error);
|
|
468
|
+
}
|
|
441
469
|
});
|
|
442
470
|
const start = () => {
|
|
443
471
|
if (!enabled) {
|
|
@@ -455,6 +483,7 @@ var useHeartbeat = (options) => {
|
|
|
455
483
|
}));
|
|
456
484
|
};
|
|
457
485
|
const stop = () => {
|
|
486
|
+
generationRef.current += 1;
|
|
458
487
|
intervalRef.current.cancel();
|
|
459
488
|
timeoutRef.current.cancel();
|
|
460
489
|
commitState((current) => ({
|
|
@@ -500,6 +529,7 @@ var useHeartbeat = (options) => {
|
|
|
500
529
|
return void 0;
|
|
501
530
|
}, [enabled, options.intervalMs, startOnMount]);
|
|
502
531
|
useEffect(() => () => {
|
|
532
|
+
generationRef.current += 1;
|
|
503
533
|
intervalRef.current.cancel();
|
|
504
534
|
timeoutRef.current.cancel();
|
|
505
535
|
}, []);
|
|
@@ -605,6 +635,7 @@ var toProtocolsDependency = (protocols) => {
|
|
|
605
635
|
return Array.isArray(protocols) ? protocols.join("|") : protocols;
|
|
606
636
|
};
|
|
607
637
|
var toHeartbeatConfig = (heartbeat) => heartbeat === void 0 || heartbeat === false ? null : heartbeat;
|
|
638
|
+
var isSocketActive = (socket) => socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;
|
|
608
639
|
var useWebSocket = (options) => {
|
|
609
640
|
const connect = options.connect ?? true;
|
|
610
641
|
const supported = isWebSocketSupported();
|
|
@@ -616,6 +647,8 @@ var useWebSocket = (options) => {
|
|
|
616
647
|
const manualOpenRef = useRef(false);
|
|
617
648
|
const skipCloseReconnectRef = useRef(false);
|
|
618
649
|
const suppressReconnectRef = useRef(false);
|
|
650
|
+
const pendingCloseActionRef = useRef(null);
|
|
651
|
+
const terminalErrorRef = useRef(null);
|
|
619
652
|
const [openNonce, setOpenNonce] = useState(0);
|
|
620
653
|
const [state, setState] = useState(
|
|
621
654
|
() => createInitialState3(connect ? "connecting" : "idle")
|
|
@@ -633,6 +666,7 @@ var useWebSocket = (options) => {
|
|
|
633
666
|
const heartbeatConfig = toHeartbeatConfig(
|
|
634
667
|
options.heartbeat
|
|
635
668
|
);
|
|
669
|
+
const defaultHeartbeatAction = options.reconnect === false ? "close" : "reconnect";
|
|
636
670
|
const heartbeatHookOptions = heartbeatConfig === null ? {
|
|
637
671
|
enabled: false,
|
|
638
672
|
intervalMs: 1e3,
|
|
@@ -668,6 +702,27 @@ var useWebSocket = (options) => {
|
|
|
668
702
|
if (heartbeatConfig !== null && heartbeatConfig.onTimeout !== void 0) {
|
|
669
703
|
heartbeatHookOptions.onTimeout = heartbeatConfig.onTimeout;
|
|
670
704
|
}
|
|
705
|
+
if (heartbeatConfig !== null) {
|
|
706
|
+
const onTimeout = heartbeatHookOptions.onTimeout;
|
|
707
|
+
heartbeatHookOptions.onTimeout = () => {
|
|
708
|
+
applyHeartbeatAction(
|
|
709
|
+
heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
710
|
+
new Event("heartbeat-timeout"),
|
|
711
|
+
"heartbeat-timeout"
|
|
712
|
+
);
|
|
713
|
+
onTimeout?.();
|
|
714
|
+
};
|
|
715
|
+
const onError = heartbeatConfig.onError;
|
|
716
|
+
heartbeatHookOptions.onError = (error) => {
|
|
717
|
+
const event = error instanceof Event ? error : new Event("heartbeat-error");
|
|
718
|
+
applyHeartbeatAction(
|
|
719
|
+
heartbeatConfig.errorAction ?? heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
720
|
+
event,
|
|
721
|
+
"error"
|
|
722
|
+
);
|
|
723
|
+
onError?.(error);
|
|
724
|
+
};
|
|
725
|
+
}
|
|
671
726
|
const heartbeat = useHeartbeat(
|
|
672
727
|
heartbeatHookOptions
|
|
673
728
|
);
|
|
@@ -683,10 +738,46 @@ var useWebSocket = (options) => {
|
|
|
683
738
|
}
|
|
684
739
|
socketRef.current = null;
|
|
685
740
|
socketKeyRef.current = null;
|
|
686
|
-
if (socket2
|
|
741
|
+
if (isSocketActive(socket2)) {
|
|
687
742
|
socket2.close(code, reason);
|
|
688
743
|
}
|
|
689
744
|
});
|
|
745
|
+
const applyHeartbeatAction = useEffectEvent(
|
|
746
|
+
(action, error, reconnectTrigger) => {
|
|
747
|
+
heartbeat.stop();
|
|
748
|
+
if (action === "none") {
|
|
749
|
+
commitState((current) => ({
|
|
750
|
+
...current,
|
|
751
|
+
lastChangedAt: Date.now(),
|
|
752
|
+
lastError: error
|
|
753
|
+
}));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const shouldReconnect = action === "reconnect" && reconnectEnabled && (options.shouldReconnect?.(error) ?? true);
|
|
757
|
+
manualOpenRef.current = false;
|
|
758
|
+
terminalErrorRef.current = shouldReconnect ? null : error;
|
|
759
|
+
const socket2 = socketRef.current;
|
|
760
|
+
if (socket2 === null || !isSocketActive(socket2)) {
|
|
761
|
+
commitState((current) => ({
|
|
762
|
+
...current,
|
|
763
|
+
lastChangedAt: Date.now(),
|
|
764
|
+
lastError: error,
|
|
765
|
+
status: shouldReconnect ? "reconnecting" : "error"
|
|
766
|
+
}));
|
|
767
|
+
if (shouldReconnect) {
|
|
768
|
+
reconnect.schedule(reconnectTrigger);
|
|
769
|
+
}
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
pendingCloseActionRef.current = {
|
|
773
|
+
error,
|
|
774
|
+
reconnectTrigger: shouldReconnect ? reconnectTrigger : null
|
|
775
|
+
};
|
|
776
|
+
skipCloseReconnectRef.current = true;
|
|
777
|
+
suppressReconnectRef.current = true;
|
|
778
|
+
closeSocket();
|
|
779
|
+
}
|
|
780
|
+
);
|
|
690
781
|
const parseMessage = useEffectEvent((event) => {
|
|
691
782
|
const parser = options.parseMessage ?? defaultParseMessage;
|
|
692
783
|
return parser(event);
|
|
@@ -699,7 +790,9 @@ var useWebSocket = (options) => {
|
|
|
699
790
|
});
|
|
700
791
|
const handleOpen = useEffectEvent((event, socket2) => {
|
|
701
792
|
manualCloseRef.current = false;
|
|
793
|
+
manualOpenRef.current = false;
|
|
702
794
|
suppressReconnectRef.current = false;
|
|
795
|
+
terminalErrorRef.current = null;
|
|
703
796
|
reconnect.markConnected();
|
|
704
797
|
heartbeat.start();
|
|
705
798
|
commitState((current) => ({
|
|
@@ -723,11 +816,19 @@ var useWebSocket = (options) => {
|
|
|
723
816
|
options.onMessage?.(message, event);
|
|
724
817
|
} catch {
|
|
725
818
|
const parseError = new Event("error");
|
|
819
|
+
terminalErrorRef.current = parseError;
|
|
820
|
+
manualOpenRef.current = false;
|
|
821
|
+
skipCloseReconnectRef.current = true;
|
|
822
|
+
suppressReconnectRef.current = true;
|
|
823
|
+
reconnect.cancel();
|
|
824
|
+
heartbeat.stop();
|
|
726
825
|
commitState((current) => ({
|
|
727
826
|
...current,
|
|
827
|
+
lastChangedAt: Date.now(),
|
|
728
828
|
lastError: parseError,
|
|
729
829
|
status: "error"
|
|
730
830
|
}));
|
|
831
|
+
closeSocket(1003, "parse-error");
|
|
731
832
|
}
|
|
732
833
|
});
|
|
733
834
|
const handleError = useEffectEvent((event) => {
|
|
@@ -744,8 +845,38 @@ var useWebSocket = (options) => {
|
|
|
744
845
|
socketKeyRef.current = null;
|
|
745
846
|
heartbeat.stop();
|
|
746
847
|
updateBufferedAmount();
|
|
848
|
+
const pendingCloseAction = pendingCloseActionRef.current;
|
|
849
|
+
pendingCloseActionRef.current = null;
|
|
850
|
+
const terminalError = terminalErrorRef.current;
|
|
747
851
|
const skipCloseReconnect = skipCloseReconnectRef.current;
|
|
748
852
|
skipCloseReconnectRef.current = false;
|
|
853
|
+
if (pendingCloseAction !== null) {
|
|
854
|
+
suppressReconnectRef.current = false;
|
|
855
|
+
commitState((current) => ({
|
|
856
|
+
...current,
|
|
857
|
+
lastChangedAt: Date.now(),
|
|
858
|
+
lastCloseEvent: event,
|
|
859
|
+
lastError: pendingCloseAction.error ?? current.lastError,
|
|
860
|
+
status: pendingCloseAction.reconnectTrigger === null ? "error" : "reconnecting"
|
|
861
|
+
}));
|
|
862
|
+
options.onClose?.(event);
|
|
863
|
+
if (pendingCloseAction.reconnectTrigger !== null) {
|
|
864
|
+
reconnect.schedule(pendingCloseAction.reconnectTrigger);
|
|
865
|
+
}
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (terminalError !== null) {
|
|
869
|
+
suppressReconnectRef.current = false;
|
|
870
|
+
commitState((current) => ({
|
|
871
|
+
...current,
|
|
872
|
+
lastChangedAt: Date.now(),
|
|
873
|
+
lastCloseEvent: event,
|
|
874
|
+
lastError: terminalError,
|
|
875
|
+
status: "error"
|
|
876
|
+
}));
|
|
877
|
+
options.onClose?.(event);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
749
880
|
const shouldReconnect = !suppressReconnectRef.current && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
750
881
|
commitState((current) => ({
|
|
751
882
|
...current,
|
|
@@ -764,6 +895,7 @@ var useWebSocket = (options) => {
|
|
|
764
895
|
manualCloseRef.current = false;
|
|
765
896
|
manualOpenRef.current = true;
|
|
766
897
|
suppressReconnectRef.current = false;
|
|
898
|
+
terminalErrorRef.current = null;
|
|
767
899
|
reconnect.cancel();
|
|
768
900
|
setOpenNonce((current) => current + 1);
|
|
769
901
|
};
|
|
@@ -772,6 +904,7 @@ var useWebSocket = (options) => {
|
|
|
772
904
|
manualOpenRef.current = true;
|
|
773
905
|
skipCloseReconnectRef.current = true;
|
|
774
906
|
suppressReconnectRef.current = true;
|
|
907
|
+
terminalErrorRef.current = null;
|
|
775
908
|
heartbeat.stop();
|
|
776
909
|
closeSocket();
|
|
777
910
|
suppressReconnectRef.current = false;
|
|
@@ -781,6 +914,7 @@ var useWebSocket = (options) => {
|
|
|
781
914
|
manualCloseRef.current = true;
|
|
782
915
|
manualOpenRef.current = false;
|
|
783
916
|
suppressReconnectRef.current = true;
|
|
917
|
+
terminalErrorRef.current = null;
|
|
784
918
|
reconnect.cancel();
|
|
785
919
|
heartbeat.stop();
|
|
786
920
|
commitState((current) => ({
|
|
@@ -818,7 +952,7 @@ var useWebSocket = (options) => {
|
|
|
818
952
|
}));
|
|
819
953
|
return;
|
|
820
954
|
}
|
|
821
|
-
const shouldConnect = connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running";
|
|
955
|
+
const shouldConnect = terminalErrorRef.current === null && (connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running");
|
|
822
956
|
const nextSocketKey = `${resolvedUrl}::${protocolsDependency}::${options.binaryType ?? "blob"}`;
|
|
823
957
|
if (!shouldConnect) {
|
|
824
958
|
if (socketRef.current !== null) {
|
|
@@ -828,7 +962,7 @@ var useWebSocket = (options) => {
|
|
|
828
962
|
socketKeyRef.current = null;
|
|
829
963
|
commitState((current) => ({
|
|
830
964
|
...current,
|
|
831
|
-
status: manualCloseRef.current ? "closed" : "idle"
|
|
965
|
+
status: terminalErrorRef.current !== null ? "error" : manualCloseRef.current ? "closed" : "idle"
|
|
832
966
|
}));
|
|
833
967
|
return;
|
|
834
968
|
}
|
|
@@ -883,12 +1017,13 @@ var useWebSocket = (options) => {
|
|
|
883
1017
|
useEffect(() => () => {
|
|
884
1018
|
suppressReconnectRef.current = true;
|
|
885
1019
|
socketKeyRef.current = null;
|
|
1020
|
+
terminalErrorRef.current = null;
|
|
886
1021
|
const socket2 = socketRef.current;
|
|
887
1022
|
socketRef.current = null;
|
|
888
1023
|
if (socket2 === null) {
|
|
889
1024
|
return;
|
|
890
1025
|
}
|
|
891
|
-
if (socket2
|
|
1026
|
+
if (isSocketActive(socket2)) {
|
|
892
1027
|
socket2.close();
|
|
893
1028
|
}
|
|
894
1029
|
}, []);
|
|
@@ -979,6 +1114,7 @@ var useEventSource = (options) => {
|
|
|
979
1114
|
const manualOpenRef = useRef(false);
|
|
980
1115
|
const skipErrorReconnectRef = useRef(false);
|
|
981
1116
|
const suppressReconnectRef = useRef(false);
|
|
1117
|
+
const terminalErrorRef = useRef(null);
|
|
982
1118
|
const [openNonce, setOpenNonce] = useState(0);
|
|
983
1119
|
const [state, setState] = useState(
|
|
984
1120
|
() => createInitialState4(connect ? "connecting" : "idle")
|
|
@@ -1012,7 +1148,9 @@ var useEventSource = (options) => {
|
|
|
1012
1148
|
});
|
|
1013
1149
|
const handleOpen = useEffectEvent((event, source) => {
|
|
1014
1150
|
manualCloseRef.current = false;
|
|
1151
|
+
manualOpenRef.current = false;
|
|
1015
1152
|
suppressReconnectRef.current = false;
|
|
1153
|
+
terminalErrorRef.current = null;
|
|
1016
1154
|
reconnect.markConnected();
|
|
1017
1155
|
commitState((current) => ({
|
|
1018
1156
|
...current,
|
|
@@ -1038,8 +1176,14 @@ var useEventSource = (options) => {
|
|
|
1038
1176
|
options.onMessage?.(message, event);
|
|
1039
1177
|
} catch {
|
|
1040
1178
|
const parseError = new Event("error");
|
|
1179
|
+
terminalErrorRef.current = parseError;
|
|
1180
|
+
manualOpenRef.current = false;
|
|
1181
|
+
suppressReconnectRef.current = true;
|
|
1182
|
+
reconnect.cancel();
|
|
1183
|
+
closeEventSource();
|
|
1041
1184
|
commitState((current) => ({
|
|
1042
1185
|
...current,
|
|
1186
|
+
lastChangedAt: Date.now(),
|
|
1043
1187
|
lastError: parseError,
|
|
1044
1188
|
status: "error"
|
|
1045
1189
|
}));
|
|
@@ -1047,6 +1191,17 @@ var useEventSource = (options) => {
|
|
|
1047
1191
|
}
|
|
1048
1192
|
);
|
|
1049
1193
|
const handleError = useEffectEvent((event, source) => {
|
|
1194
|
+
const terminalError = terminalErrorRef.current;
|
|
1195
|
+
if (terminalError !== null) {
|
|
1196
|
+
suppressReconnectRef.current = false;
|
|
1197
|
+
commitState((current) => ({
|
|
1198
|
+
...current,
|
|
1199
|
+
lastChangedAt: Date.now(),
|
|
1200
|
+
lastError: terminalError,
|
|
1201
|
+
status: "error"
|
|
1202
|
+
}));
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1050
1205
|
const skipErrorReconnect = skipErrorReconnectRef.current;
|
|
1051
1206
|
skipErrorReconnectRef.current = false;
|
|
1052
1207
|
const shouldReconnect = !suppressReconnectRef.current && !skipErrorReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
@@ -1073,6 +1228,7 @@ var useEventSource = (options) => {
|
|
|
1073
1228
|
manualCloseRef.current = false;
|
|
1074
1229
|
manualOpenRef.current = true;
|
|
1075
1230
|
suppressReconnectRef.current = false;
|
|
1231
|
+
terminalErrorRef.current = null;
|
|
1076
1232
|
reconnect.cancel();
|
|
1077
1233
|
setOpenNonce((current) => current + 1);
|
|
1078
1234
|
};
|
|
@@ -1081,6 +1237,7 @@ var useEventSource = (options) => {
|
|
|
1081
1237
|
manualOpenRef.current = true;
|
|
1082
1238
|
skipErrorReconnectRef.current = true;
|
|
1083
1239
|
suppressReconnectRef.current = true;
|
|
1240
|
+
terminalErrorRef.current = null;
|
|
1084
1241
|
closeEventSource();
|
|
1085
1242
|
suppressReconnectRef.current = false;
|
|
1086
1243
|
reconnect.schedule("manual");
|
|
@@ -1089,6 +1246,7 @@ var useEventSource = (options) => {
|
|
|
1089
1246
|
manualCloseRef.current = true;
|
|
1090
1247
|
manualOpenRef.current = false;
|
|
1091
1248
|
suppressReconnectRef.current = true;
|
|
1249
|
+
terminalErrorRef.current = null;
|
|
1092
1250
|
reconnect.cancel();
|
|
1093
1251
|
closeEventSource();
|
|
1094
1252
|
commitState((current) => ({
|
|
@@ -1115,7 +1273,7 @@ var useEventSource = (options) => {
|
|
|
1115
1273
|
}));
|
|
1116
1274
|
return;
|
|
1117
1275
|
}
|
|
1118
|
-
const shouldConnect = connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running";
|
|
1276
|
+
const shouldConnect = terminalErrorRef.current === null && (connect && !manualCloseRef.current || manualOpenRef.current || reconnect.status === "running");
|
|
1119
1277
|
const nextEventSourceKey = [
|
|
1120
1278
|
resolvedUrl,
|
|
1121
1279
|
options.withCredentials ? "credentials" : "anonymous",
|
|
@@ -1129,7 +1287,7 @@ var useEventSource = (options) => {
|
|
|
1129
1287
|
eventSourceKeyRef.current = null;
|
|
1130
1288
|
commitState((current) => ({
|
|
1131
1289
|
...current,
|
|
1132
|
-
status: manualCloseRef.current ? "closed" : "idle"
|
|
1290
|
+
status: terminalErrorRef.current !== null ? "error" : manualCloseRef.current ? "closed" : "idle"
|
|
1133
1291
|
}));
|
|
1134
1292
|
return;
|
|
1135
1293
|
}
|
|
@@ -1191,6 +1349,7 @@ var useEventSource = (options) => {
|
|
|
1191
1349
|
useEffect(() => () => {
|
|
1192
1350
|
suppressReconnectRef.current = true;
|
|
1193
1351
|
eventSourceKeyRef.current = null;
|
|
1352
|
+
terminalErrorRef.current = null;
|
|
1194
1353
|
const source = eventSourceRef.current;
|
|
1195
1354
|
eventSourceRef.current = null;
|
|
1196
1355
|
if (source !== null) {
|