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 CHANGED
@@ -7,9 +7,9 @@
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-typed-3178c6)](https://www.typescriptlang.org/)
8
8
  [![react](https://img.shields.io/badge/react-19.x-149eca)](https://www.npmjs.com/package/react)
9
9
 
10
- Production-ready React hooks for WebSocket and SSE with auto-reconnect, heartbeat, typed connection state, and browser network awareness.
10
+ Production-ready React hooks for WebSocket and SSE with auto-reconnect, heartbeat, typed connection state, and browser network awareness including page visibility and connection gating.
11
11
 
12
- `react-realtime-hooks` is for apps that need more than "open a socket and hope for the best". It gives you composable hooks for transport lifecycle, retry strategy, heartbeat, and online status, so your UI can react to realtime state without rebuilding the same connection logic in every screen.
12
+ `react-realtime-hooks` is for apps that need more than "open a socket and hope for the best". It gives you composable hooks for transport lifecycle, retry strategy, heartbeat, online status, page visibility, and connection gating, so your UI can react to realtime state without rebuilding the same connection logic in every screen.
13
13
 
14
14
  Live demo: https://volkov85.github.io/react-realtime-hooks/
15
15
 
@@ -23,7 +23,8 @@ Real apps need:
23
23
  - reconnect strategy with caps, jitter, and manual control
24
24
  - heartbeat and timeout tracking
25
25
  - clean SSR behavior
26
- - browser network awareness
26
+ - browser network and page visibility awareness
27
+ - environment-aware connection gating for offline state and background tabs
27
28
  - typed message parsing and sending
28
29
 
29
30
  `react-realtime-hooks` packages those concerns into small hooks that compose cleanly in React.
@@ -33,6 +34,7 @@ Real apps need:
33
34
  - `useWebSocket` and `useEventSource` return state you can render, not just transport instances.
34
35
  - Built-in reconnect flow with exponential backoff, jitter, attempt limits, and manual restart.
35
36
  - Heartbeat support with ack matching, timeout detection, and latency measurement.
37
+ - `useConnectionGate` turns online and visibility signals into a single `connect` flag for transport hooks.
36
38
  - Discriminated connection snapshots: `idle`, `connecting`, `open`, `reconnecting`, `closing`, `closed`, `error`.
37
39
  - First-class TypeScript support with generic message types and custom parsers/serializers.
38
40
  - SSR-safe by default. No browser-only globals are touched during server render.
@@ -46,7 +48,7 @@ Real apps need:
46
48
  | Connection state | You model it yourself | Built-in status model you can render directly |
47
49
  | Reconnect flow | Manual timers and teardown | `useReconnect` with backoff, jitter, and limits |
48
50
  | Heartbeat | Custom ping/pong loop | `heartbeat` support with timeout and latency |
49
- | Network awareness | Separate browser event wiring | `useOnlineStatus` for online/offline state |
51
+ | Browser awareness | Separate browser event wiring | `useOnlineStatus`, `usePageVisibility`, and `useConnectionGate` for browser-aware state |
50
52
  | SSR safety | Easy to break during render | Browser-only behavior stays out of server render |
51
53
  | UI ergonomics | Event handlers and refs everywhere | Hook result already shaped for product UI |
52
54
 
@@ -65,7 +67,11 @@ Peer dependency:
65
67
  ## How It Feels
66
68
 
67
69
  ```tsx
68
- import { useOnlineStatus, useWebSocket } from "react-realtime-hooks";
70
+ import {
71
+ useOnlineStatus,
72
+ usePageVisibility,
73
+ useWebSocket
74
+ } from "react-realtime-hooks";
69
75
 
70
76
  type IncomingMessage =
71
77
  | { type: "notification"; text: string }
@@ -75,6 +81,7 @@ type OutgoingMessage = { type: "ack"; id: string } | { type: "ping" };
75
81
 
76
82
  export function NotificationsPanel() {
77
83
  const network = useOnlineStatus();
84
+ const page = usePageVisibility();
78
85
  const socket = useWebSocket<IncomingMessage, OutgoingMessage>({
79
86
  url: "ws://localhost:8080/notifications",
80
87
  parseMessage: (event) => JSON.parse(String(event.data)) as IncomingMessage,
@@ -93,8 +100,8 @@ export function NotificationsPanel() {
93
100
  return (
94
101
  <section>
95
102
  <p>
96
- Network: {network.isOnline ? "online" : "offline"} | Transport:{" "}
97
- {socket.status}
103
+ Page: {page.isVisible ? "visible" : "hidden"} | Network:{" "}
104
+ {network.isOnline ? "online" : "offline"} | Transport: {socket.status}
98
105
  </p>
99
106
 
100
107
  {socket.status === "reconnecting" && (
@@ -146,7 +153,7 @@ Browser APIs
146
153
  WebSocket / EventSource / navigator.onLine
147
154
 
148
155
  Core hooks
149
- useReconnect / useHeartbeat / useOnlineStatus
156
+ useReconnect / useHeartbeat / useOnlineStatus / usePageVisibility / useConnectionGate
150
157
 
151
158
  Transport hooks
152
159
  useWebSocket / useEventSource
@@ -204,7 +211,9 @@ This library already models those edges in a reusable way.
204
211
  | `useEventSource` | Server-Sent Events streams | `status`, `eventSource`, `lastMessage`, `lastEventName`, `reconnect()` |
205
212
  | `useReconnect` | Reusable retry and backoff logic | `schedule()`, `cancel()`, `reset()`, `attempt`, `status` |
206
213
  | `useHeartbeat` | Liveness checks and timeout tracking | `start()`, `stop()`, `beat()`, `notifyAck()`, `latencyMs` |
214
+ | `useConnectionGate` | Browser-aware transport gating | `connect`, `reason`, `isBlocked`, gate transition timestamps |
207
215
  | `useOnlineStatus` | Browser online/offline state | `isOnline`, `isSupported`, transition timestamps |
216
+ | `usePageVisibility` | Browser tab/page visibility state | `isVisible`, `visibilityState`, `isSupported`, transition timestamps |
208
217
 
209
218
  ## Transport Examples
210
219
 
@@ -334,6 +343,50 @@ export function NetworkIndicator() {
334
343
  }
335
344
  ```
336
345
 
346
+ ### `usePageVisibility`
347
+
348
+ ```tsx
349
+ import { usePageVisibility } from "react-realtime-hooks";
350
+
351
+ export function AttentionAwareBadge() {
352
+ const page = usePageVisibility({
353
+ trackTransitions: true,
354
+ });
355
+
356
+ return (
357
+ <span>
358
+ {page.isVisible ? "Active tab" : "Background tab"} ({page.visibilityState})
359
+ </span>
360
+ );
361
+ }
362
+ ```
363
+ ### `useConnectionGate`
364
+
365
+ ```tsx
366
+ import { useConnectionGate, useWebSocket } from "react-realtime-hooks";
367
+
368
+ export function GatedNotifications() {
369
+ const gate = useConnectionGate({
370
+ requireOnline: true,
371
+ requireVisible: true,
372
+ hiddenGraceMs: 30_000,
373
+ });
374
+ const socket = useWebSocket({
375
+ connect: gate.connect,
376
+ reconnect: {
377
+ initialDelayMs: 1_000,
378
+ maxAttempts: null,
379
+ },
380
+ url: "ws://localhost:8080/notifications",
381
+ });
382
+
383
+ return (
384
+ <div>
385
+ Gate: {gate.reason} | Transport: {socket.status}
386
+ </div>
387
+ );
388
+ }
389
+ ```
337
390
  ## API Reference
338
391
 
339
392
  <details>
@@ -486,6 +539,42 @@ When you configure `useWebSocket` heartbeat, you can also set `timeoutAction` an
486
539
 
487
540
  </details>
488
541
 
542
+ <details>
543
+ <summary><strong>useConnectionGate</strong></summary>
544
+
545
+ ### Options
546
+
547
+ | Option | Type | Default | Description |
548
+ | ------------------ | --------- | ------- | ------------------------------------------------------------------ |
549
+ | `enabled` | `boolean` | `true` | Master on/off switch for the gate |
550
+ | `requireOnline` | `boolean` | `true` | Blocks `connect` when the browser reports offline |
551
+ | `requireVisible` | `boolean` | `false` | Blocks `connect` when the page is hidden |
552
+ | `hiddenGraceMs` | `number` | `0` | Delay before hidden pages are blocked |
553
+ | `initialOnline` | `boolean` | `true` | Fallback value when `navigator.onLine` is unavailable |
554
+ | `initialVisible` | `boolean` | `true` | Fallback value when the Visibility API is unavailable |
555
+ | `trackTransitions` | `boolean` | `true` | Tracks `lastChangedAt`, `becameReadyAt`, and `becameBlockedAt` |
556
+
557
+ ### Result
558
+
559
+ | Field | Type | Description |
560
+ | -------------------------- | ------------------------------------------ | ---------------------------------------------------------- |
561
+ | `connect` | `boolean` | Flag to pass into `useWebSocket` or `useEventSource` |
562
+ | `isBlocked` | `boolean` | Whether the gate is currently blocking connection |
563
+ | `reason` | `"ready" \| "manual" \| "offline" \| "hidden"` | Current gate reason |
564
+ | `isWaitingForVisibleGrace` | `boolean` | `true` while a hidden-tab grace window is still active |
565
+ | `isOnline` | `boolean` | Current browser online state |
566
+ | `isOnlineSupported` | `boolean` | Whether `navigator.onLine` is available |
567
+ | `isVisible` | `boolean` | Whether the current page is visible |
568
+ | `isVisibilitySupported` | `boolean` | Whether `document.visibilityState` is available |
569
+ | `visibilityState` | `DocumentVisibilityState \| "visible"` | Current browser visibility state |
570
+ | `lastChangedAt` | `number \| null` | Timestamp of the last gate state change |
571
+ | `becameReadyAt` | `number \| null` | Timestamp of the last transition into `reason === "ready"` |
572
+ | `becameBlockedAt` | `number \| null` | Timestamp of the last transition into a blocked state |
573
+
574
+ `reason` priority is deterministic: `manual` overrides `offline`, and `offline` overrides `hidden`.
575
+ That keeps the gate predictable when multiple blockers apply at once.
576
+
577
+ </details>
489
578
  <details>
490
579
  <summary><strong>useOnlineStatus</strong></summary>
491
580
 
@@ -508,6 +597,28 @@ When you configure `useWebSocket` heartbeat, you can also set `timeoutAction` an
508
597
 
509
598
  </details>
510
599
 
600
+ <details>
601
+ <summary><strong>usePageVisibility</strong></summary>
602
+
603
+ ### Options
604
+
605
+ | Option | Type | Default | Description |
606
+ | ------------------ | --------- | ------- | ----------------------------------------------------------- |
607
+ | `initialVisible` | `boolean` | `true` | Fallback value when the Visibility API is unavailable |
608
+ | `trackTransitions` | `boolean` | `true` | Tracks `lastChangedAt`, `becameVisibleAt`, `becameHiddenAt` |
609
+
610
+ ### Result
611
+
612
+ | Field | Type | Description |
613
+ | ----------------- | ------------------------------------ | ------------------------------------------------ |
614
+ | `isVisible` | `boolean` | Whether the current page is visible |
615
+ | `visibilityState` | `DocumentVisibilityState \| "visible"` | Current browser visibility state |
616
+ | `isSupported` | `boolean` | Whether `document.visibilityState` is available |
617
+ | `lastChangedAt` | `number \| null` | Timestamp of the last visibility transition |
618
+ | `becameVisibleAt` | `number \| null` | Timestamp of the last visible transition |
619
+ | `becameHiddenAt` | `number \| null` | Timestamp of the last hidden transition |
620
+
621
+ </details>
511
622
  ## Limitations And Edge Cases
512
623
 
513
624
  - `useEventSource` is receive-only by design. SSE is not a bidirectional transport.
@@ -527,7 +638,7 @@ The package includes behavior tests for:
527
638
  - exponential backoff
528
639
  - timer and listener cleanup
529
640
  - heartbeat start / stop / timeout
530
- - browser offline / online transitions
641
+ - browser offline / online and page visibility transitions
531
642
  - invalid payload and parse errors
532
643
  - manual reconnect and manual close
533
644
 
@@ -551,3 +662,8 @@ Development and release workflow live in [CONTRIBUTING.md](./CONTRIBUTING.md).
551
662
  ## License
552
663
 
553
664
  MIT
665
+
666
+
667
+
668
+
669
+
package/dist/index.cjs CHANGED
@@ -8,6 +8,7 @@ var react = require('react');
8
8
  var isWebSocketSupported = () => typeof WebSocket !== "undefined";
9
9
  var isEventSourceSupported = () => typeof EventSource !== "undefined";
10
10
  var hasNavigatorOnLineSupport = () => typeof navigator !== "undefined" && typeof navigator.onLine === "boolean";
11
+ var hasDocumentVisibilitySupport = () => typeof document !== "undefined" && typeof document.visibilityState === "string";
11
12
  var readOnlineStatus = (initialOnline = true) => {
12
13
  if (!hasNavigatorOnLineSupport()) {
13
14
  return {
@@ -20,6 +21,20 @@ var readOnlineStatus = (initialOnline = true) => {
20
21
  isSupported: true
21
22
  };
22
23
  };
24
+ var readPageVisibility = (initialVisible = true) => {
25
+ if (!hasDocumentVisibilitySupport()) {
26
+ return {
27
+ isVisible: initialVisible,
28
+ isSupported: false,
29
+ visibilityState: initialVisible ? "visible" : "hidden"
30
+ };
31
+ }
32
+ return {
33
+ isVisible: document.visibilityState === "visible",
34
+ isSupported: true,
35
+ visibilityState: document.visibilityState
36
+ };
37
+ };
23
38
 
24
39
  // src/hooks/useOnlineStatus.ts
25
40
  var subscribeToOnlineStatus = (onStoreChange) => {
@@ -72,6 +87,227 @@ var useOnlineStatus = (options = {}) => {
72
87
  ...transitions
73
88
  };
74
89
  };
90
+ var subscribeToPageVisibility = (onStoreChange) => {
91
+ if (typeof document === "undefined" || !hasDocumentVisibilitySupport()) {
92
+ return () => {
93
+ };
94
+ }
95
+ document.addEventListener("visibilitychange", onStoreChange);
96
+ return () => {
97
+ document.removeEventListener("visibilitychange", onStoreChange);
98
+ };
99
+ };
100
+ var createEmptyTransitionState2 = () => ({
101
+ lastChangedAt: null,
102
+ becameHiddenAt: null,
103
+ becameVisibleAt: null
104
+ });
105
+ var usePageVisibility = (options = {}) => {
106
+ const initialVisible = options.initialVisible ?? true;
107
+ const trackTransitions = options.trackTransitions ?? true;
108
+ const visibilityState = react.useSyncExternalStore(
109
+ subscribeToPageVisibility,
110
+ () => readPageVisibility(initialVisible).visibilityState,
111
+ () => initialVisible ? "visible" : "hidden"
112
+ );
113
+ const isVisible = visibilityState === "visible";
114
+ const previousVisibleRef = react.useRef(isVisible);
115
+ const [transitions, setTransitions] = react.useState(createEmptyTransitionState2);
116
+ react.useEffect(() => {
117
+ if (!trackTransitions) {
118
+ previousVisibleRef.current = isVisible;
119
+ setTransitions(createEmptyTransitionState2);
120
+ return;
121
+ }
122
+ if (previousVisibleRef.current === isVisible) {
123
+ return;
124
+ }
125
+ const changedAt = Date.now();
126
+ previousVisibleRef.current = isVisible;
127
+ setTransitions((current) => ({
128
+ lastChangedAt: changedAt,
129
+ becameHiddenAt: isVisible ? current.becameHiddenAt : changedAt,
130
+ becameVisibleAt: isVisible ? changedAt : current.becameVisibleAt
131
+ }));
132
+ }, [isVisible, trackTransitions]);
133
+ return {
134
+ isSupported: hasDocumentVisibilitySupport(),
135
+ isVisible,
136
+ visibilityState,
137
+ ...transitions
138
+ };
139
+ };
140
+
141
+ // src/core/timers.ts
142
+ var sanitizeTimerDelay = (delayMs) => {
143
+ if (!Number.isFinite(delayMs)) {
144
+ return 0;
145
+ }
146
+ return Math.max(0, Math.round(delayMs));
147
+ };
148
+ var createManagedTimeout = () => {
149
+ let timeoutId = null;
150
+ return {
151
+ cancel() {
152
+ if (timeoutId !== null) {
153
+ clearTimeout(timeoutId);
154
+ timeoutId = null;
155
+ }
156
+ },
157
+ isActive() {
158
+ return timeoutId !== null;
159
+ },
160
+ schedule(callback, delayMs) {
161
+ if (timeoutId !== null) {
162
+ clearTimeout(timeoutId);
163
+ }
164
+ timeoutId = setTimeout(() => {
165
+ timeoutId = null;
166
+ callback();
167
+ }, sanitizeTimerDelay(delayMs));
168
+ }
169
+ };
170
+ };
171
+ var createManagedInterval = () => {
172
+ let intervalId = null;
173
+ return {
174
+ cancel() {
175
+ if (intervalId !== null) {
176
+ clearInterval(intervalId);
177
+ intervalId = null;
178
+ }
179
+ },
180
+ isActive() {
181
+ return intervalId !== null;
182
+ },
183
+ start(callback, intervalMs) {
184
+ if (intervalId !== null) {
185
+ clearInterval(intervalId);
186
+ }
187
+ intervalId = setInterval(callback, sanitizeTimerDelay(intervalMs));
188
+ }
189
+ };
190
+ };
191
+
192
+ // src/hooks/useConnectionGate.ts
193
+ var createEmptyTransitionState3 = () => ({
194
+ becameBlockedAt: null,
195
+ becameReadyAt: null,
196
+ lastChangedAt: null
197
+ });
198
+ var normalizeHiddenGraceMs = (value) => {
199
+ if (value === void 0 || !Number.isFinite(value)) {
200
+ return 0;
201
+ }
202
+ return Math.max(0, value);
203
+ };
204
+ var useConnectionGate = (options = {}) => {
205
+ const enabled = options.enabled ?? true;
206
+ const requireOnline = options.requireOnline ?? true;
207
+ const requireVisible = options.requireVisible ?? false;
208
+ const hiddenGraceMs = normalizeHiddenGraceMs(options.hiddenGraceMs);
209
+ const trackTransitions = options.trackTransitions ?? true;
210
+ const onlineStatus = useOnlineStatus({
211
+ ...options.initialOnline === void 0 ? {} : { initialOnline: options.initialOnline },
212
+ trackTransitions: false
213
+ });
214
+ const pageVisibility = usePageVisibility({
215
+ ...options.initialVisible === void 0 ? {} : { initialVisible: options.initialVisible },
216
+ trackTransitions: false
217
+ });
218
+ const hiddenGraceTimeoutRef = react.useRef(createManagedTimeout());
219
+ const hiddenSinceRef = react.useRef(null);
220
+ const previousStateRef = react.useRef(null);
221
+ const [hasExceededHiddenGrace, setHasExceededHiddenGrace] = react.useState(false);
222
+ const [isWaitingForVisibleGrace, setIsWaitingForVisibleGrace] = react.useState(false);
223
+ const [transitions, setTransitions] = react.useState(createEmptyTransitionState3);
224
+ react.useEffect(() => () => {
225
+ hiddenGraceTimeoutRef.current.cancel();
226
+ }, []);
227
+ react.useEffect(() => {
228
+ hiddenGraceTimeoutRef.current.cancel();
229
+ if (!requireVisible || pageVisibility.isVisible) {
230
+ hiddenSinceRef.current = null;
231
+ setHasExceededHiddenGrace(false);
232
+ setIsWaitingForVisibleGrace(false);
233
+ return;
234
+ }
235
+ const hiddenSince = hiddenSinceRef.current ?? Date.now();
236
+ hiddenSinceRef.current = hiddenSince;
237
+ if (hiddenGraceMs <= 0) {
238
+ setHasExceededHiddenGrace(true);
239
+ setIsWaitingForVisibleGrace(false);
240
+ return;
241
+ }
242
+ const elapsedMs = Date.now() - hiddenSince;
243
+ if (elapsedMs >= hiddenGraceMs) {
244
+ setHasExceededHiddenGrace(true);
245
+ setIsWaitingForVisibleGrace(false);
246
+ return;
247
+ }
248
+ setHasExceededHiddenGrace(false);
249
+ setIsWaitingForVisibleGrace(true);
250
+ hiddenGraceTimeoutRef.current.schedule(() => {
251
+ setHasExceededHiddenGrace(true);
252
+ setIsWaitingForVisibleGrace(false);
253
+ }, hiddenGraceMs - elapsedMs);
254
+ }, [hiddenGraceMs, pageVisibility.isVisible, requireVisible]);
255
+ let reason = "ready";
256
+ if (!enabled) {
257
+ reason = "manual";
258
+ } else if (requireOnline && !onlineStatus.isOnline) {
259
+ reason = "offline";
260
+ } else if (requireVisible && !pageVisibility.isVisible && hasExceededHiddenGrace) {
261
+ reason = "hidden";
262
+ }
263
+ const connect = reason === "ready";
264
+ const isBlocked = !connect;
265
+ react.useEffect(() => {
266
+ if (!trackTransitions) {
267
+ previousStateRef.current = {
268
+ connect,
269
+ reason
270
+ };
271
+ setTransitions(createEmptyTransitionState3);
272
+ return;
273
+ }
274
+ const previousState = previousStateRef.current;
275
+ if (previousState === null) {
276
+ previousStateRef.current = {
277
+ connect,
278
+ reason
279
+ };
280
+ return;
281
+ }
282
+ if (previousState.connect === connect && previousState.reason === reason) {
283
+ return;
284
+ }
285
+ const changedAt = Date.now();
286
+ previousStateRef.current = {
287
+ connect,
288
+ reason
289
+ };
290
+ setTransitions((current) => ({
291
+ becameBlockedAt: connect ? current.becameBlockedAt : changedAt,
292
+ becameReadyAt: connect ? changedAt : current.becameReadyAt,
293
+ lastChangedAt: changedAt
294
+ }));
295
+ }, [connect, reason, trackTransitions]);
296
+ return {
297
+ becameBlockedAt: transitions.becameBlockedAt,
298
+ becameReadyAt: transitions.becameReadyAt,
299
+ connect,
300
+ isBlocked,
301
+ isOnline: onlineStatus.isOnline,
302
+ isOnlineSupported: onlineStatus.isSupported,
303
+ isVisibilitySupported: pageVisibility.isSupported,
304
+ isVisible: pageVisibility.isVisible,
305
+ isWaitingForVisibleGrace,
306
+ lastChangedAt: transitions.lastChangedAt,
307
+ reason,
308
+ visibilityState: pageVisibility.visibilityState
309
+ };
310
+ };
75
311
 
76
312
  // src/core/reconnect.ts
77
313
  var DEFAULT_RECONNECT_OPTIONS = {
@@ -209,57 +445,6 @@ var createReconnectAttempt = (attempt, trigger, options, lastDelayMs, config = {
209
445
  };
210
446
  };
211
447
 
212
- // src/core/timers.ts
213
- var sanitizeTimerDelay = (delayMs) => {
214
- if (!Number.isFinite(delayMs)) {
215
- return 0;
216
- }
217
- return Math.max(0, Math.round(delayMs));
218
- };
219
- var createManagedTimeout = () => {
220
- let timeoutId = null;
221
- return {
222
- cancel() {
223
- if (timeoutId !== null) {
224
- clearTimeout(timeoutId);
225
- timeoutId = null;
226
- }
227
- },
228
- isActive() {
229
- return timeoutId !== null;
230
- },
231
- schedule(callback, delayMs) {
232
- if (timeoutId !== null) {
233
- clearTimeout(timeoutId);
234
- }
235
- timeoutId = setTimeout(() => {
236
- timeoutId = null;
237
- callback();
238
- }, sanitizeTimerDelay(delayMs));
239
- }
240
- };
241
- };
242
- var createManagedInterval = () => {
243
- let intervalId = null;
244
- return {
245
- cancel() {
246
- if (intervalId !== null) {
247
- clearInterval(intervalId);
248
- intervalId = null;
249
- }
250
- },
251
- isActive() {
252
- return intervalId !== null;
253
- },
254
- start(callback, intervalMs) {
255
- if (intervalId !== null) {
256
- clearInterval(intervalId);
257
- }
258
- intervalId = setInterval(callback, sanitizeTimerDelay(intervalMs));
259
- }
260
- };
261
- };
262
-
263
448
  // src/hooks/useReconnect.ts
264
449
  var createInitialState = (enabled) => ({
265
450
  attempt: 0,
@@ -1395,9 +1580,11 @@ var useEventSource = (options) => {
1395
1580
  };
1396
1581
  };
1397
1582
 
1583
+ exports.useConnectionGate = useConnectionGate;
1398
1584
  exports.useEventSource = useEventSource;
1399
1585
  exports.useHeartbeat = useHeartbeat;
1400
1586
  exports.useOnlineStatus = useOnlineStatus;
1587
+ exports.usePageVisibility = usePageVisibility;
1401
1588
  exports.useReconnect = useReconnect;
1402
1589
  exports.useWebSocket = useWebSocket;
1403
1590
  //# sourceMappingURL=index.cjs.map