react-realtime-hooks 1.2.1 → 1.3.0
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 +148 -86
- 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 +149 -88
- 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,6 +841,10 @@ 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);
|
|
829
850
|
const activeSocketEpochRef = react.useRef(null);
|
|
@@ -893,14 +914,19 @@ var useWebSocket = (options) => {
|
|
|
893
914
|
heartbeatHookOptions.onTimeout = () => {
|
|
894
915
|
applyHeartbeatAction(
|
|
895
916
|
heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
896
|
-
new
|
|
917
|
+
new RealtimeErrorEvent("heartbeat-timeout", {
|
|
918
|
+
kind: "heartbeat-timeout"
|
|
919
|
+
}),
|
|
897
920
|
"heartbeat-timeout"
|
|
898
921
|
);
|
|
899
922
|
onTimeout?.();
|
|
900
923
|
};
|
|
901
924
|
const onError = heartbeatConfig.onError;
|
|
902
925
|
heartbeatHookOptions.onError = (error) => {
|
|
903
|
-
const event =
|
|
926
|
+
const event = new RealtimeErrorEvent("heartbeat-error", {
|
|
927
|
+
cause: error,
|
|
928
|
+
kind: "heartbeat-error"
|
|
929
|
+
});
|
|
904
930
|
applyHeartbeatAction(
|
|
905
931
|
heartbeatConfig.errorAction ?? heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
906
932
|
event,
|
|
@@ -912,18 +938,18 @@ var useWebSocket = (options) => {
|
|
|
912
938
|
const heartbeat = useHeartbeat(
|
|
913
939
|
heartbeatHookOptions
|
|
914
940
|
);
|
|
915
|
-
const commitState = (next) => {
|
|
941
|
+
const commitState = useStableCallback((next) => {
|
|
916
942
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
917
943
|
stateRef.current = resolved;
|
|
918
944
|
setState(resolved);
|
|
919
|
-
};
|
|
920
|
-
const isActiveSocketEvent =
|
|
945
|
+
});
|
|
946
|
+
const isActiveSocketEvent = useStableCallback((socketEpoch) => {
|
|
921
947
|
return activeSocketEpochRef.current === socketEpoch;
|
|
922
948
|
});
|
|
923
|
-
const shouldHandleSocketClose =
|
|
949
|
+
const shouldHandleSocketClose = useStableCallback((socketEpoch) => {
|
|
924
950
|
return activeSocketEpochRef.current === socketEpoch || closingSocketEpochRef.current === socketEpoch;
|
|
925
951
|
});
|
|
926
|
-
const closeSocket =
|
|
952
|
+
const closeSocket = useStableCallback(
|
|
927
953
|
(config = {}) => {
|
|
928
954
|
const socket2 = socketRef.current;
|
|
929
955
|
const socketEpoch = activeSocketEpochRef.current;
|
|
@@ -939,7 +965,7 @@ var useWebSocket = (options) => {
|
|
|
939
965
|
}
|
|
940
966
|
}
|
|
941
967
|
);
|
|
942
|
-
const applyHeartbeatAction =
|
|
968
|
+
const applyHeartbeatAction = useStableCallback(
|
|
943
969
|
(action, error, reconnectTrigger) => {
|
|
944
970
|
heartbeat.stop();
|
|
945
971
|
options.onError?.(error);
|
|
@@ -976,17 +1002,17 @@ var useWebSocket = (options) => {
|
|
|
976
1002
|
closeSocket({ trackClose: true });
|
|
977
1003
|
}
|
|
978
1004
|
);
|
|
979
|
-
const parseMessage =
|
|
1005
|
+
const parseMessage = useStableCallback((event) => {
|
|
980
1006
|
const parser = options.parseMessage ?? defaultParseMessage;
|
|
981
1007
|
return parser(event);
|
|
982
1008
|
});
|
|
983
|
-
const updateBufferedAmount =
|
|
1009
|
+
const updateBufferedAmount = useStableCallback(() => {
|
|
984
1010
|
commitState((current) => ({
|
|
985
1011
|
...current,
|
|
986
1012
|
bufferedAmount: socketRef.current?.bufferedAmount ?? 0
|
|
987
1013
|
}));
|
|
988
1014
|
});
|
|
989
|
-
const handleOpen =
|
|
1015
|
+
const handleOpen = useStableCallback((event, socket2) => {
|
|
990
1016
|
manualCloseRef.current = false;
|
|
991
1017
|
manualOpenRef.current = false;
|
|
992
1018
|
suppressReconnectRef.current = false;
|
|
@@ -1001,7 +1027,7 @@ var useWebSocket = (options) => {
|
|
|
1001
1027
|
}));
|
|
1002
1028
|
options.onOpen?.(event, socket2);
|
|
1003
1029
|
});
|
|
1004
|
-
const handleMessage =
|
|
1030
|
+
const handleMessage = useStableCallback((event) => {
|
|
1005
1031
|
try {
|
|
1006
1032
|
const message = parseMessage(event);
|
|
1007
1033
|
heartbeat.notifyAck(message);
|
|
@@ -1012,8 +1038,11 @@ var useWebSocket = (options) => {
|
|
|
1012
1038
|
lastMessageEvent: event
|
|
1013
1039
|
}));
|
|
1014
1040
|
options.onMessage?.(message, event);
|
|
1015
|
-
} catch {
|
|
1016
|
-
const parseError = new
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1043
|
+
cause: error,
|
|
1044
|
+
kind: "parse-error"
|
|
1045
|
+
});
|
|
1017
1046
|
terminalErrorRef.current = parseError;
|
|
1018
1047
|
manualOpenRef.current = false;
|
|
1019
1048
|
skipCloseReconnectRef.current = true;
|
|
@@ -1034,7 +1063,7 @@ var useWebSocket = (options) => {
|
|
|
1034
1063
|
});
|
|
1035
1064
|
}
|
|
1036
1065
|
});
|
|
1037
|
-
const handleError =
|
|
1066
|
+
const handleError = useStableCallback((event, socketEpoch) => {
|
|
1038
1067
|
if (!isActiveSocketEvent(socketEpoch)) {
|
|
1039
1068
|
return;
|
|
1040
1069
|
}
|
|
@@ -1047,7 +1076,7 @@ var useWebSocket = (options) => {
|
|
|
1047
1076
|
}));
|
|
1048
1077
|
options.onError?.(event);
|
|
1049
1078
|
});
|
|
1050
|
-
const handleClose =
|
|
1079
|
+
const handleClose = useStableCallback((event, socketEpoch) => {
|
|
1051
1080
|
if (!shouldHandleSocketClose(socketEpoch)) {
|
|
1052
1081
|
return;
|
|
1053
1082
|
}
|
|
@@ -1107,15 +1136,15 @@ var useWebSocket = (options) => {
|
|
|
1107
1136
|
suppressReconnectRef.current = false;
|
|
1108
1137
|
}
|
|
1109
1138
|
});
|
|
1110
|
-
const open = () => {
|
|
1139
|
+
const open = useStableCallback(() => {
|
|
1111
1140
|
manualCloseRef.current = false;
|
|
1112
1141
|
manualOpenRef.current = true;
|
|
1113
1142
|
suppressReconnectRef.current = false;
|
|
1114
1143
|
terminalErrorRef.current = null;
|
|
1115
1144
|
reconnect.cancel();
|
|
1116
1145
|
setOpenNonce((current) => current + 1);
|
|
1117
|
-
};
|
|
1118
|
-
const reconnectNow = () => {
|
|
1146
|
+
});
|
|
1147
|
+
const reconnectNow = useStableCallback(() => {
|
|
1119
1148
|
manualCloseRef.current = false;
|
|
1120
1149
|
manualOpenRef.current = true;
|
|
1121
1150
|
skipCloseReconnectRef.current = true;
|
|
@@ -1125,8 +1154,8 @@ var useWebSocket = (options) => {
|
|
|
1125
1154
|
closeSocket();
|
|
1126
1155
|
suppressReconnectRef.current = false;
|
|
1127
1156
|
reconnect.schedule("manual");
|
|
1128
|
-
};
|
|
1129
|
-
const close = (code, reason) => {
|
|
1157
|
+
});
|
|
1158
|
+
const close = useStableCallback((code, reason) => {
|
|
1130
1159
|
manualCloseRef.current = true;
|
|
1131
1160
|
manualOpenRef.current = false;
|
|
1132
1161
|
suppressReconnectRef.current = true;
|
|
@@ -1143,8 +1172,8 @@ var useWebSocket = (options) => {
|
|
|
1143
1172
|
reason,
|
|
1144
1173
|
trackClose: true
|
|
1145
1174
|
});
|
|
1146
|
-
};
|
|
1147
|
-
const send = (message) => {
|
|
1175
|
+
});
|
|
1176
|
+
const send = useStableCallback((message) => {
|
|
1148
1177
|
const socket2 = socketRef.current;
|
|
1149
1178
|
if (socket2 === null || socket2.readyState !== WebSocket.OPEN) {
|
|
1150
1179
|
return false;
|
|
@@ -1153,7 +1182,7 @@ var useWebSocket = (options) => {
|
|
|
1153
1182
|
socket2.send(serializer(message));
|
|
1154
1183
|
updateBufferedAmount();
|
|
1155
1184
|
return true;
|
|
1156
|
-
};
|
|
1185
|
+
});
|
|
1157
1186
|
react.useEffect(() => {
|
|
1158
1187
|
if (!supported) {
|
|
1159
1188
|
socketKeyRef.current = null;
|
|
@@ -1193,7 +1222,7 @@ var useWebSocket = (options) => {
|
|
|
1193
1222
|
if (socketRef.current !== null) {
|
|
1194
1223
|
return;
|
|
1195
1224
|
}
|
|
1196
|
-
const socket2 = new WebSocket(resolvedUrl,
|
|
1225
|
+
const socket2 = new WebSocket(resolvedUrl, protocols);
|
|
1197
1226
|
const socketEpoch = nextSocketEpochRef.current + 1;
|
|
1198
1227
|
socketRef.current = socket2;
|
|
1199
1228
|
socketKeyRef.current = nextSocketKey;
|
|
@@ -1236,9 +1265,17 @@ var useWebSocket = (options) => {
|
|
|
1236
1265
|
socket2.removeEventListener("close", handleSocketClose);
|
|
1237
1266
|
};
|
|
1238
1267
|
}, [
|
|
1268
|
+
closeSocket,
|
|
1269
|
+
commitState,
|
|
1239
1270
|
connect,
|
|
1271
|
+
handleClose,
|
|
1272
|
+
handleError,
|
|
1273
|
+
handleMessage,
|
|
1274
|
+
handleOpen,
|
|
1275
|
+
isActiveSocketEvent,
|
|
1240
1276
|
openNonce,
|
|
1241
1277
|
options.binaryType,
|
|
1278
|
+
protocols,
|
|
1242
1279
|
protocolsDependency,
|
|
1243
1280
|
reconnect.status,
|
|
1244
1281
|
resolvedUrl,
|
|
@@ -1259,11 +1296,12 @@ var useWebSocket = (options) => {
|
|
|
1259
1296
|
socket2.close();
|
|
1260
1297
|
}
|
|
1261
1298
|
}, []);
|
|
1299
|
+
const stopHeartbeat = heartbeat.stop;
|
|
1262
1300
|
react.useEffect(() => {
|
|
1263
1301
|
if (state.status !== "open") {
|
|
1264
|
-
|
|
1302
|
+
stopHeartbeat();
|
|
1265
1303
|
}
|
|
1266
|
-
}, [state.status]);
|
|
1304
|
+
}, [state.status, stopHeartbeat]);
|
|
1267
1305
|
const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
|
|
1268
1306
|
const snapshot = createConnectionStateSnapshot(status, {
|
|
1269
1307
|
isSupported: supported,
|
|
@@ -1319,29 +1357,27 @@ var createInitialState4 = (status = "idle") => ({
|
|
|
1319
1357
|
status
|
|
1320
1358
|
});
|
|
1321
1359
|
var defaultParseMessage2 = (event) => event.data;
|
|
1322
|
-
var toEventsDependency = (events) => {
|
|
1323
|
-
if (events === void 0 || events.length === 0) {
|
|
1324
|
-
return "";
|
|
1325
|
-
}
|
|
1326
|
-
return [...new Set(events)].sort().join("|");
|
|
1327
|
-
};
|
|
1328
1360
|
var normalizeNamedEvents = (events) => {
|
|
1329
1361
|
if (events === void 0 || events.length === 0) {
|
|
1330
1362
|
return [];
|
|
1331
1363
|
}
|
|
1332
|
-
return [...new Set(events)].filter((eventName) => eventName !== "message");
|
|
1364
|
+
return [...new Set(events)].filter((eventName) => eventName !== "message").sort();
|
|
1333
1365
|
};
|
|
1366
|
+
var toEventsDependency = (events) => JSON.stringify(normalizeNamedEvents(events));
|
|
1367
|
+
var parseEventsDependency = (eventsDependency) => JSON.parse(eventsDependency);
|
|
1334
1368
|
var useEventSource = (options) => {
|
|
1335
1369
|
const connect = options.connect ?? true;
|
|
1336
1370
|
const supported = isEventSourceSupported();
|
|
1337
1371
|
const resolvedUrl = react.useMemo(() => resolveUrlProvider(options.url), [options.url]);
|
|
1338
1372
|
const eventsDependency = toEventsDependency(options.events);
|
|
1339
1373
|
const namedEvents = react.useMemo(
|
|
1340
|
-
() =>
|
|
1374
|
+
() => parseEventsDependency(eventsDependency),
|
|
1341
1375
|
[eventsDependency]
|
|
1342
1376
|
);
|
|
1343
1377
|
const eventSourceRef = react.useRef(null);
|
|
1344
1378
|
const eventSourceKeyRef = react.useRef(null);
|
|
1379
|
+
const activeEventSourceEpochRef = react.useRef(null);
|
|
1380
|
+
const nextEventSourceEpochRef = react.useRef(0);
|
|
1345
1381
|
const manualCloseRef = react.useRef(false);
|
|
1346
1382
|
const manualOpenRef = react.useRef(false);
|
|
1347
1383
|
const skipErrorReconnectRef = react.useRef(false);
|
|
@@ -1360,25 +1396,29 @@ var useEventSource = (options) => {
|
|
|
1360
1396
|
enabled: reconnectEnabled && (options.reconnect?.enabled ?? true)
|
|
1361
1397
|
}
|
|
1362
1398
|
);
|
|
1363
|
-
const commitState = (next) => {
|
|
1399
|
+
const commitState = useStableCallback((next) => {
|
|
1364
1400
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
1365
1401
|
stateRef.current = resolved;
|
|
1366
1402
|
setState(resolved);
|
|
1367
|
-
};
|
|
1368
|
-
const closeEventSource =
|
|
1403
|
+
});
|
|
1404
|
+
const closeEventSource = useStableCallback(() => {
|
|
1369
1405
|
const source = eventSourceRef.current;
|
|
1370
1406
|
if (source === null) {
|
|
1371
1407
|
return;
|
|
1372
1408
|
}
|
|
1373
1409
|
eventSourceRef.current = null;
|
|
1374
1410
|
eventSourceKeyRef.current = null;
|
|
1411
|
+
activeEventSourceEpochRef.current = null;
|
|
1375
1412
|
source.close();
|
|
1376
1413
|
});
|
|
1377
|
-
const
|
|
1414
|
+
const isActiveEventSourceEvent = useStableCallback((sourceEpoch) => {
|
|
1415
|
+
return activeEventSourceEpochRef.current === sourceEpoch;
|
|
1416
|
+
});
|
|
1417
|
+
const parseMessage = useStableCallback((event) => {
|
|
1378
1418
|
const parser = options.parseMessage ?? defaultParseMessage2;
|
|
1379
1419
|
return parser(event);
|
|
1380
1420
|
});
|
|
1381
|
-
const handleOpen =
|
|
1421
|
+
const handleOpen = useStableCallback((event, source) => {
|
|
1382
1422
|
manualCloseRef.current = false;
|
|
1383
1423
|
manualOpenRef.current = false;
|
|
1384
1424
|
suppressReconnectRef.current = false;
|
|
@@ -1391,7 +1431,7 @@ var useEventSource = (options) => {
|
|
|
1391
1431
|
}));
|
|
1392
1432
|
options.onOpen?.(event, source);
|
|
1393
1433
|
});
|
|
1394
|
-
const commitParsedMessage =
|
|
1434
|
+
const commitParsedMessage = useStableCallback(
|
|
1395
1435
|
(eventName, event, isNamedEvent) => {
|
|
1396
1436
|
try {
|
|
1397
1437
|
const message = parseMessage(event);
|
|
@@ -1406,8 +1446,11 @@ var useEventSource = (options) => {
|
|
|
1406
1446
|
return;
|
|
1407
1447
|
}
|
|
1408
1448
|
options.onMessage?.(message, event);
|
|
1409
|
-
} catch {
|
|
1410
|
-
const parseError = new
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1451
|
+
cause: error,
|
|
1452
|
+
kind: "parse-error"
|
|
1453
|
+
});
|
|
1411
1454
|
terminalErrorRef.current = parseError;
|
|
1412
1455
|
manualOpenRef.current = false;
|
|
1413
1456
|
suppressReconnectRef.current = true;
|
|
@@ -1423,7 +1466,10 @@ var useEventSource = (options) => {
|
|
|
1423
1466
|
}
|
|
1424
1467
|
}
|
|
1425
1468
|
);
|
|
1426
|
-
const handleError =
|
|
1469
|
+
const handleError = useStableCallback((event, source, sourceEpoch) => {
|
|
1470
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1427
1473
|
const terminalError = terminalErrorRef.current;
|
|
1428
1474
|
if (terminalError !== null) {
|
|
1429
1475
|
suppressReconnectRef.current = false;
|
|
@@ -1438,12 +1484,11 @@ var useEventSource = (options) => {
|
|
|
1438
1484
|
const skipErrorReconnect = skipErrorReconnectRef.current;
|
|
1439
1485
|
skipErrorReconnectRef.current = false;
|
|
1440
1486
|
const shouldReconnect = !suppressReconnectRef.current && !skipErrorReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
1441
|
-
const readyState = source.readyState;
|
|
1442
1487
|
commitState((current) => ({
|
|
1443
1488
|
...current,
|
|
1444
1489
|
lastChangedAt: Date.now(),
|
|
1445
1490
|
lastError: event,
|
|
1446
|
-
status:
|
|
1491
|
+
status: shouldReconnect ? "reconnecting" : "closed"
|
|
1447
1492
|
}));
|
|
1448
1493
|
options.onError?.(event);
|
|
1449
1494
|
if (!shouldReconnect) {
|
|
@@ -1451,21 +1496,18 @@ var useEventSource = (options) => {
|
|
|
1451
1496
|
closeEventSource();
|
|
1452
1497
|
return;
|
|
1453
1498
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
eventSourceKeyRef.current = null;
|
|
1457
|
-
reconnect.schedule("error");
|
|
1458
|
-
}
|
|
1499
|
+
closeEventSource();
|
|
1500
|
+
reconnect.schedule("error");
|
|
1459
1501
|
});
|
|
1460
|
-
const open = () => {
|
|
1502
|
+
const open = useStableCallback(() => {
|
|
1461
1503
|
manualCloseRef.current = false;
|
|
1462
1504
|
manualOpenRef.current = true;
|
|
1463
1505
|
suppressReconnectRef.current = false;
|
|
1464
1506
|
terminalErrorRef.current = null;
|
|
1465
1507
|
reconnect.cancel();
|
|
1466
1508
|
setOpenNonce((current) => current + 1);
|
|
1467
|
-
};
|
|
1468
|
-
const reconnectNow = () => {
|
|
1509
|
+
});
|
|
1510
|
+
const reconnectNow = useStableCallback(() => {
|
|
1469
1511
|
manualCloseRef.current = false;
|
|
1470
1512
|
manualOpenRef.current = true;
|
|
1471
1513
|
skipErrorReconnectRef.current = true;
|
|
@@ -1474,8 +1516,8 @@ var useEventSource = (options) => {
|
|
|
1474
1516
|
closeEventSource();
|
|
1475
1517
|
suppressReconnectRef.current = false;
|
|
1476
1518
|
reconnect.schedule("manual");
|
|
1477
|
-
};
|
|
1478
|
-
const close = () => {
|
|
1519
|
+
});
|
|
1520
|
+
const close = useStableCallback(() => {
|
|
1479
1521
|
manualCloseRef.current = true;
|
|
1480
1522
|
manualOpenRef.current = false;
|
|
1481
1523
|
suppressReconnectRef.current = true;
|
|
@@ -1487,7 +1529,7 @@ var useEventSource = (options) => {
|
|
|
1487
1529
|
lastChangedAt: Date.now(),
|
|
1488
1530
|
status: "closed"
|
|
1489
1531
|
}));
|
|
1490
|
-
};
|
|
1532
|
+
});
|
|
1491
1533
|
react.useEffect(() => {
|
|
1492
1534
|
if (!supported) {
|
|
1493
1535
|
eventSourceKeyRef.current = null;
|
|
@@ -1506,7 +1548,7 @@ var useEventSource = (options) => {
|
|
|
1506
1548
|
}));
|
|
1507
1549
|
return;
|
|
1508
1550
|
}
|
|
1509
|
-
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));
|
|
1510
1552
|
const nextEventSourceKey = [
|
|
1511
1553
|
resolvedUrl,
|
|
1512
1554
|
options.withCredentials ? "credentials" : "anonymous",
|
|
@@ -1534,27 +1576,39 @@ var useEventSource = (options) => {
|
|
|
1534
1576
|
const source = new EventSource(resolvedUrl, {
|
|
1535
1577
|
withCredentials: options.withCredentials ?? false
|
|
1536
1578
|
});
|
|
1579
|
+
const sourceEpoch = nextEventSourceEpochRef.current + 1;
|
|
1537
1580
|
eventSourceRef.current = source;
|
|
1538
1581
|
eventSourceKeyRef.current = nextEventSourceKey;
|
|
1582
|
+
activeEventSourceEpochRef.current = sourceEpoch;
|
|
1583
|
+
nextEventSourceEpochRef.current = sourceEpoch;
|
|
1539
1584
|
commitState((current) => ({
|
|
1540
1585
|
...current,
|
|
1541
1586
|
lastChangedAt: Date.now(),
|
|
1542
1587
|
status: reconnect.status === "running" || reconnect.status === "scheduled" ? "reconnecting" : "connecting"
|
|
1543
1588
|
}));
|
|
1544
1589
|
const handleSourceOpen = (event) => {
|
|
1590
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1545
1593
|
handleOpen(event, source);
|
|
1546
1594
|
};
|
|
1547
1595
|
const handleSourceMessage = (event) => {
|
|
1596
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1548
1599
|
commitParsedMessage("message", event, false);
|
|
1549
1600
|
};
|
|
1550
1601
|
const namedEventHandlers = /* @__PURE__ */ new Map();
|
|
1551
1602
|
const handleSourceError = (event) => {
|
|
1552
|
-
handleError(event, source);
|
|
1603
|
+
handleError(event, source, sourceEpoch);
|
|
1553
1604
|
};
|
|
1554
1605
|
source.addEventListener("open", handleSourceOpen);
|
|
1555
1606
|
source.addEventListener("message", handleSourceMessage);
|
|
1556
1607
|
for (const eventName of namedEvents) {
|
|
1557
1608
|
const handler = (event) => {
|
|
1609
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1558
1612
|
commitParsedMessage(eventName, event, true);
|
|
1559
1613
|
};
|
|
1560
1614
|
namedEventHandlers.set(eventName, handler);
|
|
@@ -1570,8 +1624,14 @@ var useEventSource = (options) => {
|
|
|
1570
1624
|
source.removeEventListener("error", handleSourceError);
|
|
1571
1625
|
};
|
|
1572
1626
|
}, [
|
|
1627
|
+
closeEventSource,
|
|
1628
|
+
commitParsedMessage,
|
|
1629
|
+
commitState,
|
|
1573
1630
|
connect,
|
|
1574
1631
|
eventsDependency,
|
|
1632
|
+
handleError,
|
|
1633
|
+
handleOpen,
|
|
1634
|
+
isActiveEventSourceEvent,
|
|
1575
1635
|
namedEvents,
|
|
1576
1636
|
openNonce,
|
|
1577
1637
|
options.withCredentials,
|
|
@@ -1582,6 +1642,7 @@ var useEventSource = (options) => {
|
|
|
1582
1642
|
react.useEffect(() => () => {
|
|
1583
1643
|
suppressReconnectRef.current = true;
|
|
1584
1644
|
eventSourceKeyRef.current = null;
|
|
1645
|
+
activeEventSourceEpochRef.current = null;
|
|
1585
1646
|
terminalErrorRef.current = null;
|
|
1586
1647
|
const source = eventSourceRef.current;
|
|
1587
1648
|
eventSourceRef.current = null;
|
|
@@ -1626,6 +1687,7 @@ var useEventSource = (options) => {
|
|
|
1626
1687
|
};
|
|
1627
1688
|
};
|
|
1628
1689
|
|
|
1690
|
+
exports.RealtimeErrorEvent = RealtimeErrorEvent;
|
|
1629
1691
|
exports.useConnectionGate = useConnectionGate;
|
|
1630
1692
|
exports.useEventSource = useEventSource;
|
|
1631
1693
|
exports.useHeartbeat = useHeartbeat;
|