react-realtime-hooks 1.0.4 → 1.2.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 +125 -9
- package/dist/index.cjs +238 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +237 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,50 @@ type UseOnlineStatusHook = (options?: UseOnlineStatusOptions) => UseOnlineStatus
|
|
|
13
13
|
|
|
14
14
|
declare const useOnlineStatus: UseOnlineStatusHook;
|
|
15
15
|
|
|
16
|
+
interface UsePageVisibilityOptions {
|
|
17
|
+
initialVisible?: boolean;
|
|
18
|
+
trackTransitions?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface UsePageVisibilityResult {
|
|
21
|
+
isVisible: boolean;
|
|
22
|
+
visibilityState: DocumentVisibilityState | "visible";
|
|
23
|
+
isSupported: boolean;
|
|
24
|
+
lastChangedAt: number | null;
|
|
25
|
+
becameVisibleAt: number | null;
|
|
26
|
+
becameHiddenAt: number | null;
|
|
27
|
+
}
|
|
28
|
+
type UsePageVisibilityHook = (options?: UsePageVisibilityOptions) => UsePageVisibilityResult;
|
|
29
|
+
|
|
30
|
+
declare const usePageVisibility: UsePageVisibilityHook;
|
|
31
|
+
|
|
32
|
+
type ConnectionGateReason = "ready" | "manual" | "offline" | "hidden";
|
|
33
|
+
interface UseConnectionGateOptions {
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
requireOnline?: boolean;
|
|
36
|
+
requireVisible?: boolean;
|
|
37
|
+
hiddenGraceMs?: number;
|
|
38
|
+
initialOnline?: boolean;
|
|
39
|
+
initialVisible?: boolean;
|
|
40
|
+
trackTransitions?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface UseConnectionGateResult {
|
|
43
|
+
connect: boolean;
|
|
44
|
+
isBlocked: boolean;
|
|
45
|
+
isWaitingForVisibleGrace: boolean;
|
|
46
|
+
reason: ConnectionGateReason;
|
|
47
|
+
isOnline: boolean;
|
|
48
|
+
isOnlineSupported: boolean;
|
|
49
|
+
isVisible: boolean;
|
|
50
|
+
isVisibilitySupported: boolean;
|
|
51
|
+
visibilityState: DocumentVisibilityState | "visible";
|
|
52
|
+
lastChangedAt: number | null;
|
|
53
|
+
becameReadyAt: number | null;
|
|
54
|
+
becameBlockedAt: number | null;
|
|
55
|
+
}
|
|
56
|
+
type UseConnectionGateHook = (options?: UseConnectionGateOptions) => UseConnectionGateResult;
|
|
57
|
+
|
|
58
|
+
declare const useConnectionGate: UseConnectionGateHook;
|
|
59
|
+
|
|
16
60
|
type ReconnectStatus = "idle" | "scheduled" | "running" | "stopped";
|
|
17
61
|
type ReconnectTrigger = "mount" | "manual" | "close" | "error" | "heartbeat-timeout" | "offline" | "online" | "visibility";
|
|
18
62
|
interface ReconnectAttempt {
|
|
@@ -219,4 +263,4 @@ type UseEventSourceHook = <TMessage = unknown>(options: UseEventSourceOptions<TM
|
|
|
219
263
|
|
|
220
264
|
declare const useEventSource: UseEventSourceHook;
|
|
221
265
|
|
|
222
|
-
export { type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, type RealtimeTransport, type ReconnectAttempt, type ReconnectDelayContext, type ReconnectDelayStrategy, type ReconnectStatus, type ReconnectTrigger, type UrlProvider, type UseEventSourceHook, type UseEventSourceOptions, type UseEventSourceResult, type UseHeartbeatHook, type UseHeartbeatOptions, type UseHeartbeatResult, type UseOnlineStatusHook, type UseOnlineStatusOptions, type UseOnlineStatusResult, type UseReconnectHook, type UseReconnectOptions, type UseReconnectResult, type UseWebSocketHeartbeatOptions, type UseWebSocketHook, type UseWebSocketOptions, type UseWebSocketResult, type WebSocketHeartbeatAction, useEventSource, useHeartbeat, useOnlineStatus, useReconnect, useWebSocket };
|
|
266
|
+
export { type ConnectionGateReason, type ConnectionStateSnapshot, type HeartbeatAckMatcher, type HeartbeatBeatFn, type MessageParser, type MessageSerializer, type Milliseconds, type RealtimeConnectionStatus, 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
|
@@ -6,6 +6,7 @@ import { useSyncExternalStore, useRef, useState, useEffect, useEffectEvent, useM
|
|
|
6
6
|
var isWebSocketSupported = () => typeof WebSocket !== "undefined";
|
|
7
7
|
var isEventSourceSupported = () => typeof EventSource !== "undefined";
|
|
8
8
|
var hasNavigatorOnLineSupport = () => typeof navigator !== "undefined" && typeof navigator.onLine === "boolean";
|
|
9
|
+
var hasDocumentVisibilitySupport = () => typeof document !== "undefined" && typeof document.visibilityState === "string";
|
|
9
10
|
var readOnlineStatus = (initialOnline = true) => {
|
|
10
11
|
if (!hasNavigatorOnLineSupport()) {
|
|
11
12
|
return {
|
|
@@ -18,6 +19,20 @@ var readOnlineStatus = (initialOnline = true) => {
|
|
|
18
19
|
isSupported: true
|
|
19
20
|
};
|
|
20
21
|
};
|
|
22
|
+
var readPageVisibility = (initialVisible = true) => {
|
|
23
|
+
if (!hasDocumentVisibilitySupport()) {
|
|
24
|
+
return {
|
|
25
|
+
isVisible: initialVisible,
|
|
26
|
+
isSupported: false,
|
|
27
|
+
visibilityState: initialVisible ? "visible" : "hidden"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
isVisible: document.visibilityState === "visible",
|
|
32
|
+
isSupported: true,
|
|
33
|
+
visibilityState: document.visibilityState
|
|
34
|
+
};
|
|
35
|
+
};
|
|
21
36
|
|
|
22
37
|
// src/hooks/useOnlineStatus.ts
|
|
23
38
|
var subscribeToOnlineStatus = (onStoreChange) => {
|
|
@@ -70,6 +85,227 @@ var useOnlineStatus = (options = {}) => {
|
|
|
70
85
|
...transitions
|
|
71
86
|
};
|
|
72
87
|
};
|
|
88
|
+
var subscribeToPageVisibility = (onStoreChange) => {
|
|
89
|
+
if (typeof document === "undefined" || !hasDocumentVisibilitySupport()) {
|
|
90
|
+
return () => {
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
document.addEventListener("visibilitychange", onStoreChange);
|
|
94
|
+
return () => {
|
|
95
|
+
document.removeEventListener("visibilitychange", onStoreChange);
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
var createEmptyTransitionState2 = () => ({
|
|
99
|
+
lastChangedAt: null,
|
|
100
|
+
becameHiddenAt: null,
|
|
101
|
+
becameVisibleAt: null
|
|
102
|
+
});
|
|
103
|
+
var usePageVisibility = (options = {}) => {
|
|
104
|
+
const initialVisible = options.initialVisible ?? true;
|
|
105
|
+
const trackTransitions = options.trackTransitions ?? true;
|
|
106
|
+
const visibilityState = useSyncExternalStore(
|
|
107
|
+
subscribeToPageVisibility,
|
|
108
|
+
() => readPageVisibility(initialVisible).visibilityState,
|
|
109
|
+
() => initialVisible ? "visible" : "hidden"
|
|
110
|
+
);
|
|
111
|
+
const isVisible = visibilityState === "visible";
|
|
112
|
+
const previousVisibleRef = useRef(isVisible);
|
|
113
|
+
const [transitions, setTransitions] = useState(createEmptyTransitionState2);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!trackTransitions) {
|
|
116
|
+
previousVisibleRef.current = isVisible;
|
|
117
|
+
setTransitions(createEmptyTransitionState2);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (previousVisibleRef.current === isVisible) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const changedAt = Date.now();
|
|
124
|
+
previousVisibleRef.current = isVisible;
|
|
125
|
+
setTransitions((current) => ({
|
|
126
|
+
lastChangedAt: changedAt,
|
|
127
|
+
becameHiddenAt: isVisible ? current.becameHiddenAt : changedAt,
|
|
128
|
+
becameVisibleAt: isVisible ? changedAt : current.becameVisibleAt
|
|
129
|
+
}));
|
|
130
|
+
}, [isVisible, trackTransitions]);
|
|
131
|
+
return {
|
|
132
|
+
isSupported: hasDocumentVisibilitySupport(),
|
|
133
|
+
isVisible,
|
|
134
|
+
visibilityState,
|
|
135
|
+
...transitions
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/core/timers.ts
|
|
140
|
+
var sanitizeTimerDelay = (delayMs) => {
|
|
141
|
+
if (!Number.isFinite(delayMs)) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
return Math.max(0, Math.round(delayMs));
|
|
145
|
+
};
|
|
146
|
+
var createManagedTimeout = () => {
|
|
147
|
+
let timeoutId = null;
|
|
148
|
+
return {
|
|
149
|
+
cancel() {
|
|
150
|
+
if (timeoutId !== null) {
|
|
151
|
+
clearTimeout(timeoutId);
|
|
152
|
+
timeoutId = null;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
isActive() {
|
|
156
|
+
return timeoutId !== null;
|
|
157
|
+
},
|
|
158
|
+
schedule(callback, delayMs) {
|
|
159
|
+
if (timeoutId !== null) {
|
|
160
|
+
clearTimeout(timeoutId);
|
|
161
|
+
}
|
|
162
|
+
timeoutId = setTimeout(() => {
|
|
163
|
+
timeoutId = null;
|
|
164
|
+
callback();
|
|
165
|
+
}, sanitizeTimerDelay(delayMs));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
var createManagedInterval = () => {
|
|
170
|
+
let intervalId = null;
|
|
171
|
+
return {
|
|
172
|
+
cancel() {
|
|
173
|
+
if (intervalId !== null) {
|
|
174
|
+
clearInterval(intervalId);
|
|
175
|
+
intervalId = null;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
isActive() {
|
|
179
|
+
return intervalId !== null;
|
|
180
|
+
},
|
|
181
|
+
start(callback, intervalMs) {
|
|
182
|
+
if (intervalId !== null) {
|
|
183
|
+
clearInterval(intervalId);
|
|
184
|
+
}
|
|
185
|
+
intervalId = setInterval(callback, sanitizeTimerDelay(intervalMs));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/hooks/useConnectionGate.ts
|
|
191
|
+
var createEmptyTransitionState3 = () => ({
|
|
192
|
+
becameBlockedAt: null,
|
|
193
|
+
becameReadyAt: null,
|
|
194
|
+
lastChangedAt: null
|
|
195
|
+
});
|
|
196
|
+
var normalizeHiddenGraceMs = (value) => {
|
|
197
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
return Math.max(0, value);
|
|
201
|
+
};
|
|
202
|
+
var useConnectionGate = (options = {}) => {
|
|
203
|
+
const enabled = options.enabled ?? true;
|
|
204
|
+
const requireOnline = options.requireOnline ?? true;
|
|
205
|
+
const requireVisible = options.requireVisible ?? false;
|
|
206
|
+
const hiddenGraceMs = normalizeHiddenGraceMs(options.hiddenGraceMs);
|
|
207
|
+
const trackTransitions = options.trackTransitions ?? true;
|
|
208
|
+
const onlineStatus = useOnlineStatus({
|
|
209
|
+
...options.initialOnline === void 0 ? {} : { initialOnline: options.initialOnline },
|
|
210
|
+
trackTransitions: false
|
|
211
|
+
});
|
|
212
|
+
const pageVisibility = usePageVisibility({
|
|
213
|
+
...options.initialVisible === void 0 ? {} : { initialVisible: options.initialVisible },
|
|
214
|
+
trackTransitions: false
|
|
215
|
+
});
|
|
216
|
+
const hiddenGraceTimeoutRef = useRef(createManagedTimeout());
|
|
217
|
+
const hiddenSinceRef = useRef(null);
|
|
218
|
+
const previousStateRef = useRef(null);
|
|
219
|
+
const [hasExceededHiddenGrace, setHasExceededHiddenGrace] = useState(false);
|
|
220
|
+
const [isWaitingForVisibleGrace, setIsWaitingForVisibleGrace] = useState(false);
|
|
221
|
+
const [transitions, setTransitions] = useState(createEmptyTransitionState3);
|
|
222
|
+
useEffect(() => () => {
|
|
223
|
+
hiddenGraceTimeoutRef.current.cancel();
|
|
224
|
+
}, []);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
hiddenGraceTimeoutRef.current.cancel();
|
|
227
|
+
if (!requireVisible || pageVisibility.isVisible) {
|
|
228
|
+
hiddenSinceRef.current = null;
|
|
229
|
+
setHasExceededHiddenGrace(false);
|
|
230
|
+
setIsWaitingForVisibleGrace(false);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const hiddenSince = hiddenSinceRef.current ?? Date.now();
|
|
234
|
+
hiddenSinceRef.current = hiddenSince;
|
|
235
|
+
if (hiddenGraceMs <= 0) {
|
|
236
|
+
setHasExceededHiddenGrace(true);
|
|
237
|
+
setIsWaitingForVisibleGrace(false);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const elapsedMs = Date.now() - hiddenSince;
|
|
241
|
+
if (elapsedMs >= hiddenGraceMs) {
|
|
242
|
+
setHasExceededHiddenGrace(true);
|
|
243
|
+
setIsWaitingForVisibleGrace(false);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
setHasExceededHiddenGrace(false);
|
|
247
|
+
setIsWaitingForVisibleGrace(true);
|
|
248
|
+
hiddenGraceTimeoutRef.current.schedule(() => {
|
|
249
|
+
setHasExceededHiddenGrace(true);
|
|
250
|
+
setIsWaitingForVisibleGrace(false);
|
|
251
|
+
}, hiddenGraceMs - elapsedMs);
|
|
252
|
+
}, [hiddenGraceMs, pageVisibility.isVisible, requireVisible]);
|
|
253
|
+
let reason = "ready";
|
|
254
|
+
if (!enabled) {
|
|
255
|
+
reason = "manual";
|
|
256
|
+
} else if (requireOnline && !onlineStatus.isOnline) {
|
|
257
|
+
reason = "offline";
|
|
258
|
+
} else if (requireVisible && !pageVisibility.isVisible && hasExceededHiddenGrace) {
|
|
259
|
+
reason = "hidden";
|
|
260
|
+
}
|
|
261
|
+
const connect = reason === "ready";
|
|
262
|
+
const isBlocked = !connect;
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!trackTransitions) {
|
|
265
|
+
previousStateRef.current = {
|
|
266
|
+
connect,
|
|
267
|
+
reason
|
|
268
|
+
};
|
|
269
|
+
setTransitions(createEmptyTransitionState3);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const previousState = previousStateRef.current;
|
|
273
|
+
if (previousState === null) {
|
|
274
|
+
previousStateRef.current = {
|
|
275
|
+
connect,
|
|
276
|
+
reason
|
|
277
|
+
};
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (previousState.connect === connect && previousState.reason === reason) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const changedAt = Date.now();
|
|
284
|
+
previousStateRef.current = {
|
|
285
|
+
connect,
|
|
286
|
+
reason
|
|
287
|
+
};
|
|
288
|
+
setTransitions((current) => ({
|
|
289
|
+
becameBlockedAt: connect ? current.becameBlockedAt : changedAt,
|
|
290
|
+
becameReadyAt: connect ? changedAt : current.becameReadyAt,
|
|
291
|
+
lastChangedAt: changedAt
|
|
292
|
+
}));
|
|
293
|
+
}, [connect, reason, trackTransitions]);
|
|
294
|
+
return {
|
|
295
|
+
becameBlockedAt: transitions.becameBlockedAt,
|
|
296
|
+
becameReadyAt: transitions.becameReadyAt,
|
|
297
|
+
connect,
|
|
298
|
+
isBlocked,
|
|
299
|
+
isOnline: onlineStatus.isOnline,
|
|
300
|
+
isOnlineSupported: onlineStatus.isSupported,
|
|
301
|
+
isVisibilitySupported: pageVisibility.isSupported,
|
|
302
|
+
isVisible: pageVisibility.isVisible,
|
|
303
|
+
isWaitingForVisibleGrace,
|
|
304
|
+
lastChangedAt: transitions.lastChangedAt,
|
|
305
|
+
reason,
|
|
306
|
+
visibilityState: pageVisibility.visibilityState
|
|
307
|
+
};
|
|
308
|
+
};
|
|
73
309
|
|
|
74
310
|
// src/core/reconnect.ts
|
|
75
311
|
var DEFAULT_RECONNECT_OPTIONS = {
|
|
@@ -207,57 +443,6 @@ var createReconnectAttempt = (attempt, trigger, options, lastDelayMs, config = {
|
|
|
207
443
|
};
|
|
208
444
|
};
|
|
209
445
|
|
|
210
|
-
// src/core/timers.ts
|
|
211
|
-
var sanitizeTimerDelay = (delayMs) => {
|
|
212
|
-
if (!Number.isFinite(delayMs)) {
|
|
213
|
-
return 0;
|
|
214
|
-
}
|
|
215
|
-
return Math.max(0, Math.round(delayMs));
|
|
216
|
-
};
|
|
217
|
-
var createManagedTimeout = () => {
|
|
218
|
-
let timeoutId = null;
|
|
219
|
-
return {
|
|
220
|
-
cancel() {
|
|
221
|
-
if (timeoutId !== null) {
|
|
222
|
-
clearTimeout(timeoutId);
|
|
223
|
-
timeoutId = null;
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
isActive() {
|
|
227
|
-
return timeoutId !== null;
|
|
228
|
-
},
|
|
229
|
-
schedule(callback, delayMs) {
|
|
230
|
-
if (timeoutId !== null) {
|
|
231
|
-
clearTimeout(timeoutId);
|
|
232
|
-
}
|
|
233
|
-
timeoutId = setTimeout(() => {
|
|
234
|
-
timeoutId = null;
|
|
235
|
-
callback();
|
|
236
|
-
}, sanitizeTimerDelay(delayMs));
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
};
|
|
240
|
-
var createManagedInterval = () => {
|
|
241
|
-
let intervalId = null;
|
|
242
|
-
return {
|
|
243
|
-
cancel() {
|
|
244
|
-
if (intervalId !== null) {
|
|
245
|
-
clearInterval(intervalId);
|
|
246
|
-
intervalId = null;
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
isActive() {
|
|
250
|
-
return intervalId !== null;
|
|
251
|
-
},
|
|
252
|
-
start(callback, intervalMs) {
|
|
253
|
-
if (intervalId !== null) {
|
|
254
|
-
clearInterval(intervalId);
|
|
255
|
-
}
|
|
256
|
-
intervalId = setInterval(callback, sanitizeTimerDelay(intervalMs));
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
};
|
|
260
|
-
|
|
261
446
|
// src/hooks/useReconnect.ts
|
|
262
447
|
var createInitialState = (enabled) => ({
|
|
263
448
|
attempt: 0,
|
|
@@ -1393,6 +1578,6 @@ var useEventSource = (options) => {
|
|
|
1393
1578
|
};
|
|
1394
1579
|
};
|
|
1395
1580
|
|
|
1396
|
-
export { useEventSource, useHeartbeat, useOnlineStatus, useReconnect, useWebSocket };
|
|
1581
|
+
export { useConnectionGate, useEventSource, useHeartbeat, useOnlineStatus, usePageVisibility, useReconnect, useWebSocket };
|
|
1397
1582
|
//# sourceMappingURL=index.js.map
|
|
1398
1583
|
//# sourceMappingURL=index.js.map
|