react-realtime-hooks 1.2.1 → 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 +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.d.ts
CHANGED
|
@@ -263,4 +263,15 @@ type UseEventSourceHook = <TMessage = unknown>(options: UseEventSourceOptions<TM
|
|
|
263
263
|
|
|
264
264
|
declare const useEventSource: UseEventSourceHook;
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
type RealtimeErrorKind = "heartbeat-error" | "heartbeat-timeout" | "parse-error";
|
|
267
|
+
interface RealtimeErrorEventInit extends EventInit {
|
|
268
|
+
cause?: unknown;
|
|
269
|
+
kind: RealtimeErrorKind;
|
|
270
|
+
}
|
|
271
|
+
declare class RealtimeErrorEvent extends Event {
|
|
272
|
+
readonly cause: unknown;
|
|
273
|
+
readonly kind: RealtimeErrorKind;
|
|
274
|
+
constructor(type: string, init: RealtimeErrorEventInit);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export { type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, RealtimeErrorEvent, type RealtimeErrorEventInit, type RealtimeErrorKind, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseConnectionGateHook, type UseConnectionGateOptions, type UseConnectionGateResult, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UsePageVisibilityHook, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
|
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,6 +839,10 @@ 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);
|
|
827
848
|
const activeSocketEpochRef = useRef(null);
|
|
@@ -891,14 +912,19 @@ var useWebSocket = (options) => {
|
|
|
891
912
|
heartbeatHookOptions.onTimeout = () => {
|
|
892
913
|
applyHeartbeatAction(
|
|
893
914
|
heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
894
|
-
new
|
|
915
|
+
new RealtimeErrorEvent("heartbeat-timeout", {
|
|
916
|
+
kind: "heartbeat-timeout"
|
|
917
|
+
}),
|
|
895
918
|
"heartbeat-timeout"
|
|
896
919
|
);
|
|
897
920
|
onTimeout?.();
|
|
898
921
|
};
|
|
899
922
|
const onError = heartbeatConfig.onError;
|
|
900
923
|
heartbeatHookOptions.onError = (error) => {
|
|
901
|
-
const event =
|
|
924
|
+
const event = new RealtimeErrorEvent("heartbeat-error", {
|
|
925
|
+
cause: error,
|
|
926
|
+
kind: "heartbeat-error"
|
|
927
|
+
});
|
|
902
928
|
applyHeartbeatAction(
|
|
903
929
|
heartbeatConfig.errorAction ?? heartbeatConfig.timeoutAction ?? defaultHeartbeatAction,
|
|
904
930
|
event,
|
|
@@ -910,18 +936,18 @@ var useWebSocket = (options) => {
|
|
|
910
936
|
const heartbeat = useHeartbeat(
|
|
911
937
|
heartbeatHookOptions
|
|
912
938
|
);
|
|
913
|
-
const commitState = (next) => {
|
|
939
|
+
const commitState = useStableCallback((next) => {
|
|
914
940
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
915
941
|
stateRef.current = resolved;
|
|
916
942
|
setState(resolved);
|
|
917
|
-
};
|
|
918
|
-
const isActiveSocketEvent =
|
|
943
|
+
});
|
|
944
|
+
const isActiveSocketEvent = useStableCallback((socketEpoch) => {
|
|
919
945
|
return activeSocketEpochRef.current === socketEpoch;
|
|
920
946
|
});
|
|
921
|
-
const shouldHandleSocketClose =
|
|
947
|
+
const shouldHandleSocketClose = useStableCallback((socketEpoch) => {
|
|
922
948
|
return activeSocketEpochRef.current === socketEpoch || closingSocketEpochRef.current === socketEpoch;
|
|
923
949
|
});
|
|
924
|
-
const closeSocket =
|
|
950
|
+
const closeSocket = useStableCallback(
|
|
925
951
|
(config = {}) => {
|
|
926
952
|
const socket2 = socketRef.current;
|
|
927
953
|
const socketEpoch = activeSocketEpochRef.current;
|
|
@@ -937,7 +963,7 @@ var useWebSocket = (options) => {
|
|
|
937
963
|
}
|
|
938
964
|
}
|
|
939
965
|
);
|
|
940
|
-
const applyHeartbeatAction =
|
|
966
|
+
const applyHeartbeatAction = useStableCallback(
|
|
941
967
|
(action, error, reconnectTrigger) => {
|
|
942
968
|
heartbeat.stop();
|
|
943
969
|
options.onError?.(error);
|
|
@@ -974,17 +1000,17 @@ var useWebSocket = (options) => {
|
|
|
974
1000
|
closeSocket({ trackClose: true });
|
|
975
1001
|
}
|
|
976
1002
|
);
|
|
977
|
-
const parseMessage =
|
|
1003
|
+
const parseMessage = useStableCallback((event) => {
|
|
978
1004
|
const parser = options.parseMessage ?? defaultParseMessage;
|
|
979
1005
|
return parser(event);
|
|
980
1006
|
});
|
|
981
|
-
const updateBufferedAmount =
|
|
1007
|
+
const updateBufferedAmount = useStableCallback(() => {
|
|
982
1008
|
commitState((current) => ({
|
|
983
1009
|
...current,
|
|
984
1010
|
bufferedAmount: socketRef.current?.bufferedAmount ?? 0
|
|
985
1011
|
}));
|
|
986
1012
|
});
|
|
987
|
-
const handleOpen =
|
|
1013
|
+
const handleOpen = useStableCallback((event, socket2) => {
|
|
988
1014
|
manualCloseRef.current = false;
|
|
989
1015
|
manualOpenRef.current = false;
|
|
990
1016
|
suppressReconnectRef.current = false;
|
|
@@ -999,7 +1025,7 @@ var useWebSocket = (options) => {
|
|
|
999
1025
|
}));
|
|
1000
1026
|
options.onOpen?.(event, socket2);
|
|
1001
1027
|
});
|
|
1002
|
-
const handleMessage =
|
|
1028
|
+
const handleMessage = useStableCallback((event) => {
|
|
1003
1029
|
try {
|
|
1004
1030
|
const message = parseMessage(event);
|
|
1005
1031
|
heartbeat.notifyAck(message);
|
|
@@ -1010,8 +1036,11 @@ var useWebSocket = (options) => {
|
|
|
1010
1036
|
lastMessageEvent: event
|
|
1011
1037
|
}));
|
|
1012
1038
|
options.onMessage?.(message, event);
|
|
1013
|
-
} catch {
|
|
1014
|
-
const parseError = new
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1041
|
+
cause: error,
|
|
1042
|
+
kind: "parse-error"
|
|
1043
|
+
});
|
|
1015
1044
|
terminalErrorRef.current = parseError;
|
|
1016
1045
|
manualOpenRef.current = false;
|
|
1017
1046
|
skipCloseReconnectRef.current = true;
|
|
@@ -1032,7 +1061,7 @@ var useWebSocket = (options) => {
|
|
|
1032
1061
|
});
|
|
1033
1062
|
}
|
|
1034
1063
|
});
|
|
1035
|
-
const handleError =
|
|
1064
|
+
const handleError = useStableCallback((event, socketEpoch) => {
|
|
1036
1065
|
if (!isActiveSocketEvent(socketEpoch)) {
|
|
1037
1066
|
return;
|
|
1038
1067
|
}
|
|
@@ -1045,7 +1074,7 @@ var useWebSocket = (options) => {
|
|
|
1045
1074
|
}));
|
|
1046
1075
|
options.onError?.(event);
|
|
1047
1076
|
});
|
|
1048
|
-
const handleClose =
|
|
1077
|
+
const handleClose = useStableCallback((event, socketEpoch) => {
|
|
1049
1078
|
if (!shouldHandleSocketClose(socketEpoch)) {
|
|
1050
1079
|
return;
|
|
1051
1080
|
}
|
|
@@ -1105,15 +1134,15 @@ var useWebSocket = (options) => {
|
|
|
1105
1134
|
suppressReconnectRef.current = false;
|
|
1106
1135
|
}
|
|
1107
1136
|
});
|
|
1108
|
-
const open = () => {
|
|
1137
|
+
const open = useStableCallback(() => {
|
|
1109
1138
|
manualCloseRef.current = false;
|
|
1110
1139
|
manualOpenRef.current = true;
|
|
1111
1140
|
suppressReconnectRef.current = false;
|
|
1112
1141
|
terminalErrorRef.current = null;
|
|
1113
1142
|
reconnect.cancel();
|
|
1114
1143
|
setOpenNonce((current) => current + 1);
|
|
1115
|
-
};
|
|
1116
|
-
const reconnectNow = () => {
|
|
1144
|
+
});
|
|
1145
|
+
const reconnectNow = useStableCallback(() => {
|
|
1117
1146
|
manualCloseRef.current = false;
|
|
1118
1147
|
manualOpenRef.current = true;
|
|
1119
1148
|
skipCloseReconnectRef.current = true;
|
|
@@ -1123,8 +1152,8 @@ var useWebSocket = (options) => {
|
|
|
1123
1152
|
closeSocket();
|
|
1124
1153
|
suppressReconnectRef.current = false;
|
|
1125
1154
|
reconnect.schedule("manual");
|
|
1126
|
-
};
|
|
1127
|
-
const close = (code, reason) => {
|
|
1155
|
+
});
|
|
1156
|
+
const close = useStableCallback((code, reason) => {
|
|
1128
1157
|
manualCloseRef.current = true;
|
|
1129
1158
|
manualOpenRef.current = false;
|
|
1130
1159
|
suppressReconnectRef.current = true;
|
|
@@ -1141,8 +1170,8 @@ var useWebSocket = (options) => {
|
|
|
1141
1170
|
reason,
|
|
1142
1171
|
trackClose: true
|
|
1143
1172
|
});
|
|
1144
|
-
};
|
|
1145
|
-
const send = (message) => {
|
|
1173
|
+
});
|
|
1174
|
+
const send = useStableCallback((message) => {
|
|
1146
1175
|
const socket2 = socketRef.current;
|
|
1147
1176
|
if (socket2 === null || socket2.readyState !== WebSocket.OPEN) {
|
|
1148
1177
|
return false;
|
|
@@ -1151,7 +1180,7 @@ var useWebSocket = (options) => {
|
|
|
1151
1180
|
socket2.send(serializer(message));
|
|
1152
1181
|
updateBufferedAmount();
|
|
1153
1182
|
return true;
|
|
1154
|
-
};
|
|
1183
|
+
});
|
|
1155
1184
|
useEffect(() => {
|
|
1156
1185
|
if (!supported) {
|
|
1157
1186
|
socketKeyRef.current = null;
|
|
@@ -1191,7 +1220,7 @@ var useWebSocket = (options) => {
|
|
|
1191
1220
|
if (socketRef.current !== null) {
|
|
1192
1221
|
return;
|
|
1193
1222
|
}
|
|
1194
|
-
const socket2 = new WebSocket(resolvedUrl,
|
|
1223
|
+
const socket2 = new WebSocket(resolvedUrl, protocols);
|
|
1195
1224
|
const socketEpoch = nextSocketEpochRef.current + 1;
|
|
1196
1225
|
socketRef.current = socket2;
|
|
1197
1226
|
socketKeyRef.current = nextSocketKey;
|
|
@@ -1234,9 +1263,17 @@ var useWebSocket = (options) => {
|
|
|
1234
1263
|
socket2.removeEventListener("close", handleSocketClose);
|
|
1235
1264
|
};
|
|
1236
1265
|
}, [
|
|
1266
|
+
closeSocket,
|
|
1267
|
+
commitState,
|
|
1237
1268
|
connect,
|
|
1269
|
+
handleClose,
|
|
1270
|
+
handleError,
|
|
1271
|
+
handleMessage,
|
|
1272
|
+
handleOpen,
|
|
1273
|
+
isActiveSocketEvent,
|
|
1238
1274
|
openNonce,
|
|
1239
1275
|
options.binaryType,
|
|
1276
|
+
protocols,
|
|
1240
1277
|
protocolsDependency,
|
|
1241
1278
|
reconnect.status,
|
|
1242
1279
|
resolvedUrl,
|
|
@@ -1257,11 +1294,12 @@ var useWebSocket = (options) => {
|
|
|
1257
1294
|
socket2.close();
|
|
1258
1295
|
}
|
|
1259
1296
|
}, []);
|
|
1297
|
+
const stopHeartbeat = heartbeat.stop;
|
|
1260
1298
|
useEffect(() => {
|
|
1261
1299
|
if (state.status !== "open") {
|
|
1262
|
-
|
|
1300
|
+
stopHeartbeat();
|
|
1263
1301
|
}
|
|
1264
|
-
}, [state.status]);
|
|
1302
|
+
}, [state.status, stopHeartbeat]);
|
|
1265
1303
|
const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
|
|
1266
1304
|
const snapshot = createConnectionStateSnapshot(status, {
|
|
1267
1305
|
isSupported: supported,
|
|
@@ -1317,29 +1355,27 @@ var createInitialState4 = (status = "idle") => ({
|
|
|
1317
1355
|
status
|
|
1318
1356
|
});
|
|
1319
1357
|
var defaultParseMessage2 = (event) => event.data;
|
|
1320
|
-
var toEventsDependency = (events) => {
|
|
1321
|
-
if (events === void 0 || events.length === 0) {
|
|
1322
|
-
return "";
|
|
1323
|
-
}
|
|
1324
|
-
return [...new Set(events)].sort().join("|");
|
|
1325
|
-
};
|
|
1326
1358
|
var normalizeNamedEvents = (events) => {
|
|
1327
1359
|
if (events === void 0 || events.length === 0) {
|
|
1328
1360
|
return [];
|
|
1329
1361
|
}
|
|
1330
|
-
return [...new Set(events)].filter((eventName) => eventName !== "message");
|
|
1362
|
+
return [...new Set(events)].filter((eventName) => eventName !== "message").sort();
|
|
1331
1363
|
};
|
|
1364
|
+
var toEventsDependency = (events) => JSON.stringify(normalizeNamedEvents(events));
|
|
1365
|
+
var parseEventsDependency = (eventsDependency) => JSON.parse(eventsDependency);
|
|
1332
1366
|
var useEventSource = (options) => {
|
|
1333
1367
|
const connect = options.connect ?? true;
|
|
1334
1368
|
const supported = isEventSourceSupported();
|
|
1335
1369
|
const resolvedUrl = useMemo(() => resolveUrlProvider(options.url), [options.url]);
|
|
1336
1370
|
const eventsDependency = toEventsDependency(options.events);
|
|
1337
1371
|
const namedEvents = useMemo(
|
|
1338
|
-
() =>
|
|
1372
|
+
() => parseEventsDependency(eventsDependency),
|
|
1339
1373
|
[eventsDependency]
|
|
1340
1374
|
);
|
|
1341
1375
|
const eventSourceRef = useRef(null);
|
|
1342
1376
|
const eventSourceKeyRef = useRef(null);
|
|
1377
|
+
const activeEventSourceEpochRef = useRef(null);
|
|
1378
|
+
const nextEventSourceEpochRef = useRef(0);
|
|
1343
1379
|
const manualCloseRef = useRef(false);
|
|
1344
1380
|
const manualOpenRef = useRef(false);
|
|
1345
1381
|
const skipErrorReconnectRef = useRef(false);
|
|
@@ -1358,25 +1394,29 @@ var useEventSource = (options) => {
|
|
|
1358
1394
|
enabled: reconnectEnabled && (options.reconnect?.enabled ?? true)
|
|
1359
1395
|
}
|
|
1360
1396
|
);
|
|
1361
|
-
const commitState = (next) => {
|
|
1397
|
+
const commitState = useStableCallback((next) => {
|
|
1362
1398
|
const resolved = typeof next === "function" ? next(stateRef.current) : next;
|
|
1363
1399
|
stateRef.current = resolved;
|
|
1364
1400
|
setState(resolved);
|
|
1365
|
-
};
|
|
1366
|
-
const closeEventSource =
|
|
1401
|
+
});
|
|
1402
|
+
const closeEventSource = useStableCallback(() => {
|
|
1367
1403
|
const source = eventSourceRef.current;
|
|
1368
1404
|
if (source === null) {
|
|
1369
1405
|
return;
|
|
1370
1406
|
}
|
|
1371
1407
|
eventSourceRef.current = null;
|
|
1372
1408
|
eventSourceKeyRef.current = null;
|
|
1409
|
+
activeEventSourceEpochRef.current = null;
|
|
1373
1410
|
source.close();
|
|
1374
1411
|
});
|
|
1375
|
-
const
|
|
1412
|
+
const isActiveEventSourceEvent = useStableCallback((sourceEpoch) => {
|
|
1413
|
+
return activeEventSourceEpochRef.current === sourceEpoch;
|
|
1414
|
+
});
|
|
1415
|
+
const parseMessage = useStableCallback((event) => {
|
|
1376
1416
|
const parser = options.parseMessage ?? defaultParseMessage2;
|
|
1377
1417
|
return parser(event);
|
|
1378
1418
|
});
|
|
1379
|
-
const handleOpen =
|
|
1419
|
+
const handleOpen = useStableCallback((event, source) => {
|
|
1380
1420
|
manualCloseRef.current = false;
|
|
1381
1421
|
manualOpenRef.current = false;
|
|
1382
1422
|
suppressReconnectRef.current = false;
|
|
@@ -1389,7 +1429,7 @@ var useEventSource = (options) => {
|
|
|
1389
1429
|
}));
|
|
1390
1430
|
options.onOpen?.(event, source);
|
|
1391
1431
|
});
|
|
1392
|
-
const commitParsedMessage =
|
|
1432
|
+
const commitParsedMessage = useStableCallback(
|
|
1393
1433
|
(eventName, event, isNamedEvent) => {
|
|
1394
1434
|
try {
|
|
1395
1435
|
const message = parseMessage(event);
|
|
@@ -1404,8 +1444,11 @@ var useEventSource = (options) => {
|
|
|
1404
1444
|
return;
|
|
1405
1445
|
}
|
|
1406
1446
|
options.onMessage?.(message, event);
|
|
1407
|
-
} catch {
|
|
1408
|
-
const parseError = new
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
const parseError = new RealtimeErrorEvent("error", {
|
|
1449
|
+
cause: error,
|
|
1450
|
+
kind: "parse-error"
|
|
1451
|
+
});
|
|
1409
1452
|
terminalErrorRef.current = parseError;
|
|
1410
1453
|
manualOpenRef.current = false;
|
|
1411
1454
|
suppressReconnectRef.current = true;
|
|
@@ -1421,7 +1464,10 @@ var useEventSource = (options) => {
|
|
|
1421
1464
|
}
|
|
1422
1465
|
}
|
|
1423
1466
|
);
|
|
1424
|
-
const handleError =
|
|
1467
|
+
const handleError = useStableCallback((event, source, sourceEpoch) => {
|
|
1468
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1425
1471
|
const terminalError = terminalErrorRef.current;
|
|
1426
1472
|
if (terminalError !== null) {
|
|
1427
1473
|
suppressReconnectRef.current = false;
|
|
@@ -1436,12 +1482,11 @@ var useEventSource = (options) => {
|
|
|
1436
1482
|
const skipErrorReconnect = skipErrorReconnectRef.current;
|
|
1437
1483
|
skipErrorReconnectRef.current = false;
|
|
1438
1484
|
const shouldReconnect = !suppressReconnectRef.current && !skipErrorReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
1439
|
-
const readyState = source.readyState;
|
|
1440
1485
|
commitState((current) => ({
|
|
1441
1486
|
...current,
|
|
1442
1487
|
lastChangedAt: Date.now(),
|
|
1443
1488
|
lastError: event,
|
|
1444
|
-
status:
|
|
1489
|
+
status: shouldReconnect ? "reconnecting" : "closed"
|
|
1445
1490
|
}));
|
|
1446
1491
|
options.onError?.(event);
|
|
1447
1492
|
if (!shouldReconnect) {
|
|
@@ -1449,21 +1494,18 @@ var useEventSource = (options) => {
|
|
|
1449
1494
|
closeEventSource();
|
|
1450
1495
|
return;
|
|
1451
1496
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
eventSourceKeyRef.current = null;
|
|
1455
|
-
reconnect.schedule("error");
|
|
1456
|
-
}
|
|
1497
|
+
closeEventSource();
|
|
1498
|
+
reconnect.schedule("error");
|
|
1457
1499
|
});
|
|
1458
|
-
const open = () => {
|
|
1500
|
+
const open = useStableCallback(() => {
|
|
1459
1501
|
manualCloseRef.current = false;
|
|
1460
1502
|
manualOpenRef.current = true;
|
|
1461
1503
|
suppressReconnectRef.current = false;
|
|
1462
1504
|
terminalErrorRef.current = null;
|
|
1463
1505
|
reconnect.cancel();
|
|
1464
1506
|
setOpenNonce((current) => current + 1);
|
|
1465
|
-
};
|
|
1466
|
-
const reconnectNow = () => {
|
|
1507
|
+
});
|
|
1508
|
+
const reconnectNow = useStableCallback(() => {
|
|
1467
1509
|
manualCloseRef.current = false;
|
|
1468
1510
|
manualOpenRef.current = true;
|
|
1469
1511
|
skipErrorReconnectRef.current = true;
|
|
@@ -1472,8 +1514,8 @@ var useEventSource = (options) => {
|
|
|
1472
1514
|
closeEventSource();
|
|
1473
1515
|
suppressReconnectRef.current = false;
|
|
1474
1516
|
reconnect.schedule("manual");
|
|
1475
|
-
};
|
|
1476
|
-
const close = () => {
|
|
1517
|
+
});
|
|
1518
|
+
const close = useStableCallback(() => {
|
|
1477
1519
|
manualCloseRef.current = true;
|
|
1478
1520
|
manualOpenRef.current = false;
|
|
1479
1521
|
suppressReconnectRef.current = true;
|
|
@@ -1485,7 +1527,7 @@ var useEventSource = (options) => {
|
|
|
1485
1527
|
lastChangedAt: Date.now(),
|
|
1486
1528
|
status: "closed"
|
|
1487
1529
|
}));
|
|
1488
|
-
};
|
|
1530
|
+
});
|
|
1489
1531
|
useEffect(() => {
|
|
1490
1532
|
if (!supported) {
|
|
1491
1533
|
eventSourceKeyRef.current = null;
|
|
@@ -1504,7 +1546,7 @@ var useEventSource = (options) => {
|
|
|
1504
1546
|
}));
|
|
1505
1547
|
return;
|
|
1506
1548
|
}
|
|
1507
|
-
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));
|
|
1508
1550
|
const nextEventSourceKey = [
|
|
1509
1551
|
resolvedUrl,
|
|
1510
1552
|
options.withCredentials ? "credentials" : "anonymous",
|
|
@@ -1532,27 +1574,39 @@ var useEventSource = (options) => {
|
|
|
1532
1574
|
const source = new EventSource(resolvedUrl, {
|
|
1533
1575
|
withCredentials: options.withCredentials ?? false
|
|
1534
1576
|
});
|
|
1577
|
+
const sourceEpoch = nextEventSourceEpochRef.current + 1;
|
|
1535
1578
|
eventSourceRef.current = source;
|
|
1536
1579
|
eventSourceKeyRef.current = nextEventSourceKey;
|
|
1580
|
+
activeEventSourceEpochRef.current = sourceEpoch;
|
|
1581
|
+
nextEventSourceEpochRef.current = sourceEpoch;
|
|
1537
1582
|
commitState((current) => ({
|
|
1538
1583
|
...current,
|
|
1539
1584
|
lastChangedAt: Date.now(),
|
|
1540
1585
|
status: reconnect.status === "running" || reconnect.status === "scheduled" ? "reconnecting" : "connecting"
|
|
1541
1586
|
}));
|
|
1542
1587
|
const handleSourceOpen = (event) => {
|
|
1588
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1543
1591
|
handleOpen(event, source);
|
|
1544
1592
|
};
|
|
1545
1593
|
const handleSourceMessage = (event) => {
|
|
1594
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1546
1597
|
commitParsedMessage("message", event, false);
|
|
1547
1598
|
};
|
|
1548
1599
|
const namedEventHandlers = /* @__PURE__ */ new Map();
|
|
1549
1600
|
const handleSourceError = (event) => {
|
|
1550
|
-
handleError(event, source);
|
|
1601
|
+
handleError(event, source, sourceEpoch);
|
|
1551
1602
|
};
|
|
1552
1603
|
source.addEventListener("open", handleSourceOpen);
|
|
1553
1604
|
source.addEventListener("message", handleSourceMessage);
|
|
1554
1605
|
for (const eventName of namedEvents) {
|
|
1555
1606
|
const handler = (event) => {
|
|
1607
|
+
if (!isActiveEventSourceEvent(sourceEpoch)) {
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1556
1610
|
commitParsedMessage(eventName, event, true);
|
|
1557
1611
|
};
|
|
1558
1612
|
namedEventHandlers.set(eventName, handler);
|
|
@@ -1568,8 +1622,14 @@ var useEventSource = (options) => {
|
|
|
1568
1622
|
source.removeEventListener("error", handleSourceError);
|
|
1569
1623
|
};
|
|
1570
1624
|
}, [
|
|
1625
|
+
closeEventSource,
|
|
1626
|
+
commitParsedMessage,
|
|
1627
|
+
commitState,
|
|
1571
1628
|
connect,
|
|
1572
1629
|
eventsDependency,
|
|
1630
|
+
handleError,
|
|
1631
|
+
handleOpen,
|
|
1632
|
+
isActiveEventSourceEvent,
|
|
1573
1633
|
namedEvents,
|
|
1574
1634
|
openNonce,
|
|
1575
1635
|
options.withCredentials,
|
|
@@ -1580,6 +1640,7 @@ var useEventSource = (options) => {
|
|
|
1580
1640
|
useEffect(() => () => {
|
|
1581
1641
|
suppressReconnectRef.current = true;
|
|
1582
1642
|
eventSourceKeyRef.current = null;
|
|
1643
|
+
activeEventSourceEpochRef.current = null;
|
|
1583
1644
|
terminalErrorRef.current = null;
|
|
1584
1645
|
const source = eventSourceRef.current;
|
|
1585
1646
|
eventSourceRef.current = null;
|
|
@@ -1624,6 +1685,6 @@ var useEventSource = (options) => {
|
|
|
1624
1685
|
};
|
|
1625
1686
|
};
|
|
1626
1687
|
|
|
1627
|
-
export { useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
|
|
1688
|
+
export { RealtimeErrorEvent, useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
|
|
1628
1689
|
//# sourceMappingURL=index.js.map
|
|
1629
1690
|
//# sourceMappingURL=index.js.map
|