react-realtime-hooks 1.3.4 → 1.4.1
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 +17 -1
- package/dist/index.cjs +166 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +166 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -168,11 +168,39 @@ interface UseWebSocketHeartbeatOptions<TOutgoing = unknown, TIncoming = TOutgoin
|
|
|
168
168
|
timeoutAction?: WebSocketHeartbeatAction;
|
|
169
169
|
errorAction?: WebSocketHeartbeatAction;
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Controls how the hook surfaces the live `WebSocket.bufferedAmount`
|
|
173
|
+
* value through `state.bufferedAmount`.
|
|
174
|
+
*
|
|
175
|
+
* The native `WebSocket` does not emit any event when buffered bytes
|
|
176
|
+
* drain to the network, so by default `state.bufferedAmount` is only
|
|
177
|
+
* refreshed when the consumer calls `send(...)`, when a message
|
|
178
|
+
* arrives, or when the socket transitions to `open`. For UIs that need
|
|
179
|
+
* to display real-time backpressure (e.g. an outbound queue meter,
|
|
180
|
+
* a "flushing..." indicator, or a flow-control gauge), enable polling.
|
|
181
|
+
*
|
|
182
|
+
* - `false` (default): no polling. `state.bufferedAmount` updates only
|
|
183
|
+
* on the natural lifecycle events listed above.
|
|
184
|
+
* - `true`: poll once every 100ms while the socket is `open`.
|
|
185
|
+
* - `"raf"`: poll on every animation frame while the socket is `open`.
|
|
186
|
+
* Recommended for visual gauges that are tied to a render loop.
|
|
187
|
+
* - `{ intervalMs: N }`: poll every `N` milliseconds. `N` must be a
|
|
188
|
+
* positive integer; values <= 0 disable polling.
|
|
189
|
+
*
|
|
190
|
+
* Polling is automatically suspended when the socket is not in the
|
|
191
|
+
* `open` state and resumed when it returns to `open`. The hook diffs
|
|
192
|
+
* the polled value against the last committed one, so identical
|
|
193
|
+
* readings do not trigger React re-renders.
|
|
194
|
+
*/
|
|
195
|
+
type BufferedAmountPolling = false | true | "raf" | {
|
|
196
|
+
intervalMs: number;
|
|
197
|
+
};
|
|
171
198
|
interface UseWebSocketOptions<TIncoming = unknown, TOutgoing = TIncoming> {
|
|
172
199
|
url: UrlProvider;
|
|
173
200
|
protocols?: string | string[];
|
|
174
201
|
connect?: boolean;
|
|
175
202
|
binaryType?: BinaryType;
|
|
203
|
+
bufferedAmountPolling?: BufferedAmountPolling;
|
|
176
204
|
parseMessage?: MessageParser<TIncoming>;
|
|
177
205
|
serializeMessage?: MessageSerializer<TOutgoing>;
|
|
178
206
|
reconnect?: false | UseReconnectOptions;
|
|
@@ -274,4 +302,4 @@ declare class RealtimeErrorEvent extends Event {
|
|
|
274
302
|
constructor(type: string, init: RealtimeErrorEventInit);
|
|
275
303
|
}
|
|
276
304
|
|
|
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 };
|
|
305
|
+
export { type BufferedAmountPolling, 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.d.ts
CHANGED
|
@@ -168,11 +168,39 @@ interface UseWebSocketHeartbeatOptions<TOutgoing = unknown, TIncoming = TOutgoin
|
|
|
168
168
|
timeoutAction?: WebSocketHeartbeatAction;
|
|
169
169
|
errorAction?: WebSocketHeartbeatAction;
|
|
170
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Controls how the hook surfaces the live `WebSocket.bufferedAmount`
|
|
173
|
+
* value through `state.bufferedAmount`.
|
|
174
|
+
*
|
|
175
|
+
* The native `WebSocket` does not emit any event when buffered bytes
|
|
176
|
+
* drain to the network, so by default `state.bufferedAmount` is only
|
|
177
|
+
* refreshed when the consumer calls `send(...)`, when a message
|
|
178
|
+
* arrives, or when the socket transitions to `open`. For UIs that need
|
|
179
|
+
* to display real-time backpressure (e.g. an outbound queue meter,
|
|
180
|
+
* a "flushing..." indicator, or a flow-control gauge), enable polling.
|
|
181
|
+
*
|
|
182
|
+
* - `false` (default): no polling. `state.bufferedAmount` updates only
|
|
183
|
+
* on the natural lifecycle events listed above.
|
|
184
|
+
* - `true`: poll once every 100ms while the socket is `open`.
|
|
185
|
+
* - `"raf"`: poll on every animation frame while the socket is `open`.
|
|
186
|
+
* Recommended for visual gauges that are tied to a render loop.
|
|
187
|
+
* - `{ intervalMs: N }`: poll every `N` milliseconds. `N` must be a
|
|
188
|
+
* positive integer; values <= 0 disable polling.
|
|
189
|
+
*
|
|
190
|
+
* Polling is automatically suspended when the socket is not in the
|
|
191
|
+
* `open` state and resumed when it returns to `open`. The hook diffs
|
|
192
|
+
* the polled value against the last committed one, so identical
|
|
193
|
+
* readings do not trigger React re-renders.
|
|
194
|
+
*/
|
|
195
|
+
type BufferedAmountPolling = false | true | "raf" | {
|
|
196
|
+
intervalMs: number;
|
|
197
|
+
};
|
|
171
198
|
interface UseWebSocketOptions<TIncoming = unknown, TOutgoing = TIncoming> {
|
|
172
199
|
url: UrlProvider;
|
|
173
200
|
protocols?: string | string[];
|
|
174
201
|
connect?: boolean;
|
|
175
202
|
binaryType?: BinaryType;
|
|
203
|
+
bufferedAmountPolling?: BufferedAmountPolling;
|
|
176
204
|
parseMessage?: MessageParser<TIncoming>;
|
|
177
205
|
serializeMessage?: MessageSerializer<TOutgoing>;
|
|
178
206
|
reconnect?: false | UseReconnectOptions;
|
|
@@ -274,4 +302,4 @@ declare class RealtimeErrorEvent extends Event {
|
|
|
274
302
|
constructor(type: string, init: RealtimeErrorEventInit);
|
|
275
303
|
}
|
|
276
304
|
|
|
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 };
|
|
305
|
+
export { type BufferedAmountPolling, 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
|
@@ -803,9 +803,95 @@ var resolveUrlProvider = (url) => {
|
|
|
803
803
|
const resolved = typeof url === "function" ? url() : url;
|
|
804
804
|
return normalizeResolvedUrl(resolved ?? null);
|
|
805
805
|
};
|
|
806
|
+
var createInitialState3 = () => ({
|
|
807
|
+
manualClose: false,
|
|
808
|
+
manualOpen: false,
|
|
809
|
+
pendingCloseAction: null,
|
|
810
|
+
reconnectSuppressed: false,
|
|
811
|
+
skipNextCloseReconnect: false,
|
|
812
|
+
terminalError: null
|
|
813
|
+
});
|
|
814
|
+
var useWebSocketController = () => {
|
|
815
|
+
const ref = useRef(null);
|
|
816
|
+
if (ref.current === null) {
|
|
817
|
+
ref.current = createInitialState3();
|
|
818
|
+
}
|
|
819
|
+
const state = ref.current;
|
|
820
|
+
const controllerRef = useRef(null);
|
|
821
|
+
if (controllerRef.current === null) {
|
|
822
|
+
controllerRef.current = {
|
|
823
|
+
clearReconnectSuppression: () => {
|
|
824
|
+
state.reconnectSuppressed = false;
|
|
825
|
+
},
|
|
826
|
+
consumePendingCloseAction: () => {
|
|
827
|
+
const action = state.pendingCloseAction;
|
|
828
|
+
state.pendingCloseAction = null;
|
|
829
|
+
return action;
|
|
830
|
+
},
|
|
831
|
+
consumeSkipNextCloseReconnect: () => {
|
|
832
|
+
const skip = state.skipNextCloseReconnect;
|
|
833
|
+
state.skipNextCloseReconnect = false;
|
|
834
|
+
return skip;
|
|
835
|
+
},
|
|
836
|
+
hasManualCloseRequested: () => state.manualClose,
|
|
837
|
+
hasManualOpenRequested: () => state.manualOpen,
|
|
838
|
+
isReconnectSuppressed: () => state.reconnectSuppressed,
|
|
839
|
+
noteEffectInitiatedClose: () => {
|
|
840
|
+
state.reconnectSuppressed = true;
|
|
841
|
+
},
|
|
842
|
+
noteHeartbeatActiveSocketClose: (input) => {
|
|
843
|
+
state.manualOpen = false;
|
|
844
|
+
state.terminalError = input.reconnectTrigger === null ? input.error : null;
|
|
845
|
+
state.pendingCloseAction = input;
|
|
846
|
+
state.skipNextCloseReconnect = true;
|
|
847
|
+
state.reconnectSuppressed = true;
|
|
848
|
+
},
|
|
849
|
+
noteHeartbeatNoActiveSocket: ({ shouldReconnect, error }) => {
|
|
850
|
+
state.manualOpen = false;
|
|
851
|
+
state.terminalError = shouldReconnect ? null : error;
|
|
852
|
+
},
|
|
853
|
+
noteParseError: (error) => {
|
|
854
|
+
state.terminalError = error;
|
|
855
|
+
state.manualOpen = false;
|
|
856
|
+
state.skipNextCloseReconnect = true;
|
|
857
|
+
state.reconnectSuppressed = true;
|
|
858
|
+
},
|
|
859
|
+
noteSocketOpened: () => {
|
|
860
|
+
state.manualClose = false;
|
|
861
|
+
state.manualOpen = false;
|
|
862
|
+
state.reconnectSuppressed = false;
|
|
863
|
+
state.terminalError = null;
|
|
864
|
+
},
|
|
865
|
+
noteUserCloseRequested: () => {
|
|
866
|
+
state.manualClose = true;
|
|
867
|
+
state.manualOpen = false;
|
|
868
|
+
state.reconnectSuppressed = true;
|
|
869
|
+
state.terminalError = null;
|
|
870
|
+
},
|
|
871
|
+
noteUserOpenRequested: () => {
|
|
872
|
+
state.manualClose = false;
|
|
873
|
+
state.manualOpen = true;
|
|
874
|
+
state.reconnectSuppressed = false;
|
|
875
|
+
state.terminalError = null;
|
|
876
|
+
},
|
|
877
|
+
noteUserReconnectClosed: () => {
|
|
878
|
+
state.reconnectSuppressed = false;
|
|
879
|
+
},
|
|
880
|
+
noteUserReconnectRequested: () => {
|
|
881
|
+
state.manualClose = false;
|
|
882
|
+
state.manualOpen = true;
|
|
883
|
+
state.skipNextCloseReconnect = true;
|
|
884
|
+
state.reconnectSuppressed = true;
|
|
885
|
+
state.terminalError = null;
|
|
886
|
+
},
|
|
887
|
+
peekTerminalError: () => state.terminalError
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
return controllerRef.current;
|
|
891
|
+
};
|
|
806
892
|
|
|
807
893
|
// src/hooks/useWebSocket.ts
|
|
808
|
-
var
|
|
894
|
+
var createInitialState4 = (status = "idle") => ({
|
|
809
895
|
bufferedAmount: 0,
|
|
810
896
|
lastChangedAt: null,
|
|
811
897
|
lastCloseEvent: null,
|
|
@@ -848,15 +934,10 @@ var useWebSocket = (options) => {
|
|
|
848
934
|
const activeSocketEpochRef = useRef(null);
|
|
849
935
|
const closingSocketEpochRef = useRef(null);
|
|
850
936
|
const nextSocketEpochRef = useRef(0);
|
|
851
|
-
const
|
|
852
|
-
const manualOpenRef = useRef(false);
|
|
853
|
-
const skipCloseReconnectRef = useRef(false);
|
|
854
|
-
const suppressReconnectRef = useRef(false);
|
|
855
|
-
const pendingCloseActionRef = useRef(null);
|
|
856
|
-
const terminalErrorRef = useRef(null);
|
|
937
|
+
const controller = useWebSocketController();
|
|
857
938
|
const [openNonce, setOpenNonce] = useState(0);
|
|
858
939
|
const [state, setState] = useState(
|
|
859
|
-
() =>
|
|
940
|
+
() => createInitialState4(connect ? "connecting" : "idle")
|
|
860
941
|
);
|
|
861
942
|
const stateRef = useRef(state);
|
|
862
943
|
stateRef.current = state;
|
|
@@ -976,10 +1057,9 @@ var useWebSocket = (options) => {
|
|
|
976
1057
|
return;
|
|
977
1058
|
}
|
|
978
1059
|
const shouldReconnect = action === "reconnect" && reconnectEnabled && (options.shouldReconnect?.(error) ?? true);
|
|
979
|
-
manualOpenRef.current = false;
|
|
980
|
-
terminalErrorRef.current = shouldReconnect ? null : error;
|
|
981
1060
|
const socket2 = socketRef.current;
|
|
982
1061
|
if (socket2 === null || !isSocketActive(socket2)) {
|
|
1062
|
+
controller.noteHeartbeatNoActiveSocket({ error, shouldReconnect });
|
|
983
1063
|
commitState((current) => ({
|
|
984
1064
|
...current,
|
|
985
1065
|
lastChangedAt: Date.now(),
|
|
@@ -991,12 +1071,10 @@ var useWebSocket = (options) => {
|
|
|
991
1071
|
}
|
|
992
1072
|
return;
|
|
993
1073
|
}
|
|
994
|
-
|
|
1074
|
+
controller.noteHeartbeatActiveSocketClose({
|
|
995
1075
|
error,
|
|
996
1076
|
reconnectTrigger: shouldReconnect ? reconnectTrigger : null
|
|
997
|
-
};
|
|
998
|
-
skipCloseReconnectRef.current = true;
|
|
999
|
-
suppressReconnectRef.current = true;
|
|
1077
|
+
});
|
|
1000
1078
|
closeSocket({ trackClose: true });
|
|
1001
1079
|
}
|
|
1002
1080
|
);
|
|
@@ -1011,10 +1089,7 @@ var useWebSocket = (options) => {
|
|
|
1011
1089
|
}));
|
|
1012
1090
|
});
|
|
1013
1091
|
const handleOpen = useStableCallback((event, socket2) => {
|
|
1014
|
-
|
|
1015
|
-
manualOpenRef.current = false;
|
|
1016
|
-
suppressReconnectRef.current = false;
|
|
1017
|
-
terminalErrorRef.current = null;
|
|
1092
|
+
controller.noteSocketOpened();
|
|
1018
1093
|
reconnect.markConnected();
|
|
1019
1094
|
heartbeat.start();
|
|
1020
1095
|
commitState((current) => ({
|
|
@@ -1041,10 +1116,7 @@ var useWebSocket = (options) => {
|
|
|
1041
1116
|
cause: error,
|
|
1042
1117
|
kind: "parse-error"
|
|
1043
1118
|
});
|
|
1044
|
-
|
|
1045
|
-
manualOpenRef.current = false;
|
|
1046
|
-
skipCloseReconnectRef.current = true;
|
|
1047
|
-
suppressReconnectRef.current = true;
|
|
1119
|
+
controller.noteParseError(parseError);
|
|
1048
1120
|
reconnect.cancel();
|
|
1049
1121
|
heartbeat.stop();
|
|
1050
1122
|
options.onError?.(parseError);
|
|
@@ -1088,13 +1160,11 @@ var useWebSocket = (options) => {
|
|
|
1088
1160
|
}
|
|
1089
1161
|
heartbeat.stop();
|
|
1090
1162
|
updateBufferedAmount();
|
|
1091
|
-
const pendingCloseAction =
|
|
1092
|
-
|
|
1093
|
-
const
|
|
1094
|
-
const skipCloseReconnect = skipCloseReconnectRef.current;
|
|
1095
|
-
skipCloseReconnectRef.current = false;
|
|
1163
|
+
const pendingCloseAction = controller.consumePendingCloseAction();
|
|
1164
|
+
const terminalError = controller.peekTerminalError();
|
|
1165
|
+
const skipCloseReconnect = controller.consumeSkipNextCloseReconnect();
|
|
1096
1166
|
if (pendingCloseAction !== null) {
|
|
1097
|
-
|
|
1167
|
+
controller.clearReconnectSuppression();
|
|
1098
1168
|
commitState((current) => ({
|
|
1099
1169
|
...current,
|
|
1100
1170
|
lastChangedAt: Date.now(),
|
|
@@ -1109,7 +1179,7 @@ var useWebSocket = (options) => {
|
|
|
1109
1179
|
return;
|
|
1110
1180
|
}
|
|
1111
1181
|
if (terminalError !== null) {
|
|
1112
|
-
|
|
1182
|
+
controller.clearReconnectSuppression();
|
|
1113
1183
|
commitState((current) => ({
|
|
1114
1184
|
...current,
|
|
1115
1185
|
lastChangedAt: Date.now(),
|
|
@@ -1120,7 +1190,7 @@ var useWebSocket = (options) => {
|
|
|
1120
1190
|
options.onClose?.(event);
|
|
1121
1191
|
return;
|
|
1122
1192
|
}
|
|
1123
|
-
const shouldReconnect = !
|
|
1193
|
+
const shouldReconnect = !controller.isReconnectSuppressed() && !skipCloseReconnect && reconnectEnabled && (options.shouldReconnect?.(event) ?? true);
|
|
1124
1194
|
commitState((current) => ({
|
|
1125
1195
|
...current,
|
|
1126
1196
|
lastChangedAt: Date.now(),
|
|
@@ -1131,33 +1201,23 @@ var useWebSocket = (options) => {
|
|
|
1131
1201
|
if (shouldReconnect) {
|
|
1132
1202
|
reconnect.schedule("close");
|
|
1133
1203
|
} else {
|
|
1134
|
-
|
|
1204
|
+
controller.clearReconnectSuppression();
|
|
1135
1205
|
}
|
|
1136
1206
|
});
|
|
1137
1207
|
const open = useStableCallback(() => {
|
|
1138
|
-
|
|
1139
|
-
manualOpenRef.current = true;
|
|
1140
|
-
suppressReconnectRef.current = false;
|
|
1141
|
-
terminalErrorRef.current = null;
|
|
1208
|
+
controller.noteUserOpenRequested();
|
|
1142
1209
|
reconnect.cancel();
|
|
1143
1210
|
setOpenNonce((current) => current + 1);
|
|
1144
1211
|
});
|
|
1145
1212
|
const reconnectNow = useStableCallback(() => {
|
|
1146
|
-
|
|
1147
|
-
manualOpenRef.current = true;
|
|
1148
|
-
skipCloseReconnectRef.current = true;
|
|
1149
|
-
suppressReconnectRef.current = true;
|
|
1150
|
-
terminalErrorRef.current = null;
|
|
1213
|
+
controller.noteUserReconnectRequested();
|
|
1151
1214
|
heartbeat.stop();
|
|
1152
1215
|
closeSocket();
|
|
1153
|
-
|
|
1216
|
+
controller.noteUserReconnectClosed();
|
|
1154
1217
|
reconnect.schedule("manual");
|
|
1155
1218
|
});
|
|
1156
1219
|
const close = useStableCallback((code, reason) => {
|
|
1157
|
-
|
|
1158
|
-
manualOpenRef.current = false;
|
|
1159
|
-
suppressReconnectRef.current = true;
|
|
1160
|
-
terminalErrorRef.current = null;
|
|
1220
|
+
controller.noteUserCloseRequested();
|
|
1161
1221
|
reconnect.cancel();
|
|
1162
1222
|
heartbeat.stop();
|
|
1163
1223
|
commitState((current) => ({
|
|
@@ -1199,22 +1259,22 @@ var useWebSocket = (options) => {
|
|
|
1199
1259
|
}));
|
|
1200
1260
|
return;
|
|
1201
1261
|
}
|
|
1202
|
-
const shouldConnect =
|
|
1262
|
+
const shouldConnect = controller.peekTerminalError() === null && (connect && !controller.hasManualCloseRequested() || controller.hasManualOpenRequested() || reconnect.status === "running");
|
|
1203
1263
|
const nextSocketKey = `${resolvedUrl}::${protocolsDependency}::${options.binaryType ?? "blob"}`;
|
|
1204
1264
|
if (!shouldConnect) {
|
|
1205
1265
|
if (socketRef.current !== null) {
|
|
1206
|
-
|
|
1266
|
+
controller.noteEffectInitiatedClose();
|
|
1207
1267
|
closeSocket();
|
|
1208
1268
|
}
|
|
1209
1269
|
socketKeyRef.current = null;
|
|
1210
1270
|
commitState((current) => ({
|
|
1211
1271
|
...current,
|
|
1212
|
-
status:
|
|
1272
|
+
status: controller.peekTerminalError() !== null ? "error" : controller.hasManualCloseRequested() ? "closed" : "idle"
|
|
1213
1273
|
}));
|
|
1214
1274
|
return;
|
|
1215
1275
|
}
|
|
1216
1276
|
if (socketRef.current !== null && socketKeyRef.current !== nextSocketKey) {
|
|
1217
|
-
|
|
1277
|
+
controller.noteEffectInitiatedClose();
|
|
1218
1278
|
closeSocket();
|
|
1219
1279
|
}
|
|
1220
1280
|
if (socketRef.current !== null) {
|
|
@@ -1281,6 +1341,7 @@ var useWebSocket = (options) => {
|
|
|
1281
1341
|
closeSocket,
|
|
1282
1342
|
commitState,
|
|
1283
1343
|
connect,
|
|
1344
|
+
controller,
|
|
1284
1345
|
handleClose,
|
|
1285
1346
|
handleError,
|
|
1286
1347
|
handleMessage,
|
|
@@ -1295,11 +1356,10 @@ var useWebSocket = (options) => {
|
|
|
1295
1356
|
supported
|
|
1296
1357
|
]);
|
|
1297
1358
|
useEffect(() => () => {
|
|
1298
|
-
|
|
1359
|
+
controller.noteEffectInitiatedClose();
|
|
1299
1360
|
socketKeyRef.current = null;
|
|
1300
1361
|
activeSocketEpochRef.current = null;
|
|
1301
1362
|
closingSocketEpochRef.current = null;
|
|
1302
|
-
terminalErrorRef.current = null;
|
|
1303
1363
|
const socket2 = socketRef.current;
|
|
1304
1364
|
socketRef.current = null;
|
|
1305
1365
|
if (socket2 === null) {
|
|
@@ -1308,13 +1368,65 @@ var useWebSocket = (options) => {
|
|
|
1308
1368
|
if (isSocketActive(socket2)) {
|
|
1309
1369
|
socket2.close();
|
|
1310
1370
|
}
|
|
1311
|
-
}, []);
|
|
1371
|
+
}, [controller]);
|
|
1312
1372
|
const stopHeartbeat = heartbeat.stop;
|
|
1313
1373
|
useEffect(() => {
|
|
1314
1374
|
if (state.status !== "open") {
|
|
1315
1375
|
stopHeartbeat();
|
|
1316
1376
|
}
|
|
1317
1377
|
}, [state.status, stopHeartbeat]);
|
|
1378
|
+
const rawBufferedAmountPolling = options.bufferedAmountPolling;
|
|
1379
|
+
const bufferedAmountPollingMode = useMemo(() => {
|
|
1380
|
+
if (rawBufferedAmountPolling === void 0 || rawBufferedAmountPolling === false) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
if (rawBufferedAmountPolling === true) {
|
|
1384
|
+
return 100;
|
|
1385
|
+
}
|
|
1386
|
+
if (rawBufferedAmountPolling === "raf") {
|
|
1387
|
+
return "raf";
|
|
1388
|
+
}
|
|
1389
|
+
const intervalMs = rawBufferedAmountPolling.intervalMs;
|
|
1390
|
+
return Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : null;
|
|
1391
|
+
}, [rawBufferedAmountPolling]);
|
|
1392
|
+
const pollBufferedAmount = useStableCallback(() => {
|
|
1393
|
+
const socket2 = socketRef.current;
|
|
1394
|
+
if (socket2 === null) {
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
const next = socket2.bufferedAmount;
|
|
1398
|
+
if (stateRef.current.bufferedAmount === next) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
commitState((current) => ({
|
|
1402
|
+
...current,
|
|
1403
|
+
bufferedAmount: next
|
|
1404
|
+
}));
|
|
1405
|
+
});
|
|
1406
|
+
useEffect(() => {
|
|
1407
|
+
if (bufferedAmountPollingMode === null) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (state.status !== "open") {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (bufferedAmountPollingMode === "raf") {
|
|
1414
|
+
if (typeof requestAnimationFrame !== "function") {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
let frame = requestAnimationFrame(function loop() {
|
|
1418
|
+
pollBufferedAmount();
|
|
1419
|
+
frame = requestAnimationFrame(loop);
|
|
1420
|
+
});
|
|
1421
|
+
return () => {
|
|
1422
|
+
cancelAnimationFrame(frame);
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
const intervalId = setInterval(pollBufferedAmount, bufferedAmountPollingMode);
|
|
1426
|
+
return () => {
|
|
1427
|
+
clearInterval(intervalId);
|
|
1428
|
+
};
|
|
1429
|
+
}, [bufferedAmountPollingMode, state.status, pollBufferedAmount]);
|
|
1318
1430
|
const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
|
|
1319
1431
|
const snapshot = createConnectionStateSnapshot(status, {
|
|
1320
1432
|
isSupported: supported,
|
|
@@ -1361,7 +1473,7 @@ var useWebSocket = (options) => {
|
|
|
1361
1473
|
socket
|
|
1362
1474
|
};
|
|
1363
1475
|
};
|
|
1364
|
-
var
|
|
1476
|
+
var createInitialState5 = (status = "idle") => ({
|
|
1365
1477
|
lastChangedAt: null,
|
|
1366
1478
|
lastError: null,
|
|
1367
1479
|
lastEventName: null,
|
|
@@ -1398,7 +1510,7 @@ var useEventSource = (options) => {
|
|
|
1398
1510
|
const terminalErrorRef = useRef(null);
|
|
1399
1511
|
const [openNonce, setOpenNonce] = useState(0);
|
|
1400
1512
|
const [state, setState] = useState(
|
|
1401
|
-
() =>
|
|
1513
|
+
() => createInitialState5(connect ? "connecting" : "idle")
|
|
1402
1514
|
);
|
|
1403
1515
|
const stateRef = useRef(state);
|
|
1404
1516
|
stateRef.current = state;
|