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