react-realtime-hooks 1.2.0 → 1.2.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 -5
- package/dist/index.cjs +208 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +209 -102
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.cjs
CHANGED
|
@@ -444,6 +444,11 @@ var createReconnectAttempt = (attempt, trigger, options, lastDelayMs, config = {
|
|
|
444
444
|
trigger
|
|
445
445
|
};
|
|
446
446
|
};
|
|
447
|
+
var useStableCallback = (callback) => {
|
|
448
|
+
const callbackRef = react.useRef(callback);
|
|
449
|
+
callbackRef.current = callback;
|
|
450
|
+
return react.useCallback((...args) => callbackRef.current(...args), []);
|
|
451
|
+
};
|
|
447
452
|
|
|
448
453
|
// src/hooks/useReconnect.ts
|
|
449
454
|
var createInitialState = (enabled) => ({
|
|
@@ -467,20 +472,20 @@ var useReconnect = (options = {}) => {
|
|
|
467
472
|
setState(resolved);
|
|
468
473
|
});
|
|
469
474
|
};
|
|
470
|
-
const runAttempt =
|
|
475
|
+
const runAttempt = useStableCallback((attempt) => {
|
|
471
476
|
commitState({
|
|
472
477
|
attempt,
|
|
473
478
|
nextDelayMs: null,
|
|
474
479
|
status: "running"
|
|
475
480
|
});
|
|
476
481
|
});
|
|
477
|
-
const emitSchedule =
|
|
482
|
+
const emitSchedule = useStableCallback((attempt) => {
|
|
478
483
|
normalizedOptions.onSchedule?.(attempt);
|
|
479
484
|
});
|
|
480
|
-
const emitCancel =
|
|
485
|
+
const emitCancel = useStableCallback(() => {
|
|
481
486
|
normalizedOptions.onCancel?.();
|
|
482
487
|
});
|
|
483
|
-
const emitReset =
|
|
488
|
+
const emitReset = useStableCallback(() => {
|
|
484
489
|
normalizedOptions.onReset?.();
|
|
485
490
|
});
|
|
486
491
|
react.useEffect(() => {
|
|
@@ -503,7 +508,7 @@ var useReconnect = (options = {}) => {
|
|
|
503
508
|
react.useEffect(() => () => {
|
|
504
509
|
timeoutRef.current.cancel();
|
|
505
510
|
}, []);
|
|
506
|
-
const schedule = (trigger = "manual") => {
|
|
511
|
+
const schedule = useStableCallback((trigger = "manual") => {
|
|
507
512
|
const current = stateRef.current;
|
|
508
513
|
const nextAttempt = current.attempt + 1;
|
|
509
514
|
const attempt = createReconnectAttempt(
|
|
@@ -531,8 +536,8 @@ var useReconnect = (options = {}) => {
|
|
|
531
536
|
status: "scheduled"
|
|
532
537
|
});
|
|
533
538
|
emitSchedule(attempt);
|
|
534
|
-
};
|
|
535
|
-
const cancel = () => {
|
|
539
|
+
});
|
|
540
|
+
const cancel = useStableCallback(() => {
|
|
536
541
|
const current = stateRef.current;
|
|
537
542
|
const shouldEmitCancel = timeoutRef.current.isActive() || current.status === "scheduled" || current.status === "running";
|
|
538
543
|
timeoutRef.current.cancel();
|
|
@@ -544,14 +549,14 @@ var useReconnect = (options = {}) => {
|
|
|
544
549
|
if (shouldEmitCancel) {
|
|
545
550
|
emitCancel();
|
|
546
551
|
}
|
|
547
|
-
};
|
|
548
|
-
const reset = () => {
|
|
552
|
+
});
|
|
553
|
+
const reset = useStableCallback(() => {
|
|
549
554
|
timeoutRef.current.cancel();
|
|
550
555
|
lastDelayRef.current = null;
|
|
551
556
|
commitState(createInitialState(normalizedOptions.enabled));
|
|
552
557
|
emitReset();
|
|
553
|
-
};
|
|
554
|
-
const markConnected = () => {
|
|
558
|
+
});
|
|
559
|
+
const markConnected = useStableCallback(() => {
|
|
555
560
|
timeoutRef.current.cancel();
|
|
556
561
|
if (normalizedOptions.resetOnSuccess) {
|
|
557
562
|
lastDelayRef.current = null;
|
|
@@ -564,7 +569,7 @@ var useReconnect = (options = {}) => {
|
|
|
564
569
|
nextDelayMs: null,
|
|
565
570
|
status: normalizedOptions.enabled ? "idle" : "stopped"
|
|
566
571
|
}));
|
|
567
|
-
};
|
|
572
|
+
});
|
|
568
573
|
return {
|
|
569
574
|
attempt: state.attempt,
|
|
570
575
|
cancel,
|
|
@@ -600,14 +605,14 @@ var useHeartbeat = (options) => {
|
|
|
600
605
|
stateRef.current = resolved;
|
|
601
606
|
setState(resolved);
|
|
602
607
|
};
|
|
603
|
-
const handleTimeout =
|
|
608
|
+
const handleTimeout = useStableCallback(() => {
|
|
604
609
|
commitState((current) => ({
|
|
605
610
|
...current,
|
|
606
611
|
hasTimedOut: true
|
|
607
612
|
}));
|
|
608
613
|
options.onTimeout?.();
|
|
609
614
|
});
|
|
610
|
-
const scheduleTimeout =
|
|
615
|
+
const scheduleTimeout = useStableCallback(() => {
|
|
611
616
|
if (options.timeoutMs === void 0) {
|
|
612
617
|
timeoutRef.current.cancel();
|
|
613
618
|
return;
|
|
@@ -616,7 +621,7 @@ var useHeartbeat = (options) => {
|
|
|
616
621
|
handleTimeout();
|
|
617
622
|
}, options.timeoutMs);
|
|
618
623
|
});
|
|
619
|
-
const handleBeatSuccess =
|
|
624
|
+
const handleBeatSuccess = useStableCallback((performedAt) => {
|
|
620
625
|
commitState((current) => ({
|
|
621
626
|
...current,
|
|
622
627
|
hasTimedOut: false,
|
|
@@ -625,11 +630,11 @@ var useHeartbeat = (options) => {
|
|
|
625
630
|
scheduleTimeout();
|
|
626
631
|
options.onBeat?.();
|
|
627
632
|
});
|
|
628
|
-
const handleBeatError =
|
|
633
|
+
const handleBeatError = useStableCallback((error) => {
|
|
629
634
|
timeoutRef.current.cancel();
|
|
630
635
|
options.onError?.(error);
|
|
631
636
|
});
|
|
632
|
-
const runBeat =
|
|
637
|
+
const runBeat = useStableCallback(() => {
|
|
633
638
|
const generation = generationRef.current;
|
|
634
639
|
const completeBeat = (result) => {
|
|
635
640
|
if (generation !== generationRef.current || result === false) {
|
|
@@ -654,7 +659,7 @@ var useHeartbeat = (options) => {
|
|
|
654
659
|
failBeat(error);
|
|
655
660
|
}
|
|
656
661
|
});
|
|
657
|
-
const start = () => {
|
|
662
|
+
const start = useStableCallback(() => {
|
|
658
663
|
if (!enabled) {
|
|
659
664
|
return;
|
|
660
665
|
}
|
|
@@ -668,20 +673,20 @@ var useHeartbeat = (options) => {
|
|
|
668
673
|
hasTimedOut: false,
|
|
669
674
|
isRunning: true
|
|
670
675
|
}));
|
|
671
|
-
};
|
|
672
|
-
const stop = () => {
|
|
676
|
+
});
|
|
677
|
+
const stop = useStableCallback(() => {
|
|
673
678
|
generationRef.current += 1;
|
|
674
679
|
intervalRef.current.cancel();
|
|
675
680
|
timeoutRef.current.cancel();
|
|
676
681
|
commitState(createInitialState2(false));
|
|
677
|
-
};
|
|
678
|
-
const beat = () => {
|
|
682
|
+
});
|
|
683
|
+
const beat = useStableCallback(() => {
|
|
679
684
|
if (!enabled) {
|
|
680
685
|
return;
|
|
681
686
|
}
|
|
682
687
|
runBeat();
|
|
683
|
-
};
|
|
684
|
-
const notifyAck = (message) => {
|
|
688
|
+
});
|
|
689
|
+
const notifyAck = useStableCallback((message) => {
|
|
685
690
|
if (stateRef.current.lastBeatAt === null) {
|
|
686
691
|
return false;
|
|
687
692
|
}
|
|
@@ -698,7 +703,7 @@ var useHeartbeat = (options) => {
|
|
|
698
703
|
latencyMs: current.lastBeatAt === null ? null : acknowledgedAt - current.lastBeatAt
|
|
699
704
|
}));
|
|
700
705
|
return true;
|
|
701
|
-
};
|
|
706
|
+
});
|
|
702
707
|
react.useEffect(() => {
|
|
703
708
|
if (!enabled) {
|
|
704
709
|
stop();
|
|
@@ -710,7 +715,7 @@ var useHeartbeat = (options) => {
|
|
|
710
715
|
}
|
|
711
716
|
stop();
|
|
712
717
|
return void 0;
|
|
713
|
-
}, [enabled, options.intervalMs, startOnMount]);
|
|
718
|
+
}, [enabled, options.intervalMs, start, startOnMount, stop]);
|
|
714
719
|
react.useEffect(() => () => {
|
|
715
720
|
generationRef.current += 1;
|
|
716
721
|
intervalRef.current.cancel();
|
|
@@ -774,6 +779,17 @@ var createConnectionStateSnapshot = (status, config = {}) => {
|
|
|
774
779
|
}
|
|
775
780
|
};
|
|
776
781
|
|
|
782
|
+
// src/core/errors.ts
|
|
783
|
+
var RealtimeErrorEvent = class extends Event {
|
|
784
|
+
cause;
|
|
785
|
+
kind;
|
|
786
|
+
constructor(type, init) {
|
|
787
|
+
super(type, init);
|
|
788
|
+
this.cause = init.cause;
|
|
789
|
+
this.kind = init.kind;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
777
793
|
// src/core/url.ts
|
|
778
794
|
var normalizeResolvedUrl = (value) => {
|
|
779
795
|
if (value === null) {
|
|
@@ -815,8 +831,9 @@ var toProtocolsDependency = (protocols) => {
|
|
|
815
831
|
if (protocols === void 0) {
|
|
816
832
|
return "";
|
|
817
833
|
}
|
|
818
|
-
return
|
|
834
|
+
return JSON.stringify(protocols);
|
|
819
835
|
};
|
|
836
|
+
var parseProtocolsDependency = (protocolsDependency) => protocolsDependency === "" ? void 0 : JSON.parse(protocolsDependency);
|
|
820
837
|
var toHeartbeatConfig = (heartbeat) => heartbeat === void 0 || heartbeat === false ? null : heartbeat;
|
|
821
838
|
var isSocketActive = (socket) => socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;
|
|
822
839
|
var useWebSocket = (options) => {
|
|
@@ -824,8 +841,15 @@ var useWebSocket = (options) => {
|
|
|
824
841
|
const supported = isWebSocketSupported();
|
|
825
842
|
const resolvedUrl = react.useMemo(() => resolveUrlProvider(options.url), [options.url]);
|
|
826
843
|
const protocolsDependency = toProtocolsDependency(options.protocols);
|
|
844
|
+
const protocols = react.useMemo(
|
|
845
|
+
() => parseProtocolsDependency(protocolsDependency),
|
|
846
|
+
[protocolsDependency]
|
|
847
|
+
);
|
|
827
848
|
const socketRef = react.useRef(null);
|
|
828
849
|
const socketKeyRef = react.useRef(null);
|
|
850
|
+
const activeSocketEpochRef = react.useRef(null);
|
|
851
|
+
const closingSocketEpochRef = react.useRef(null);
|
|
852
|
+
const nextSocketEpochRef = react.useRef(0);
|
|
829
853
|
const manualCloseRef = react.useRef(false);
|
|
830
854
|
const manualOpenRef = react.useRef(false);
|
|
831
855
|
const skipCloseReconnectRef = react.useRef(false);
|
|
@@ -890,14 +914,19 @@ var useWebSocket = (options) => {
|
|
|
890
914
|
heartbeatHookOptions.onTimeout = () => {
|
|
891
915
|
applyHeartbeatAction(
|
|
892
916
|
heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
893
|
-
new
|
|
917
|
+
new RealtimeErrorEvent("heartbeat-timeout", {
|
|
918
|
+
kind: "heartbeat-timeout"
|
|
919
|
+
}),
|
|
894
920
|
"heartbeat-timeout"
|
|
895
921
|
);
|
|
896
922
|
onTimeout?.();
|
|
897
923
|
};
|
|
898
924
|
const onError = heartbeatConfig.onError;
|
|
899
925
|
heartbeatHookOptions.onError = (error) => {
|
|
900
|
-
const event =
|
|
926
|
+
const event = new RealtimeErrorEvent("heartbeat-error", {
|
|
927
|
+
cause: error,
|
|
928
|
+
kind: "heartbeat-error"
|
|
929
|
+
});
|
|
901
930
|
applyHeartbeatAction(
|
|
902
931
|
heartbeatConfig.errorAction ?? heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
903
932
|
event,
|
|
@@ -909,23 +938,34 @@ var useWebSocket = (options) => {
|
|
|
909
938
|
const heartbeat = useHeartbeat(
|
|
910
939
|
heartbeatHookOptions
|
|
911
940
|
);
|
|
912
|
-
const commitState = (next) => {
|
|
941
|
+
const commitState = useStableCallback((next) => {
|
|
913
942
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
914
943
|
stateRef.current = resolved;
|
|
915
944
|
setState(resolved);
|
|
916
|
-
};
|
|
917
|
-
const closeSocket = react.useEffectEvent((code, reason) => {
|
|
918
|
-
const socket2 = socketRef.current;
|
|
919
|
-
if (socket2 === null) {
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
socketRef.current = null;
|
|
923
|
-
socketKeyRef.current = null;
|
|
924
|
-
if (isSocketActive(socket2)) {
|
|
925
|
-
socket2.close(code, reason);
|
|
926
|
-
}
|
|
927
945
|
});
|
|
928
|
-
const
|
|
946
|
+
const isActiveSocketEvent = useStableCallback((socketEpoch) => {
|
|
947
|
+
return activeSocketEpochRef.current === socketEpoch;
|
|
948
|
+
});
|
|
949
|
+
const shouldHandleSocketClose = useStableCallback((socketEpoch) => {
|
|
950
|
+
return activeSocketEpochRef.current === socketEpoch || closingSocketEpochRef.current === socketEpoch;
|
|
951
|
+
});
|
|
952
|
+
const closeSocket = useStableCallback(
|
|
953
|
+
(config = {}) => {
|
|
954
|
+
const socket2 = socketRef.current;
|
|
955
|
+
const socketEpoch = activeSocketEpochRef.current;
|
|
956
|
+
if (socket2 === null || socketEpoch === null) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
socketRef.current = null;
|
|
960
|
+
socketKeyRef.current = null;
|
|
961
|
+
activeSocketEpochRef.current = null;
|
|
962
|
+
closingSocketEpochRef.current = config.trackClose ? socketEpoch : null;
|
|
963
|
+
if (isSocketActive(socket2)) {
|
|
964
|
+
socket2.close(config.code, config.reason);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
const applyHeartbeatAction = useStableCallback(
|
|
929
969
|
(action, error, reconnectTrigger) => {
|
|
930
970
|
heartbeat.stop();
|
|
931
971
|
options.onError?.(error);
|
|
@@ -959,20 +999,20 @@ var useWebSocket = (options) => {
|
|
|
959
999
|
};
|
|
960
1000
|
skipCloseReconnectRef.current = true;
|
|
961
1001
|
suppressReconnectRef.current = true;
|
|
962
|
-
closeSocket();
|
|
1002
|
+
closeSocket({ trackClose: true });
|
|
963
1003
|
}
|
|
964
1004
|
);
|
|
965
|
-
const parseMessage =
|
|
1005
|
+
const parseMessage = useStableCallback((event) => {
|
|
966
1006
|
const parser = options.parseMessage ?? defaultParseMessage;
|
|
967
1007
|
return parser(event);
|
|
968
1008
|
});
|
|
969
|
-
const updateBufferedAmount =
|
|
1009
|
+
const updateBufferedAmount = useStableCallback(() => {
|
|
970
1010
|
commitState((current) => ({
|
|
971
1011
|
...current,
|
|
972
1012
|
bufferedAmount: socketRef.current?.bufferedAmount ?? 0
|
|
973
1013
|
}));
|
|
974
1014
|
});
|
|
975
|
-
const handleOpen =
|
|
1015
|
+
const handleOpen = useStableCallback((event, socket2) => {
|
|
976
1016
|
manualCloseRef.current = false;
|
|
977
1017
|
manualOpenRef.current = false;
|
|
978
1018
|
suppressReconnectRef.current = false;
|
|
@@ -987,7 +1027,7 @@ var useWebSocket = (options) => {
|
|
|
987
1027
|
}));
|
|
988
1028
|
options.onOpen?.(event, socket2);
|
|
989
1029
|
});
|
|
990
|
-
const handleMessage =
|
|
1030
|
+
const handleMessage = useStableCallback((event) => {
|
|
991
1031
|
try {
|
|
992
1032
|
const message = parseMessage(event);
|
|
993
1033
|
heartbeat.notifyAck(message);
|
|
@@ -998,8 +1038,11 @@ var useWebSocket = (options) => {
|
|
|
998
1038
|
lastMessageEvent: event
|
|
999
1039
|
}));
|
|
1000
1040
|
options.onMessage?.(message, event);
|
|
1001
|
-
} catch {
|
|
1002
|
-
const parseError = new
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1043
|
+
cause: error,
|
|
1044
|
+
kind: "parse-error"
|
|
1045
|
+
});
|
|
1003
1046
|
terminalErrorRef.current = parseError;
|
|
1004
1047
|
manualOpenRef.current = false;
|
|
1005
1048
|
skipCloseReconnectRef.current = true;
|
|
@@ -1013,10 +1056,17 @@ var useWebSocket = (options) => {
|
|
|
1013
1056
|
lastError: parseError,
|
|
1014
1057
|
status: "error"
|
|
1015
1058
|
}));
|
|
1016
|
-
closeSocket(
|
|
1059
|
+
closeSocket({
|
|
1060
|
+
code: 1003,
|
|
1061
|
+
reason: "parse-error",
|
|
1062
|
+
trackClose: true
|
|
1063
|
+
});
|
|
1017
1064
|
}
|
|
1018
1065
|
});
|
|
1019
|
-
const handleError =
|
|
1066
|
+
const handleError = useStableCallback((event, socketEpoch) => {
|
|
1067
|
+
if (!isActiveSocketEvent(socketEpoch)) {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1020
1070
|
heartbeat.stop();
|
|
1021
1071
|
commitState((current) => ({
|
|
1022
1072
|
...current,
|
|
@@ -1026,9 +1076,18 @@ var useWebSocket = (options) => {
|
|
|
1026
1076
|
}));
|
|
1027
1077
|
options.onError?.(event);
|
|
1028
1078
|
});
|
|
1029
|
-
const handleClose =
|
|
1030
|
-
|
|
1031
|
-
|
|
1079
|
+
const handleClose = useStableCallback((event, socketEpoch) => {
|
|
1080
|
+
if (!shouldHandleSocketClose(socketEpoch)) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (activeSocketEpochRef.current === socketEpoch) {
|
|
1084
|
+
socketRef.current = null;
|
|
1085
|
+
socketKeyRef.current = null;
|
|
1086
|
+
activeSocketEpochRef.current = null;
|
|
1087
|
+
}
|
|
1088
|
+
if (closingSocketEpochRef.current === socketEpoch) {
|
|
1089
|
+
closingSocketEpochRef.current = null;
|
|
1090
|
+
}
|
|
1032
1091
|
heartbeat.stop();
|
|
1033
1092
|
updateBufferedAmount();
|
|
1034
1093
|
const pendingCloseAction = pendingCloseActionRef.current;
|
|
@@ -1077,15 +1136,15 @@ var useWebSocket = (options) => {
|
|
|
1077
1136
|
suppressReconnectRef.current = false;
|
|
1078
1137
|
}
|
|
1079
1138
|
});
|
|
1080
|
-
const open = () => {
|
|
1139
|
+
const open = useStableCallback(() => {
|
|
1081
1140
|
manualCloseRef.current = false;
|
|
1082
1141
|
manualOpenRef.current = true;
|
|
1083
1142
|
suppressReconnectRef.current = false;
|
|
1084
1143
|
terminalErrorRef.current = null;
|
|
1085
1144
|
reconnect.cancel();
|
|
1086
1145
|
setOpenNonce((current) => current + 1);
|
|
1087
|
-
};
|
|
1088
|
-
const reconnectNow = () => {
|
|
1146
|
+
});
|
|
1147
|
+
const reconnectNow = useStableCallback(() => {
|
|
1089
1148
|
manualCloseRef.current = false;
|
|
1090
1149
|
manualOpenRef.current = true;
|
|
1091
1150
|
skipCloseReconnectRef.current = true;
|
|
@@ -1095,8 +1154,8 @@ var useWebSocket = (options) => {
|
|
|
1095
1154
|
closeSocket();
|
|
1096
1155
|
suppressReconnectRef.current = false;
|
|
1097
1156
|
reconnect.schedule("manual");
|
|
1098
|
-
};
|
|
1099
|
-
const close = (code, reason) => {
|
|
1157
|
+
});
|
|
1158
|
+
const close = useStableCallback((code, reason) => {
|
|
1100
1159
|
manualCloseRef.current = true;
|
|
1101
1160
|
manualOpenRef.current = false;
|
|
1102
1161
|
suppressReconnectRef.current = true;
|
|
@@ -1108,9 +1167,13 @@ var useWebSocket = (options) => {
|
|
|
1108
1167
|
lastChangedAt: Date.now(),
|
|
1109
1168
|
status: "closing"
|
|
1110
1169
|
}));
|
|
1111
|
-
closeSocket(
|
|
1112
|
-
|
|
1113
|
-
|
|
1170
|
+
closeSocket({
|
|
1171
|
+
code,
|
|
1172
|
+
reason,
|
|
1173
|
+
trackClose: true
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
const send = useStableCallback((message) => {
|
|
1114
1177
|
const socket2 = socketRef.current;
|
|
1115
1178
|
if (socket2 === null || socket2.readyState !== WebSocket.OPEN) {
|
|
1116
1179
|
return false;
|
|
@@ -1119,7 +1182,7 @@ var useWebSocket = (options) => {
|
|
|
1119
1182
|
socket2.send(serializer(message));
|
|
1120
1183
|
updateBufferedAmount();
|
|
1121
1184
|
return true;
|
|
1122
|
-
};
|
|
1185
|
+
});
|
|
1123
1186
|
react.useEffect(() => {
|
|
1124
1187
|
if (!supported) {
|
|
1125
1188
|
socketKeyRef.current = null;
|
|
@@ -1159,9 +1222,13 @@ var useWebSocket = (options) => {
|
|
|
1159
1222
|
if (socketRef.current !== null) {
|
|
1160
1223
|
return;
|
|
1161
1224
|
}
|
|
1162
|
-
const socket2 = new WebSocket(resolvedUrl,
|
|
1225
|
+
const socket2 = new WebSocket(resolvedUrl, protocols);
|
|
1226
|
+
const socketEpoch = nextSocketEpochRef.current + 1;
|
|
1163
1227
|
socketRef.current = socket2;
|
|
1164
1228
|
socketKeyRef.current = nextSocketKey;
|
|
1229
|
+
activeSocketEpochRef.current = socketEpoch;
|
|
1230
|
+
closingSocketEpochRef.current = null;
|
|
1231
|
+
nextSocketEpochRef.current = socketEpoch;
|
|
1165
1232
|
socket2.binaryType = options.binaryType ?? "blob";
|
|
1166
1233
|
commitState((current) => ({
|
|
1167
1234
|
...current,
|
|
@@ -1170,16 +1237,22 @@ var useWebSocket = (options) => {
|
|
|
1170
1237
|
status: reconnect.status === "running" || reconnect.status === "scheduled" ? "reconnecting" : "connecting"
|
|
1171
1238
|
}));
|
|
1172
1239
|
const handleSocketOpen = (event) => {
|
|
1240
|
+
if (!isActiveSocketEvent(socketEpoch)) {
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1173
1243
|
handleOpen(event, socket2);
|
|
1174
1244
|
};
|
|
1175
1245
|
const handleSocketMessage = (event) => {
|
|
1246
|
+
if (!isActiveSocketEvent(socketEpoch)) {
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1176
1249
|
handleMessage(event);
|
|
1177
1250
|
};
|
|
1178
1251
|
const handleSocketError = (event) => {
|
|
1179
|
-
handleError(event);
|
|
1252
|
+
handleError(event, socketEpoch);
|
|
1180
1253
|
};
|
|
1181
1254
|
const handleSocketClose = (event) => {
|
|
1182
|
-
handleClose(event);
|
|
1255
|
+
handleClose(event, socketEpoch);
|
|
1183
1256
|
};
|
|
1184
1257
|
socket2.addEventListener("open", handleSocketOpen);
|
|
1185
1258
|
socket2.addEventListener("message", handleSocketMessage);
|
|
@@ -1192,9 +1265,17 @@ var useWebSocket = (options) => {
|
|
|
1192
1265
|
socket2.removeEventListener("close", handleSocketClose);
|
|
1193
1266
|
};
|
|
1194
1267
|
}, [
|
|
1268
|
+
closeSocket,
|
|
1269
|
+
commitState,
|
|
1195
1270
|
connect,
|
|
1271
|
+
handleClose,
|
|
1272
|
+
handleError,
|
|
1273
|
+
handleMessage,
|
|
1274
|
+
handleOpen,
|
|
1275
|
+
isActiveSocketEvent,
|
|
1196
1276
|
openNonce,
|
|
1197
1277
|
options.binaryType,
|
|
1278
|
+
protocols,
|
|
1198
1279
|
protocolsDependency,
|
|
1199
1280
|
reconnect.status,
|
|
1200
1281
|
resolvedUrl,
|
|
@@ -1203,6 +1284,8 @@ var useWebSocket = (options) => {
|
|
|
1203
1284
|
react.useEffect(() => () => {
|
|
1204
1285
|
suppressReconnectRef.current = true;
|
|
1205
1286
|
socketKeyRef.current = null;
|
|
1287
|
+
activeSocketEpochRef.current = null;
|
|
1288
|
+
closingSocketEpochRef.current = null;
|
|
1206
1289
|
terminalErrorRef.current = null;
|
|
1207
1290
|
const socket2 = socketRef.current;
|
|
1208
1291
|
socketRef.current = null;
|
|
@@ -1213,11 +1296,12 @@ var useWebSocket = (options) => {
|
|
|
1213
1296
|
socket2.close();
|
|
1214
1297
|
}
|
|
1215
1298
|
}, []);
|
|
1299
|
+
const stopHeartbeat = heartbeat.stop;
|
|
1216
1300
|
react.useEffect(() => {
|
|
1217
1301
|
if (state.status !== "open") {
|
|
1218
|
-
|
|
1302
|
+
stopHeartbeat();
|
|
1219
1303
|
}
|
|
1220
|
-
}, [state.status]);
|
|
1304
|
+
}, [state.status, stopHeartbeat]);
|
|
1221
1305
|
const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
|
|
1222
1306
|
const snapshot = createConnectionStateSnapshot(status, {
|
|
1223
1307
|
isSupported: supported,
|
|
@@ -1273,29 +1357,27 @@ var createInitialState4 = (status = "idle") => ({
|
|
|
1273
1357
|
status
|
|
1274
1358
|
});
|
|
1275
1359
|
var defaultParseMessage2 = (event) => event.data;
|
|
1276
|
-
var toEventsDependency = (events) => {
|
|
1277
|
-
if (events === void 0 || events.length === 0) {
|
|
1278
|
-
return "";
|
|
1279
|
-
}
|
|
1280
|
-
return [...new Set(events)].sort().join("|");
|
|
1281
|
-
};
|
|
1282
1360
|
var normalizeNamedEvents = (events) => {
|
|
1283
1361
|
if (events === void 0 || events.length === 0) {
|
|
1284
1362
|
return [];
|
|
1285
1363
|
}
|
|
1286
|
-
return [...new Set(events)].filter((eventName) => eventName !== "message");
|
|
1364
|
+
return [...new Set(events)].filter((eventName) => eventName !== "message").sort();
|
|
1287
1365
|
};
|
|
1366
|
+
var toEventsDependency = (events) => JSON.stringify(normalizeNamedEvents(events));
|
|
1367
|
+
var parseEventsDependency = (eventsDependency) => JSON.parse(eventsDependency);
|
|
1288
1368
|
var useEventSource = (options) => {
|
|
1289
1369
|
const connect = options.connect ?? true;
|
|
1290
1370
|
const supported = isEventSourceSupported();
|
|
1291
1371
|
const resolvedUrl = react.useMemo(() => resolveUrlProvider(options.url), [options.url]);
|
|
1292
1372
|
const eventsDependency = toEventsDependency(options.events);
|
|
1293
1373
|
const namedEvents = react.useMemo(
|
|
1294
|
-
() =>
|
|
1374
|
+
() => parseEventsDependency(eventsDependency),
|
|
1295
1375
|
[eventsDependency]
|
|
1296
1376
|
);
|
|
1297
1377
|
const eventSourceRef = react.useRef(null);
|
|
1298
1378
|
const eventSourceKeyRef = react.useRef(null);
|
|
1379
|
+
const activeEventSourceEpochRef = react.useRef(null);
|
|
1380
|
+
const nextEventSourceEpochRef = react.useRef(0);
|
|
1299
1381
|
const manualCloseRef = react.useRef(false);
|
|
1300
1382
|
const manualOpenRef = react.useRef(false);
|
|
1301
1383
|
const skipErrorReconnectRef = react.useRef(false);
|
|
@@ -1314,25 +1396,29 @@ var useEventSource = (options) => {
|
|
|
1314
1396
|
enabled: reconnectEnabled && (options.reconnect?.enabled ?? true)
|
|
1315
1397
|
}
|
|
1316
1398
|
);
|
|
1317
|
-
const commitState = (next) => {
|
|
1399
|
+
const commitState = useStableCallback((next) => {
|
|
1318
1400
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
1319
1401
|
stateRef.current = resolved;
|
|
1320
1402
|
setState(resolved);
|
|
1321
|
-
};
|
|
1322
|
-
const closeEventSource =
|
|
1403
|
+
});
|
|
1404
|
+
const closeEventSource = useStableCallback(() => {
|
|
1323
1405
|
const source = eventSourceRef.current;
|
|
1324
1406
|
if (source === null) {
|
|
1325
1407
|
return;
|
|
1326
1408
|
}
|
|
1327
1409
|
eventSourceRef.current = null;
|
|
1328
1410
|
eventSourceKeyRef.current = null;
|
|
1411
|
+
activeEventSourceEpochRef.current = null;
|
|
1329
1412
|
source.close();
|
|
1330
1413
|
});
|
|
1331
|
-
const
|
|
1414
|
+
const isActiveEventSourceEvent = useStableCallback((sourceEpoch) => {
|
|
1415
|
+
return activeEventSourceEpochRef.current === sourceEpoch;
|
|
1416
|
+
});
|
|
1417
|
+
const parseMessage = useStableCallback((event) => {
|
|
1332
1418
|
const parser = options.parseMessage ?? defaultParseMessage2;
|
|
1333
1419
|
return parser(event);
|
|
1334
1420
|
});
|
|
1335
|
-
const handleOpen =
|
|
1421
|
+
const handleOpen = useStableCallback((event, source) => {
|
|
1336
1422
|
manualCloseRef.current = false;
|
|
1337
1423
|
manualOpenRef.current = false;
|
|
1338
1424
|
suppressReconnectRef.current = false;
|
|
@@ -1345,7 +1431,7 @@ var useEventSource = (options) => {
|
|
|
1345
1431
|
}));
|
|
1346
1432
|
options.onOpen?.(event, source);
|
|
1347
1433
|
});
|
|
1348
|
-
const commitParsedMessage =
|
|
1434
|
+
const commitParsedMessage = useStableCallback(
|
|
1349
1435
|
(eventName, event, isNamedEvent) => {
|
|
1350
1436
|
try {
|
|
1351
1437
|
const message = parseMessage(event);
|
|
@@ -1360,8 +1446,11 @@ var useEventSource = (options) => {
|
|
|
1360
1446
|
return;
|
|
1361
1447
|
}
|
|
1362
1448
|
options.onMessage?.(message, event);
|
|
1363
|
-
} catch {
|
|
1364
|
-
const parseError = new
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1451
|
+
cause: error,
|
|
1452
|
+
kind: "parse-error"
|
|
1453
|
+
});
|
|
1365
1454
|
terminalErrorRef.current = parseError;
|
|
1366
1455
|
manualOpenRef.current = false;
|
|
1367
1456
|
suppressReconnectRef.current = true;
|
|
@@ -1377,7 +1466,10 @@ var useEventSource = (options) => {
|
|
|
1377
1466
|
}
|
|
1378
1467
|
}
|
|
1379
1468
|
);
|
|
1380
|
-
const handleError =
|
|
1469
|
+
const handleError = useStableCallback((event, source, sourceEpoch) => {
|
|
1470
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1381
1473
|
const terminalError = terminalErrorRef.current;
|
|
1382
1474
|
if (terminalError !== null) {
|
|
1383
1475
|
suppressReconnectRef.current = false;
|
|
@@ -1392,12 +1484,11 @@ var useEventSource = (options) => {
|
|
|
1392
1484
|
const skipErrorReconnect = skipErrorReconnectRef.current;
|
|
1393
1485
|
skipErrorReconnectRef.current = false;
|
|
1394
1486
|
const shouldReconnect = !suppressReconnectRef.current && !skipErrorReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
1395
|
-
const readyState = source.readyState;
|
|
1396
1487
|
commitState((current) => ({
|
|
1397
1488
|
...current,
|
|
1398
1489
|
lastChangedAt: Date.now(),
|
|
1399
1490
|
lastError: event,
|
|
1400
|
-
status:
|
|
1491
|
+
status: shouldReconnect ? "reconnecting" : "closed"
|
|
1401
1492
|
}));
|
|
1402
1493
|
options.onError?.(event);
|
|
1403
1494
|
if (!shouldReconnect) {
|
|
@@ -1405,21 +1496,18 @@ var useEventSource = (options) => {
|
|
|
1405
1496
|
closeEventSource();
|
|
1406
1497
|
return;
|
|
1407
1498
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
eventSourceKeyRef.current = null;
|
|
1411
|
-
reconnect.schedule("error");
|
|
1412
|
-
}
|
|
1499
|
+
closeEventSource();
|
|
1500
|
+
reconnect.schedule("error");
|
|
1413
1501
|
});
|
|
1414
|
-
const open = () => {
|
|
1502
|
+
const open = useStableCallback(() => {
|
|
1415
1503
|
manualCloseRef.current = false;
|
|
1416
1504
|
manualOpenRef.current = true;
|
|
1417
1505
|
suppressReconnectRef.current = false;
|
|
1418
1506
|
terminalErrorRef.current = null;
|
|
1419
1507
|
reconnect.cancel();
|
|
1420
1508
|
setOpenNonce((current) => current + 1);
|
|
1421
|
-
};
|
|
1422
|
-
const reconnectNow = () => {
|
|
1509
|
+
});
|
|
1510
|
+
const reconnectNow = useStableCallback(() => {
|
|
1423
1511
|
manualCloseRef.current = false;
|
|
1424
1512
|
manualOpenRef.current = true;
|
|
1425
1513
|
skipErrorReconnectRef.current = true;
|
|
@@ -1428,8 +1516,8 @@ var useEventSource = (options) => {
|
|
|
1428
1516
|
closeEventSource();
|
|
1429
1517
|
suppressReconnectRef.current = false;
|
|
1430
1518
|
reconnect.schedule("manual");
|
|
1431
|
-
};
|
|
1432
|
-
const close = () => {
|
|
1519
|
+
});
|
|
1520
|
+
const close = useStableCallback(() => {
|
|
1433
1521
|
manualCloseRef.current = true;
|
|
1434
1522
|
manualOpenRef.current = false;
|
|
1435
1523
|
suppressReconnectRef.current = true;
|
|
@@ -1441,7 +1529,7 @@ var useEventSource = (options) => {
|
|
|
1441
1529
|
lastChangedAt: Date.now(),
|
|
1442
1530
|
status: "closed"
|
|
1443
1531
|
}));
|
|
1444
|
-
};
|
|
1532
|
+
});
|
|
1445
1533
|
react.useEffect(() => {
|
|
1446
1534
|
if (!supported) {
|
|
1447
1535
|
eventSourceKeyRef.current = null;
|
|
@@ -1460,7 +1548,7 @@ var useEventSource = (options) => {
|
|
|
1460
1548
|
}));
|
|
1461
1549
|
return;
|
|
1462
1550
|
}
|
|
1463
|
-
const shouldConnect = terminalErrorRef.current === null && (connect && !manualCloseRef.current || manualOpenRef.current
|
|
1551
|
+
const shouldConnect = terminalErrorRef.current === null && (reconnect.status === "running" || reconnect.status !== "scheduled" && (connect && !manualCloseRef.current || manualOpenRef.current));
|
|
1464
1552
|
const nextEventSourceKey = [
|
|
1465
1553
|
resolvedUrl,
|
|
1466
1554
|
options.withCredentials ? "credentials" : "anonymous",
|
|
@@ -1488,27 +1576,39 @@ var useEventSource = (options) => {
|
|
|
1488
1576
|
const source = new EventSource(resolvedUrl, {
|
|
1489
1577
|
withCredentials: options.withCredentials ?? false
|
|
1490
1578
|
});
|
|
1579
|
+
const sourceEpoch = nextEventSourceEpochRef.current + 1;
|
|
1491
1580
|
eventSourceRef.current = source;
|
|
1492
1581
|
eventSourceKeyRef.current = nextEventSourceKey;
|
|
1582
|
+
activeEventSourceEpochRef.current = sourceEpoch;
|
|
1583
|
+
nextEventSourceEpochRef.current = sourceEpoch;
|
|
1493
1584
|
commitState((current) => ({
|
|
1494
1585
|
...current,
|
|
1495
1586
|
lastChangedAt: Date.now(),
|
|
1496
1587
|
status: reconnect.status === "running" || reconnect.status === "scheduled" ? "reconnecting" : "connecting"
|
|
1497
1588
|
}));
|
|
1498
1589
|
const handleSourceOpen = (event) => {
|
|
1590
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1499
1593
|
handleOpen(event, source);
|
|
1500
1594
|
};
|
|
1501
1595
|
const handleSourceMessage = (event) => {
|
|
1596
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1502
1599
|
commitParsedMessage("message", event, false);
|
|
1503
1600
|
};
|
|
1504
1601
|
const namedEventHandlers = /* @__PURE__ */ new Map();
|
|
1505
1602
|
const handleSourceError = (event) => {
|
|
1506
|
-
handleError(event, source);
|
|
1603
|
+
handleError(event, source, sourceEpoch);
|
|
1507
1604
|
};
|
|
1508
1605
|
source.addEventListener("open", handleSourceOpen);
|
|
1509
1606
|
source.addEventListener("message", handleSourceMessage);
|
|
1510
1607
|
for (const eventName of namedEvents) {
|
|
1511
1608
|
const handler = (event) => {
|
|
1609
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1512
1612
|
commitParsedMessage(eventName, event, true);
|
|
1513
1613
|
};
|
|
1514
1614
|
namedEventHandlers.set(eventName, handler);
|
|
@@ -1524,8 +1624,14 @@ var useEventSource = (options) => {
|
|
|
1524
1624
|
source.removeEventListener("error", handleSourceError);
|
|
1525
1625
|
};
|
|
1526
1626
|
}, [
|
|
1627
|
+
closeEventSource,
|
|
1628
|
+
commitParsedMessage,
|
|
1629
|
+
commitState,
|
|
1527
1630
|
connect,
|
|
1528
1631
|
eventsDependency,
|
|
1632
|
+
handleError,
|
|
1633
|
+
handleOpen,
|
|
1634
|
+
isActiveEventSourceEvent,
|
|
1529
1635
|
namedEvents,
|
|
1530
1636
|
openNonce,
|
|
1531
1637
|
options.withCredentials,
|
|
@@ -1536,6 +1642,7 @@ var useEventSource = (options) => {
|
|
|
1536
1642
|
react.useEffect(() => () => {
|
|
1537
1643
|
suppressReconnectRef.current = true;
|
|
1538
1644
|
eventSourceKeyRef.current = null;
|
|
1645
|
+
activeEventSourceEpochRef.current = null;
|
|
1539
1646
|
terminalErrorRef.current = null;
|
|
1540
1647
|
const source = eventSourceRef.current;
|
|
1541
1648
|
eventSourceRef.current = null;
|
|
@@ -1580,6 +1687,7 @@ var useEventSource = (options) => {
|
|
|
1580
1687
|
};
|
|
1581
1688
|
};
|
|
1582
1689
|
|
|
1690
|
+
exports.RealtimeErrorEvent = RealtimeErrorEvent;
|
|
1583
1691
|
exports.useConnectionGate = useConnectionGate;
|
|
1584
1692
|
exports.useEventSource = useEventSource;
|
|
1585
1693
|
exports.useHeartbeat = useHeartbeat;
|