@maxtroost/use-websocket 1.0.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.
@@ -0,0 +1,1122 @@
1
+ import { Store } from '@tanstack/react-store';
2
+ import { Store as Store_2 } from '@tanstack/store';
3
+
4
+ /** Creates the initial state for a WebsocketSubscriptionStore. */
5
+ export declare function createInitialWebsocketSubscriptionStore<TData = unknown>(): WebsocketSubscriptionStore<TData>;
6
+
7
+ /**
8
+ * Structure of incoming WebSocket messages.
9
+ *
10
+ * Messages must have a URI for routing to the correct handler and can include
11
+ * an optional body with the actual message data.
12
+ *
13
+ * @template TBody - The type of the message body payload
14
+ */
15
+ export declare interface IncomingWebsocketMessage<TBody = unknown> {
16
+ /** URI path that identifies which handler should process this message */
17
+ uri: string;
18
+ /** Optional message body/payload */
19
+ body?: TBody;
20
+ /** HTTP-like method for the message (e.g., 'subscribe', 'unsubscribe', 'post') */
21
+ method?: string;
22
+ }
23
+
24
+ /**
25
+ * WebSocket connection ready states.
26
+ *
27
+ * Values match the WebSocket API readyState constants, with an additional
28
+ * UNINSTANTIATED state for connections that haven't been created yet.
29
+ *
30
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
31
+ */
32
+ declare enum ReadyState_2 {
33
+ /** Connection has not been instantiated yet */
34
+ UNINSTANTIATED = -1,
35
+ /** Connection is being established */
36
+ CONNECTING = 0,
37
+ /** Connection is open and ready to communicate */
38
+ OPEN = 1,
39
+ /** Connection is in the process of closing */
40
+ CLOSING = 2,
41
+ /** Connection is closed or couldn't be opened */
42
+ CLOSED = 3
43
+ }
44
+ export { ReadyState_2 as ReadyState }
45
+
46
+ /**
47
+ * Structure for outgoing WebSocket messages.
48
+ *
49
+ * Messages are sent with a method (HTTP-like), URI for routing, optional body,
50
+ * and an automatically generated correlation ID for tracking.
51
+ *
52
+ * @template TMethod - The type of the HTTP method (e.g., 'subscribe', 'unsubscribe', 'post')
53
+ * @template TUri - The type of the URI string
54
+ * @template TBody - The type of the message body payload
55
+ */
56
+ export declare interface SendMessage<TMethod = string, TUri = string, TBody = unknown> {
57
+ /** HTTP-like method for the message (e.g., 'subscribe', 'unsubscribe', 'post') */
58
+ method?: TMethod;
59
+ /** URI path for routing the message to the correct handler */
60
+ uri?: TUri;
61
+ /** Optional message body/payload */
62
+ body?: TBody;
63
+ /** Correlation ID for tracking messages (automatically generated) */
64
+ correlation?: string;
65
+ }
66
+
67
+ /**
68
+ * Options for WebsocketMessageApi.sendMessage.
69
+ */
70
+ export declare interface SendMessageOptions {
71
+ /** Timeout in ms when waiting for a response. Overrides the default from options. */
72
+ timeout?: number;
73
+ }
74
+
75
+ /**
76
+ * Callback function for sending messages from a {@link WebsocketSubscriptionApi} to its parent {@link WebsocketConnection}.
77
+ *
78
+ * This callback is injected by the connection when a URI API is registered,
79
+ * replacing the previous EventTarget/CustomEvent indirection with a direct,
80
+ * type-safe function call.
81
+ */
82
+ export declare type SendToConnectionFn = (message: SendMessage<string, string, unknown>) => void;
83
+
84
+ /**
85
+ * React hook that manages a WebSocket Message API for request/response style messaging.
86
+ *
87
+ * Use this for one-off commands (validate, modify, mark read) rather than streaming
88
+ * subscriptions. Send to any URI; optionally await a response.
89
+ *
90
+ * ## Key Features
91
+ *
92
+ * - **Request/Response**: `sendMessage(uri, method, body?, options?)` returns a Promise that resolves with the response
93
+ * - **Fire-and-forget**: `sendMessageNoWait(uri, method, body?)` for commands that don't need a response
94
+ * - **Any URI**: Not bound to a single URI like subscription APIs
95
+ * - **Shared Instance**: Multiple components with the same `key` share the same Message API
96
+ * - **Automatic Cleanup**: Removes from connection when the last hook unmounts
97
+ *
98
+ * @template TData - The type of data received in the response
99
+ * @template TBody - The type of message body sent to the WebSocket
100
+ *
101
+ * @param options - Configuration options including:
102
+ * - `url`: The WebSocket URL
103
+ * - `key`: Unique identifier (components with same key share the API)
104
+ * - `enabled`: Whether this API is enabled (default: true)
105
+ * - `responseTimeoutMs`: Default timeout for `sendMessage` (default: 5000)
106
+ * - `onError`, `onMessageError`, `onClose`: Optional callbacks
107
+ * @returns {@link WebsocketMessageApiPublic} with `sendMessage`, `sendMessageNoWait`, `reset`, `url`, `key`, `isEnabled`
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const api = useWebsocketMessage<ModifyVoyageUim, ModifyVoyageUim>({
112
+ * key: 'voyages/modify',
113
+ * url: '/api',
114
+ * responseTimeoutMs: 5000
115
+ * });
116
+ *
117
+ * // Await response (full form: uri, method, body?, options?)
118
+ * const result = await api.sendMessage('voyages/modify/validate', 'post', formValues);
119
+ *
120
+ * // Fire-and-forget
121
+ * api.sendMessageNoWait(`notifications/${id}/read`, 'post');
122
+ * ```
123
+ *
124
+ * ## Edge Cases
125
+ *
126
+ * - **Overwrite**: Sending to the same URI while a request is pending cancels the previous
127
+ * request (rejects with "WebSocket request overwritten for URI").
128
+ * - **Disabled**: When `enabled=false`, `sendMessage` rejects; `sendMessageNoWait` is a no-op.
129
+ * - **Connection closed**: Pending requests are rejected with "WebSocket connection closed".
130
+ *
131
+ * @see {@link WebsocketMessageApiPublic} - Public API surface
132
+ */
133
+ export declare const useWebsocketMessage: (options: WebsocketMessageOptions) => WebsocketMessageApiPublic;
134
+
135
+ /**
136
+ * React hook that manages a WebSocket subscription for a specific Subscription endpoint.
137
+ *
138
+ * This hook provides a reactive interface to the WebSocket connection system. It establishes
139
+ * the connection architecture by linking three key components:
140
+ *
141
+ * ## Architecture Overview
142
+ *
143
+ * The hook integrates with a two-layer class architecture:
144
+ *
145
+ * 1. **WebsocketConnection** (singleton per URL)
146
+ * - Manages the underlying WebSocket connection lifecycle
147
+ * - Handles reconnection, heartbeat, and connection state
148
+ * - Routes incoming messages to the appropriate Subscription handlers
149
+ * - Retrieved via `findOrCreateWebsocketConnection()` which ensures only one
150
+ * connection exists per WebSocket URL
151
+ *
152
+ * 2. **WebsocketSubscriptionApi** (one per subscription per connection)
153
+ * - Manages subscription lifecycle for a specific URI endpoint
154
+ * - Provides a TanStack Store for reactive data updates
155
+ * - Handles subscribe/unsubscribe operations
156
+ * - Registered via `connection.addListener(subscriptionApi)` which routes messages by URI
157
+ *
158
+ * ## How the Hook Links to Classes
159
+ *
160
+ * ```
161
+ * useWebsocketSubscription
162
+ * │
163
+ * ├─→ createWebsocketSubscriptionApi(key, options)
164
+ * │ └─→ Returns/creates WebsocketSubscriptionApi singleton (per key)
165
+ * │ ├─→ Manages subscription for this specific URI
166
+ * │ ├─→ Provides reactive store for data updates
167
+ * │ └─→ Handles subscribe/unsubscribe lifecycle
168
+ * │
169
+ * └─→ findOrCreateWebsocketConnection(url, url)
170
+ * └─→ Returns/creates WebsocketConnection singleton (per URL)
171
+ * ├─→ Manages WebSocket connection (connect, reconnect, heartbeat)
172
+ * ├─→ Routes messages to registered listeners
173
+ * └─→ connection.addListener(subscriptionApi) registers the listener
174
+ * ```
175
+ *
176
+ * ## Lifecycle Management
177
+ *
178
+ * - **URI API**: Created once via `useState` initializer (singleton per key via
179
+ * `createWebsocketUriApi`). Multiple components can share the same URI API,
180
+ * tracked via registered hook IDs.
181
+ *
182
+ * - **Connection**: Found or created in a `useIsomorphicLayoutEffect` that watches
183
+ * `enabled`. The connection is a singleton per key, shared across all hooks using
184
+ * the same base URL path.
185
+ *
186
+ * - **Options Updates**: `useIsomorphicLayoutEffect` synchronously updates URI API options
187
+ * via the `options` setter when they change (deep-compared via `useDeepCompareMemoize`),
188
+ * preventing rendering with stale configuration.
189
+ *
190
+ * - **URL Replacement**: A separate `useIsomorphicLayoutEffect` watches `wsUrl` and calls
191
+ * `connection.replaceUrl()` when the URL changes (e.g. due to auth context changes).
192
+ *
193
+ * - **Cleanup**: `useEffect` registers this hook instance as a hook and provides cleanup
194
+ * that removes it. When the last hook is removed, the URI API automatically unsubscribes
195
+ * and is removed from the connection.
196
+ *
197
+ * @template TData - The type of data received from the WebSocket for this URI
198
+ * @template TBody - The type of message body sent to the WebSocket for this URI
199
+ *
200
+ * @param options - Configuration options including:
201
+ * - `url`: The WebSocket URL
202
+ * - `uri`: The specific URI endpoint for this subscription
203
+ * - `key`: Unique identifier for this subscription (used to retrieve it elsewhere via `useWebsocketSubscriptionByKey`)
204
+ * - `enabled`: Whether this subscription is enabled (default: true)
205
+ * - `body`: Optional payload for subscription or initial message
206
+ * - `onMessage`, `onSubscribe`, `onError`, `onMessageError`, `onClose`: Optional callbacks
207
+ * @returns The {@link WebsocketSubscriptionApiPublic} instance. Use `useStore(api.store, (s) => s.message)` to read data reactively.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * // Create subscription and read data via TanStack Store
212
+ * const voyageApi = useWebsocketSubscription<Voyage[], VoyageFilters>({
213
+ * key: 'voyages-list',
214
+ * url: '/api',
215
+ * uri: '/api/voyages',
216
+ * body: { status: 'active' }
217
+ * });
218
+ * const voyages = useStore(voyageApi.store, (s) => s.message);
219
+ *
220
+ * // Or use useWebsocketSubscriptionByKey in children to access the same store
221
+ * const voyagesStore = useWebsocketSubscriptionByKey<Voyage[]>('voyages-list');
222
+ * const voyages = useStore(voyagesStore, (s) => s.message);
223
+ * ```
224
+ *
225
+ * ## Edge Cases
226
+ *
227
+ * - **Multiple initiators**: Using the same `key` in multiple components registers multiple hooks.
228
+ * A console warning is emitted; multiple initiators can cause unexpected behavior.
229
+ * - **pendingSubscription**: Use `store.pendingSubscription` for loading states — it is `true`
230
+ * from subscribe until the first message is received.
231
+ *
232
+ * @see {@link useWebsocketSubscriptionByKey} - Access the store when the subscription is created in a parent
233
+ * @see {@link WebsocketSubscriptionStore} - Store shape: `{ message, subscribed, connected, ... }`
234
+ */
235
+ export declare function useWebsocketSubscription<TData = unknown, TBody = unknown>(options: WebsocketSubscriptionOptions<TData, TBody>): WebsocketSubscriptionApiPublic<TData, TBody>;
236
+
237
+ /**
238
+ * React hook that returns the store of a WebSocket subscription by key.
239
+ *
240
+ * Use when a parent creates the subscription via `useWebsocketSubscription` and
241
+ * children need to read the data. The `key` must match the one used when creating
242
+ * the subscription.
243
+ *
244
+ * **Edge case**: Returns a fallback store (initial empty state) if the subscription
245
+ * does not exist yet (e.g. parent hasn't mounted). This avoids null checks but means
246
+ * children may briefly see empty data before the parent mounts and subscribes.
247
+ *
248
+ * @template TData - The type of data in the store's `message` field
249
+ * @param key - Unique key (must match `useWebsocketSubscription` options.key)
250
+ * @returns TanStack {@link Store} with shape {@link WebsocketSubscriptionStore}
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * // Parent creates subscription
255
+ * useWebsocketSubscription<Voyage[]>({ key: 'voyages-list', url: '...', uri: '...' });
256
+ *
257
+ * // Child reads store by key
258
+ * const voyagesStore = useWebsocketSubscriptionByKey<Voyage[]>('voyages-list');
259
+ * const voyages = useStore(voyagesStore, (s) => s.message);
260
+ * ```
261
+ *
262
+ * @see {@link WebsocketSubscriptionStore} - Store shape
263
+ */
264
+ export declare const useWebsocketSubscriptionByKey: <TData = unknown>(key: string) => Store_2<WebsocketSubscriptionStore<TData>>;
265
+
266
+ /**
267
+ * Manages a WebSocket connection with automatic reconnection, heartbeat monitoring, and URI-based message routing.
268
+ *
269
+ * This class provides:
270
+ * - Automatic reconnection with exponential backoff on connection loss
271
+ * - Heartbeat/ping mechanism to keep connections alive
272
+ * - Multiple URI API registration for routing messages to different handlers
273
+ * - Online/offline detection and handling
274
+ * - Custom logger support for monitoring (configure via {@link setCustomLogger}
275
+ * - User notifications for connection status
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const connection = new WebsocketConnection('ws://example.com/api');
280
+ * const uriApi = new WebsocketSubscriptionApi({
281
+ * key: 'messages',
282
+ * url: '/api',
283
+ * uri: '/messages',
284
+ * onMessage: ({ data }) => console.log('Received:', data),
285
+ * onError: (error) => console.log('Error:', error),
286
+ * onClose: (event) => console.log('Closed:', event)
287
+ * });
288
+ * connection.addListener(uriApi);
289
+ * ```
290
+ *
291
+ * @see {@link websocketConnectionsReconnect} - Reconnect all connections (e.g. on auth change)
292
+ * @see {@link setCustomLogger} - Configure logging and connection-failed callback
293
+ */
294
+ export declare class WebsocketConnection {
295
+ /** Custom logger instance. Use {@link setCustomLogger} to configure. */
296
+ private static _logger;
297
+ /**
298
+ * Sets a custom logger for WebSocket connection events.
299
+ *
300
+ * @param logger - Logger implementation, or `undefined` to clear
301
+ */
302
+ static setCustomLogger(logger: WebsocketLogger | undefined): void;
303
+ /** The underlying WebSocket instance */
304
+ private _socket?;
305
+ /** Map of all listeners (subscription and message APIs) keyed by their unique key */
306
+ private _listeners;
307
+ /** The WebSocket URL */
308
+ private _url;
309
+ /** Display name extracted from URL pathname for user notifications */
310
+ private _name;
311
+ /** Timeout for the next ping message */
312
+ private pingTimeOut;
313
+ /** Timeout for detecting missing pong after ping (dead-connection detection) */
314
+ private pongTimeOut;
315
+ /** Timeout for closing the connection when no URIs are registered */
316
+ private closeConnectionTimeOut;
317
+ /** Counter for reconnection attempts */
318
+ private reconnectTries;
319
+ /** Guard flag that prevents concurrent teardown-and-reconnect cycles (e.g. when both replaceUrl and reconnect fire in the same render). */
320
+ private _isReconnecting;
321
+ /** True when max retry attempts exceeded; stops automatic reconnection until manual retry. */
322
+ private _maxRetriesExceeded;
323
+ /**
324
+ * Queue of non-subscribe messages sent while the socket was not open.
325
+ * Flushed when the connection opens. Subscribe messages are NOT cached — they trigger connect only.
326
+ */
327
+ private cachedMessages;
328
+ /**
329
+ * Creates a new WebSocket connection instance.
330
+ * Note: The connection is not established until a URI API is added.
331
+ *
332
+ * @param url - The WebSocket URL to connect to
333
+ */
334
+ constructor(url: string);
335
+ /**
336
+ * Gets the current ready state of the WebSocket connection.
337
+ *
338
+ * @returns The WebSocket ready state (CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3) or undefined if no socket exists.
339
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState | WebSocket.readyState}
340
+ */
341
+ get readyState(): number | undefined;
342
+ /**
343
+ * Gets the WebSocket URL for this connection.
344
+ *
345
+ * @returns The WebSocket URL string
346
+ */
347
+ get url(): string;
348
+ /**
349
+ * Gets the underlying WebSocket instance.
350
+ *
351
+ * @returns The WebSocket instance if connected, or undefined if the connection hasn't been established yet or has been closed.
352
+ */
353
+ getSocket: () => WebSocket | undefined;
354
+ /**
355
+ * Retrieves a registered subscription API by its unique key.
356
+ * Message APIs are not returned; use {@link getWebsocketMessageApiByKey} for those.
357
+ *
358
+ * @template TData - The type of data stored in the subscription store
359
+ * @param key - The unique key identifying the subscription (must match the key used in {@link addListener})
360
+ * @returns The {@link WebsocketSubscriptionApi} instance if found, or `undefined`
361
+ *
362
+ * @see {@link addListener} - Method to register a listener
363
+ * @see {@link getWebsocketUriApiByKey} - Global function to search across all connections
364
+ */
365
+ getUriApiByKey: <TData = unknown>(key: string) => WebsocketSubscriptionApi<TData, any> | undefined;
366
+ /**
367
+ * Registers a listener (subscription or message API) with this connection.
368
+ *
369
+ * Initiates the WebSocket connection if not already connected. Sets up the send callback
370
+ * so the listener can transmit messages through this connection.
371
+ *
372
+ * If the socket is already open, immediately notifies subscription listeners via `onOpen`.
373
+ *
374
+ * @param listener - The {@link WebsocketListener} to register
375
+ * @returns The registered listener
376
+ */
377
+ addListener: (listener: WebsocketListener) => WebsocketListener;
378
+ /**
379
+ * Unregisters a listener and schedules connection cleanup if no listeners remain.
380
+ *
381
+ * Disconnects the listener's send callback and removes it from the routing map.
382
+ * The WebSocket connection will be closed after {@link CONNECTION_CLEANUP_DELAY} if no other
383
+ * listeners are registered.
384
+ *
385
+ * @param listener - The listener instance to unregister
386
+ */
387
+ removeListener: (listener: WebsocketListener) => void;
388
+ /** Schedules connection close after {@link CONNECTION_CLEANUP_DELAY} when no listeners remain. */
389
+ private scheduleConnectionCleanup;
390
+ /**
391
+ * Replaces the WebSocket URL and re-establishes the connection.
392
+ *
393
+ * Closes the current connection, resets all listeners, and reconnects using the new URL
394
+ * after a short delay (1 second) to allow cleanup to complete.
395
+ *
396
+ * @param newUrl - The new WebSocket URL to connect to
397
+ */
398
+ replaceUrl: (newUrl: string) => Promise<void>;
399
+ /**
400
+ * Reconnects the WebSocket connection.
401
+ *
402
+ * Tears down the current connection by removing all event listeners, closing the socket,
403
+ * and resetting all registered URI APIs. After a short delay (1 second) to allow cleanup,
404
+ * re-establishes the connection. Typically triggered by {@link websocketConnectionsReconnect}
405
+ * when {@link useReconnectWebsocketConnections} (from @mono-fleet/common-components) detects
406
+ * the user's authentication context (region/role) change.
407
+ *
408
+ * Guarded by {@link _isReconnecting} in {@link teardownAndReconnect} — if a `replaceUrl`
409
+ * layout effect already started a reconnect cycle in the same render, this call is a no-op.
410
+ */
411
+ reconnect: () => Promise<void>;
412
+ /**
413
+ * Resets the retry counter and re-establishes the connection.
414
+ *
415
+ * Used when the user manually retries after hitting {@link RECONNECTION_CONFIG.MAX_RETRY_ATTEMPTS}.
416
+ * Clears the max-retries-exceeded state and initiates a fresh connection attempt.
417
+ */
418
+ resetRetriesAndReconnect: () => void;
419
+ /**
420
+ * Establishes the WebSocket connection if not already connecting or connected.
421
+ * Only creates a socket if at least one registered listener (subscription or message API) is enabled.
422
+ * Sets up all event listeners and logs the connection attempt via the custom logger if configured.
423
+ */
424
+ private connect;
425
+ /**
426
+ * Tears down the current socket: clears all timers, removes all event listeners,
427
+ * closes the socket, and resets the socket reference.
428
+ */
429
+ private teardownSocket;
430
+ /**
431
+ * Tears down the current connection, resets all listeners, waits for cleanup to complete,
432
+ * and re-establishes the connection. Shared by {@link replaceUrl} and {@link reconnect}.
433
+ *
434
+ * Guarded by {@link _isReconnecting} to prevent concurrent cycles. When
435
+ * `selectedRegionRole` changes, both the hook's `replaceUrl` layout effect and
436
+ * `useReconnectWebsocketConnections`'s reconnect effect may fire. Because layout effects run
437
+ * before regular effects, `replaceUrl` wins and updates the URL first; the reconnect call
438
+ * is safely skipped.
439
+ */
440
+ private teardownAndReconnect;
441
+ /**
442
+ * Cleans up the WebSocket connection when no listeners are registered.
443
+ */
444
+ private cleanupConnection;
445
+ /**
446
+ * Clears all active timers (ping heartbeat, pong timeout, and connection cleanup).
447
+ */
448
+ private clearAllTimers;
449
+ /**
450
+ * Removes all event listeners from the WebSocket and window objects.
451
+ * Used during cleanup and reconnection processes.
452
+ */
453
+ private removeListeners;
454
+ /**
455
+ * Attempts to reconnect the WebSocket connection with exponential backoff.
456
+ * Shows user notifications after the threshold number of attempts.
457
+ * Only attempts reconnection when the browser is online.
458
+ * When closeCode is 1013 (Try Again Later), waits an extra delay before reconnecting.
459
+ * Stops after {@link RECONNECTION_CONFIG.MAX_RETRY_ATTEMPTS} and shows a permanent error with a manual retry button.
460
+ *
461
+ * @param closeCode - Optional WebSocket close code; used to apply TRY_AGAIN_LATER delay when 1013
462
+ */
463
+ private attemptReconnection;
464
+ /**
465
+ * Checks if the browser is offline and, if so, defers reconnection until it comes back online
466
+ * by registering a one-time 'online' event listener.
467
+ *
468
+ * @returns `true` if reconnection was deferred (browser is offline), `false` if browser is online
469
+ */
470
+ private deferReconnectionUntilOnline;
471
+ /**
472
+ * Handles WebSocket close events.
473
+ *
474
+ * Implements automatic reconnection for any non-intentional close (anything other than
475
+ * 1000 Normal Closure). This includes 1001 Going Away, 1011 Internal Error, 1012 Service
476
+ * Restart, 1013 Try Again Later, 1006 Abnormal Closure, and other server-initiated codes.
477
+ * Reconnection only occurs when listeners are still registered. Shows user notifications
478
+ * after {@link RECONNECTION_CONFIG.NOTIFICATION_THRESHOLD} failed attempts.
479
+ * Cleans up the connection if no listeners remain. Logs the close event via the custom logger if configured.
480
+ *
481
+ * @param event - The WebSocket close event containing code, reason, and whether the close was clean
482
+ */
483
+ private handleClose;
484
+ /**
485
+ * Handles WebSocket open/connected events.
486
+ *
487
+ * Sets up offline detection, dismisses reconnection notifications, shows success message
488
+ * for recovered connections (only if {@link RECONNECTION_CONFIG.NOTIFICATION_THRESHOLD}
489
+ * was exceeded), resets reconnection counter, notifies all listeners, flushes cached
490
+ * messages, and initiates the heartbeat ping sequence.
491
+ */
492
+ private handleOpen;
493
+ /**
494
+ * Handles incoming WebSocket messages.
495
+ *
496
+ * Routes messages to matching listeners: subscription APIs by URI, message APIs by pending request URI.
497
+ * Special handling for 'ping' messages to maintain heartbeat.
498
+ * Dispatches error-method messages to listener error handlers.
499
+ *
500
+ * @param event - The WebSocket message event containing JSON data
501
+ */
502
+ private handleMessage;
503
+ /**
504
+ * Handles WebSocket error events.
505
+ * Logs the error via the custom logger if configured and notifies all registered listeners.
506
+ *
507
+ * @param event - The WebSocket error event
508
+ */
509
+ private handleError;
510
+ /**
511
+ * Handles browser coming back online during offline detection.
512
+ * Removes the online listener and re-establishes the connection.
513
+ */
514
+ private handleOnline;
515
+ /**
516
+ * Handles browser coming back online during reconnection attempts.
517
+ * Removes the online listener and resumes reconnection with a decremented counter
518
+ * to avoid adding extra wait time from being offline.
519
+ */
520
+ private handleOnlineForReconnection;
521
+ /**
522
+ * Handles browser going offline.
523
+ *
524
+ * Notifies all listeners of the closure, tears down the socket, and sets up
525
+ * a listener to reconnect when the browser comes back online.
526
+ */
527
+ private handleOffline;
528
+ /**
529
+ * Handles outgoing messages from listeners.
530
+ *
531
+ * - If socket is OPEN: serializes with correlation ID and sends immediately.
532
+ * - If socket is not open: subscribe messages trigger connect only; other messages are
533
+ * cached and sent when the connection opens.
534
+ *
535
+ * Passed to each listener via {@link WebsocketListener.setSendToConnection}.
536
+ */
537
+ private handleSendMessage;
538
+ /**
539
+ * Sends a heartbeat ping message to keep the connection alive and detect disconnections.
540
+ * Sets a pong timeout; if no pong arrives within HEARTBEAT_CONFIG.PONG_TIMEOUT_MS,
541
+ * the connection is force-closed to trigger reconnection.
542
+ */
543
+ private sendPing;
544
+ /**
545
+ * Clears the pong timeout (e.g. when a pong is received).
546
+ */
547
+ private clearPongTimeout;
548
+ /**
549
+ * Schedules a timeout to detect missing pong. If no pong arrives within
550
+ * {@link HEARTBEAT_CONFIG.PONG_TIMEOUT_MS}, force-closes the socket to trigger reconnection.
551
+ */
552
+ private schedulePongTimeout;
553
+ /**
554
+ * Schedules the next heartbeat ping after the configured interval (40 seconds).
555
+ * @see {@link getPingTime}
556
+ */
557
+ private schedulePing;
558
+ /**
559
+ * Serializes a message with a unique correlation ID for WebSocket transmission.
560
+ * @param message - The message to serialize
561
+ * @returns JSON string for WebSocket send
562
+ */
563
+ private serializeMessage;
564
+ /**
565
+ * Executes a callback for each listener that matches the given URI.
566
+ *
567
+ * - **Subscription listeners**: Match when `listener.uri === uri`
568
+ * - **Message listeners**: Match when `listener.hasWaitingUri(uri)` (pending request/response)
569
+ *
570
+ * A single message can be delivered to multiple listeners if both a subscription
571
+ * and a message API are waiting for the same URI.
572
+ *
573
+ * @param uri - The URI from the incoming message
574
+ * @param callback - Callback invoked for each matching listener
575
+ */
576
+ private forEachMatchingListener;
577
+ }
578
+
579
+ export declare const websocketConnectionsReconnect: () => void;
580
+
581
+ /**
582
+ * Common interface for WebSocket listeners registered with {@link WebsocketConnection}.
583
+ *
584
+ * Both {@link WebsocketSubscriptionApi} and {@link WebsocketMessageApi} implement this interface,
585
+ * allowing the connection to treat them uniformly via {@link addListener} / {@link removeListener}.
586
+ *
587
+ * - **Subscription listeners**: Have `uri`, `onOpen`, `onMessage` — route by URI match
588
+ * - **Message listeners**: Have `hasWaitingUri`, `deliverMessage` — route by pending URI
589
+ */
590
+ export declare interface WebsocketListener {
591
+ readonly key: string;
592
+ readonly url: string;
593
+ readonly isEnabled: boolean;
594
+ setSendToConnection(callback: SendToConnectionFn | null): void;
595
+ onError(error: WebsocketTransportError): void;
596
+ onMessageError(error: WebsocketServerError<unknown>): void;
597
+ onClose(event: CloseEvent): void;
598
+ reset(): void;
599
+ /** Subscription listeners: fixed URI for this endpoint */
600
+ readonly uri?: string;
601
+ /** Subscription listeners: called when connection opens */
602
+ onOpen?(): void;
603
+ /** Subscription listeners: called when a message is received for this URI */
604
+ onMessage?(data: unknown): void;
605
+ /** Message listeners: returns true if waiting for a response for the given URI */
606
+ hasWaitingUri?(uri: string): boolean;
607
+ /** Message listeners: delivers a response for a pending request */
608
+ deliverMessage?(uri: string, data: unknown): void;
609
+ }
610
+
611
+ /**
612
+ * Optional custom logger for WebSocket connection events.
613
+ * Set via {@link WebsocketConnection.setCustomLogger}.
614
+ */
615
+ export declare interface WebsocketLogger {
616
+ /** Logs connection events (e.g. ws-connect, ws-close, ws-error, ws-reconnect) */
617
+ log?(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: Record<string, unknown>): void;
618
+ /** Called when max retry attempts exceeded; use to trigger token refresh or other recovery */
619
+ connectionFailed?: (url: string, retries: number, subscriptions: number) => void;
620
+ }
621
+
622
+ /**
623
+ * Manages WebSocket request/response messaging without subscription.
624
+ *
625
+ * Use for one-off commands (validate, modify, mark read) rather than streaming.
626
+ * Send to any URI; optionally await a response. Tracks URIs only while waiting.
627
+ *
628
+ * ## Key Features
629
+ *
630
+ * - **Any URI**: Not bound to a single URI like {@link WebsocketSubscriptionApi}
631
+ * - **Request/Response**: `sendMessage` returns a Promise; optional per-call timeout
632
+ * - **Fire-and-forget**: `sendMessageNoWait` for commands that don't need a response
633
+ * - **No Subscription**: Use WebsocketSubscriptionApi for streaming data
634
+ *
635
+ * ## Edge Cases
636
+ *
637
+ * - **Overwrite**: Sending to the same URI while a request is pending cancels the previous
638
+ * request — the previous Promise rejects with "WebSocket request overwritten for URI".
639
+ * - **Disabled**: When `enabled=false`, `sendMessage` rejects; `sendMessageNoWait` is a no-op.
640
+ * - **Connection closed**: All pending requests reject with "WebSocket connection closed".
641
+ * - **Queued messages**: If the connection is not yet open, messages are queued and sent
642
+ * when the connection opens (via `setSendToConnection`).
643
+ *
644
+ * ## Cleanup
645
+ *
646
+ * {@link reset} is called by WebsocketConnection when the URL changes or during reconnection.
647
+ * When the last hook unmounts, {@link unregisterHook} triggers removal after
648
+ * {@link INITIATOR_REMOVAL_DELAY_MS}.
649
+ *
650
+ * @template TData - The type of data received in the response
651
+ * @template TBody - The type of message body sent to the WebSocket
652
+ *
653
+ * @example
654
+ * ```typescript
655
+ * const api = new WebsocketMessageApi<MyResponse, MyRequest>({
656
+ * url: 'wss://example.com',
657
+ * key: 'my-message-api',
658
+ * responseTimeoutMs: 5000
659
+ * });
660
+ * connection.addListener(api);
661
+ *
662
+ * const response = await api.sendMessage('/api/command', 'post', { action: 'refresh' });
663
+ * ```
664
+ */
665
+ export declare class WebsocketMessageApi implements WebsocketListener {
666
+ private _options;
667
+ private _sendToConnection;
668
+ private _pendingByUri;
669
+ private _pendingMessages;
670
+ private _registeredHooks;
671
+ private _hookRemovalTimeout;
672
+ constructor(options: WebsocketMessageOptions);
673
+ /** Unique key identifier for this Message API. */
674
+ get key(): string;
675
+ get url(): string;
676
+ /** Whether this Message API is enabled. */
677
+ get isEnabled(): boolean;
678
+ /**
679
+ * Returns whether this API is waiting for a response for the given URI.
680
+ *
681
+ * Used by {@link WebsocketConnection} to route incoming messages to the correct
682
+ * listener. Message API receives messages only for URIs with pending requests.
683
+ *
684
+ * @param uri - The URI to check
685
+ * @returns `true` if a request is pending for this URI
686
+ */
687
+ hasWaitingUri: (uri: string) => boolean;
688
+ /**
689
+ * Registers a hook (component) that is using this Message API.
690
+ *
691
+ * Tracks the hook ID so the API is only removed from the connection when
692
+ * the last hook unmounts.
693
+ *
694
+ * @param id - Unique identifier for the registering hook
695
+ */
696
+ registerHook: (id: string) => void;
697
+ /**
698
+ * Unregisters a hook from this Message API.
699
+ *
700
+ * After {@link INITIATOR_REMOVAL_DELAY_MS}, if no hooks remain, invokes the
701
+ * cleanup callback to remove this API from the connection. The delay prevents
702
+ * rapid subscribe/unsubscribe cycles during React re-renders.
703
+ *
704
+ * @param id - The hook ID to unregister
705
+ * @param onRemove - Callback invoked when the last hook is removed (after delay)
706
+ */
707
+ unregisterHook: (id: string, onRemove: () => void) => void;
708
+ /**
709
+ * Disconnects this Message API from the parent WebSocket connection.
710
+ *
711
+ * Called when the hook is disabled. After a delay, invokes the cleanup callback.
712
+ * Clears any pending hook-removal timeout to avoid duplicate cleanup.
713
+ *
714
+ * @param onRemoveFromSocket - Callback invoked after delay to remove from connection
715
+ */
716
+ disconnect: (onRemoveFromSocket: () => void) => void;
717
+ /**
718
+ * Sets or clears the callback used to send messages through the parent WebSocket connection.
719
+ *
720
+ * When setting a callback, flushes any queued messages. When clearing, cancels all
721
+ * pending requests and clears the hook removal timeout to avoid redundant cleanup.
722
+ *
723
+ * @param callback - The send function, or null to disconnect
724
+ */
725
+ setSendToConnection: (callback: SendToConnectionFn | null) => void;
726
+ /**
727
+ * Delivers an incoming message for a URI we're waiting on.
728
+ *
729
+ * Called by WebsocketConnection when a message arrives for a URI with a pending request.
730
+ *
731
+ * @param uri - The URI the response is for
732
+ * @param data - The response data
733
+ */
734
+ deliverMessage: (uri: string, data: unknown) => void;
735
+ /**
736
+ * Sends a message to the given URI and optionally waits for a response.
737
+ *
738
+ * **Overwrite behavior**: If a request is already pending for this URI, it is
739
+ * cancelled (rejected with "WebSocket request overwritten for URI") and replaced.
740
+ *
741
+ * @param uri - The URI to send the message to
742
+ * @param bodyOrMethod - Message body (short form) or HTTP method (full form)
743
+ * @param bodyOrOptions - Message body or options (full form)
744
+ * @param options - Per-call options when using full signature
745
+ * @returns Promise that resolves with the response data; rejects on timeout, overwrite, or disabled
746
+ *
747
+ * @example
748
+ * await api.sendMessage('/api/command', 'post', { action: 'refresh' });
749
+ * await api.sendMessage('/api/command', 'post', { action: 'refresh' }, { timeout: 5000 });
750
+ */
751
+ sendMessage<TData = unknown, TBody = unknown>(uri: string, method: string, body?: TBody, options?: SendMessageOptions): Promise<TData>;
752
+ /**
753
+ * Sends a message without waiting for a response (fire-and-forget).
754
+ *
755
+ * @param uri - The URI to send the message to
756
+ * @param methodOrBody - HTTP method (full form) or message body (short form)
757
+ * @param body - Message body when using full form
758
+ *
759
+ * @example
760
+ * api.sendMessageNoWait('/api/log', 'post', { event: 'click' });
761
+ */
762
+ sendMessageNoWait<TBody = unknown>(uri: string, method: string, body?: TBody): void;
763
+ /** @inheritdoc */
764
+ onError: (error: WebsocketTransportError) => void;
765
+ /** @inheritdoc */
766
+ onMessageError: (error: WebsocketServerError) => void;
767
+ /** @inheritdoc */
768
+ onClose: (event: CloseEvent) => void;
769
+ /**
770
+ * Resets this Message API, cancelling all pending requests.
771
+ *
772
+ * Called by WebsocketConnection when the URL changes or during reconnection.
773
+ * Clears the hook removal timeout to prevent stale cleanup callbacks.
774
+ */
775
+ reset: () => void;
776
+ private _clearHookRemovalTimeout;
777
+ private _scheduleHookRemoval;
778
+ private _flushPendingMessages;
779
+ private _sendOrQueue;
780
+ private _cancelPendingForUri;
781
+ private _cancelAllPending;
782
+ }
783
+
784
+ export declare type WebsocketMessageApiPublic = Pick<WebsocketMessageApi, 'sendMessage' | 'sendMessageNoWait' | 'reset' | 'url' | 'key' | 'isEnabled'>;
785
+
786
+ /**
787
+ * Configuration options for WebSocket Message API.
788
+ *
789
+ * Message API is for request/response style communication: send a message to any URI
790
+ * and optionally wait for a response. No subscription support.
791
+ *
792
+ * @template TData - The type of data received in the response
793
+ * @template TBody - The type of message body sent to the WebSocket
794
+ */
795
+ export declare interface WebsocketMessageOptions {
796
+ /** The base URL of the WebSocket connection. */
797
+ url: string;
798
+ /**
799
+ * Unique key for the Message API.
800
+ *
801
+ * Used to identify the API in the connection.
802
+ */
803
+ key: string;
804
+ /** Whether this Message API is enabled (default: true). When disabled, messages are not sent. */
805
+ enabled?: boolean;
806
+ /**
807
+ * Default timeout in ms when waiting for a response.
808
+ *
809
+ * Can be overridden per sendMessage call.
810
+ */
811
+ responseTimeoutMs?: number;
812
+ /**
813
+ * Callback invoked when a WebSocket transport error occurs.
814
+ */
815
+ onError?: (error: WebsocketTransportError) => void;
816
+ /**
817
+ * Callback invoked when a server error message is received.
818
+ */
819
+ onMessageError?: (error: WebsocketServerError) => void;
820
+ /**
821
+ * Callback invoked when the WebSocket connection closes.
822
+ */
823
+ onClose?: (event: CloseEvent) => void;
824
+ }
825
+
826
+ /**
827
+ * Error sent by the server via a message with method 'error', 'conflict', or 'exception'.
828
+ * Contains the parsed message body for application-level error handling.
829
+ *
830
+ * @template TBody - The type of the error body payload
831
+ */
832
+ export declare interface WebsocketServerError<TBody = unknown> {
833
+ readonly type: 'server';
834
+ readonly message: IncomingWebsocketMessage<TBody>;
835
+ }
836
+
837
+ /**
838
+ * Manages a single WebSocket URI endpoint with subscription lifecycle and message handling.
839
+ *
840
+ * Use for streaming data (voyage list, notifications). Provides a TanStack Store for
841
+ * reactive updates. Multiple components share one instance via a unique key.
842
+ *
843
+ * ## Key Features
844
+ *
845
+ * - **Reactive Store**: TanStack Store updates when messages are received
846
+ * - **pendingSubscription**: `true` from subscribe until first message (use for loading states)
847
+ * - **Auto-subscribe**: Subscribes when the WebSocket connection opens
848
+ * - **Hook Tracking**: Tracks components; unsubscribes when the last hook unmounts
849
+ *
850
+ * ## Edge Cases
851
+ *
852
+ * - **Multiple initiators**: Using the same key in multiple components emits a console warning;
853
+ * multiple initiators can cause unexpected behavior.
854
+ * - **Body change in subscribe-on-open**: When `options.body` changes, re-subscribes automatically.
855
+ * - **enabled=false**: Unsubscribes and disconnects; re-enabling triggers subscribe.
856
+ * - **reset**: Called by connection on URL change/reconnect; clears store state.
857
+ *
858
+ * ## Cleanup
859
+ *
860
+ * {@link reset} is called by WebsocketConnection on URL change or reconnection.
861
+ * {@link unregisterHook} triggers removal after {@link INITIATOR_REMOVAL_DELAY_MS}.
862
+ *
863
+ * @template TData - The type of data received from the WebSocket
864
+ * @template TBody - The type of message body sent
865
+ *
866
+ * @example
867
+ * ```typescript
868
+ * const api = new WebsocketSubscriptionApi<MyData, MyBody>({
869
+ * url: 'wss://example.com',
870
+ * uri: '/api/stream',
871
+ * key: 'my-stream-key',
872
+ * body: { filter: 'active' },
873
+ * onMessage: (data) => console.log('Received:', data)
874
+ * });
875
+ *
876
+ * const data = useStore(api.store, (s) => s.message);
877
+ * const isPending = useStore(api.store, (s) => s.pendingSubscription);
878
+ * api.sendMessage({ method: 'refresh', body: { force: true } });
879
+ * ```
880
+ *
881
+ * @see {@link useWebsocketSubscription} - React hook
882
+ * @see {@link WebsocketConnection} - Connection manager
883
+ */
884
+ export declare class WebsocketSubscriptionApi<TData = unknown, TBody = unknown> implements WebsocketListener {
885
+ private _options;
886
+ private _state;
887
+ private _registeredHooks;
888
+ private _disconnectTimeout;
889
+ private _hookRemovalTimeout;
890
+ private _sendToConnection;
891
+ private _pendingMessages;
892
+ constructor(options: WebsocketSubscriptionOptions<TData, TBody>);
893
+ /** Unique key identifier for this WebSocket URI API. */
894
+ get key(): string;
895
+ /** URI path for this WebSocket subscription. */
896
+ get uri(): string;
897
+ get url(): string;
898
+ /** Configuration options for this WebSocket URI. */
899
+ get options(): WebsocketSubscriptionOptions<TData, TBody>;
900
+ /**
901
+ * Current data from the store.
902
+ *
903
+ * **Do not use in React components** — it does not trigger re-renders. Use
904
+ * `useStore(api.store, (s) => s.message)` for reactive updates.
905
+ */
906
+ get data(): TData | undefined;
907
+ /** TanStack store containing subscription state (message, subscribed, connected, pendingSubscription, etc.). */
908
+ get store(): Store<WebsocketSubscriptionStore<TData>>;
909
+ /** Whether this WebSocket URI is enabled. */
910
+ get isEnabled(): boolean;
911
+ /**
912
+ * Updates the configuration options for this subscription.
913
+ *
914
+ * Handles lifecycle changes:
915
+ * - **Body change** (subscribe-on-open): Re-subscribes with new body
916
+ * - **Enabled: false → true**: Subscribes
917
+ * - **Enabled: true → false**: Unsubscribes
918
+ *
919
+ * Uses deep equality to skip no-op updates.
920
+ *
921
+ * @param options - New options (merged with existing)
922
+ */
923
+ set options(options: WebsocketSubscriptionOptions<TData, TBody>);
924
+ /**
925
+ * Sets or clears the callback used to send messages through the parent WebSocket connection.
926
+ *
927
+ * When clearing, flushes pending messages and clears removal timeouts to avoid redundant cleanup.
928
+ *
929
+ * @param callback - The send function, or null to disconnect
930
+ */
931
+ setSendToConnection: (callback: SendToConnectionFn | null) => void;
932
+ /**
933
+ * Registers a hook (component) that is using this subscription.
934
+ *
935
+ * Clears pending removal/disconnect timeouts and tracks the hook ID.
936
+ * Emits a console warning if more than one hook is registered (multiple initiators).
937
+ *
938
+ * @param id - Unique identifier for the registering hook
939
+ */
940
+ registerHook: (id: string) => void;
941
+ /**
942
+ * Unregisters a hook from this subscription.
943
+ *
944
+ * After {@link INITIATOR_REMOVAL_DELAY_MS}, if no hooks remain, unsubscribes
945
+ * and invokes the cleanup callback. The delay prevents rapid subscribe/unsubscribe
946
+ * during React re-renders.
947
+ *
948
+ * @param id - The hook ID to unregister
949
+ * @param onRemove - Callback invoked when the last hook is removed (after delay)
950
+ */
951
+ unregisterHook: (id: string, onRemove: () => void) => void;
952
+ /**
953
+ * Disconnects this subscription from the parent WebSocket connection.
954
+ *
955
+ * Immediately unsubscribes, then after {@link INITIATOR_REMOVAL_DELAY_MS} invokes
956
+ * the cleanup callback. Called when the hook is disabled (`enabled=false`).
957
+ *
958
+ * @param onRemoveFromSocket - Callback invoked after delay to remove from connection
959
+ */
960
+ disconnect: (onRemoveFromSocket: () => void) => void;
961
+ /**
962
+ * Resets this subscription to its initial state.
963
+ *
964
+ * Clears connection/subscription state, resets store data, and cancels pending timeouts.
965
+ * Only runs when currently connected. Called by WebsocketConnection on URL change
966
+ * or reconnection.
967
+ */
968
+ reset: () => void;
969
+ /**
970
+ * Sends a custom message through the WebSocket for this URI.
971
+ *
972
+ * Automatically appends the URI and method. Queues if connection not yet set.
973
+ *
974
+ * @param message - The message to send (uri and method may be overridden)
975
+ */
976
+ sendMessage: (message: SendMessage<string, string, TBody>) => void;
977
+ /**
978
+ * Subscribes to this WebSocket URI to start receiving messages.
979
+ *
980
+ * Only subscribes when enabled. Sends a 'subscribe' message through the parent connection.
981
+ *
982
+ * @param body - Optional body to send with the subscription
983
+ */
984
+ subscribe: (body?: TBody) => void;
985
+ /**
986
+ * Unsubscribes from this WebSocket URI to stop receiving messages.
987
+ *
988
+ * Only unsubscribes when currently subscribed.
989
+ */
990
+ unsubscribe: () => void;
991
+ /**
992
+ * Called by WebsocketConnection when the WebSocket connection opens.
993
+ *
994
+ * Subscribes with the configured body.
995
+ */
996
+ onOpen: () => void;
997
+ /**
998
+ * Called by WebsocketConnection when a message is received for this URI.
999
+ *
1000
+ * @param data - The message data
1001
+ */
1002
+ onMessage: (data: TData) => void;
1003
+ /** @inheritdoc */
1004
+ onError: (error: WebsocketTransportError) => void;
1005
+ /**
1006
+ * Called by WebsocketConnection when a server error message is received.
1007
+ *
1008
+ * @param error - Server error with parsed message body
1009
+ */
1010
+ onMessageError: (error: WebsocketServerError<TBody>) => void;
1011
+ /**
1012
+ * Called by WebsocketConnection when the WebSocket connection closes.
1013
+ *
1014
+ * Resets subscription state to ensure a fresh subscription on reconnect.
1015
+ */
1016
+ onClose: (event: CloseEvent) => void;
1017
+ private _clearPendingTimeouts;
1018
+ private _scheduleHookRemoval;
1019
+ private _flushPendingMessages;
1020
+ private _sendOrQueue;
1021
+ private _handleSubscriptionUpdates;
1022
+ private _handleUnsubscribeOnDisable;
1023
+ }
1024
+
1025
+ export declare type WebsocketSubscriptionApiPublic<TData = unknown, TBody = unknown> = Pick<WebsocketSubscriptionApi<TData, TBody>, 'reset' | 'url' | 'key' | 'isEnabled' | 'store'>;
1026
+
1027
+ /**
1028
+ * Configuration options for WebSocket URI APIs.
1029
+ *
1030
+ * Subscriptions automatically subscribe when the WebSocket connection opens.
1031
+ *
1032
+ * @template TData - The type of data received from the WebSocket
1033
+ * @template TBody - The type of message body sent to the WebSocket
1034
+ */
1035
+ export declare interface WebsocketSubscriptionOptions<TData = unknown, TBody = unknown> {
1036
+ /** The base URL of the WebSocket connection. */
1037
+ url: string;
1038
+ /** The URI path for this subscription. */
1039
+ uri: string;
1040
+ /**
1041
+ * Unique key for the URI API.
1042
+ *
1043
+ * Used to identify the URI API in the connection.
1044
+ */
1045
+ key: string;
1046
+ /** Whether this URI API is enabled (default: true). When disabled, messages are not sent. */
1047
+ enabled?: boolean;
1048
+ /** Optional body payload to send with subscription or initial message */
1049
+ body?: TBody;
1050
+ /** Optional HTTP method for custom messages sent via sendMessage */
1051
+ method?: string;
1052
+ /**
1053
+ * Callback invoked when subscription is successful.
1054
+ *
1055
+ * @param uri - The URI path that was subscribed to
1056
+ * @param body - The body that was sent with the subscription
1057
+ */
1058
+ onSubscribe?: (props: {
1059
+ uri: string;
1060
+ body?: TBody;
1061
+ uriApi: WebsocketSubscriptionApi<TData, TBody>;
1062
+ }) => void;
1063
+ /**
1064
+ * Callback invoked when a message is received for this URI.
1065
+ *
1066
+ * @param data - The message data received from the WebSocket
1067
+ * @param uriApi - The URI API instance that received the message
1068
+ */
1069
+ onMessage?: (props: {
1070
+ data: TData;
1071
+ uriApi: WebsocketSubscriptionApi<TData, TBody>;
1072
+ }) => void;
1073
+ /**
1074
+ * Callback invoked when a WebSocket error occurs.
1075
+ *
1076
+ * @param error - Discriminated error: use `error.type === 'server'` for server-sent error messages
1077
+ * (parsed body in `error.message`), or `error.type === 'transport'` for connection failures.
1078
+ */
1079
+ onError?: (error: WebsocketTransportError) => void;
1080
+ /**
1081
+ * A callback function called when an error occurs with a subscription or message.
1082
+ *
1083
+ * @param event - The error event object.
1084
+ */
1085
+ onMessageError?: (error: WebsocketServerError<TBody>) => void;
1086
+ /**
1087
+ * Callback invoked when the WebSocket connection closes.
1088
+ *
1089
+ * @param event - The close event from the WebSocket connection
1090
+ */
1091
+ onClose?: (event: CloseEvent) => void;
1092
+ }
1093
+
1094
+ export declare interface WebsocketSubscriptionStore<TData = unknown> {
1095
+ message: TData | undefined;
1096
+ subscribed: boolean;
1097
+ /**
1098
+ * Whether a subscription has been sent but no response received yet.
1099
+ *
1100
+ * - `true`: A subscribe message was sent and we are waiting for the first (or next) message.
1101
+ * - `false`: No subscription is active, the connection is closed, or we have already received a response.
1102
+ *
1103
+ * Use this to show loading/placeholder UI while waiting for initial data after subscribing.
1104
+ */
1105
+ pendingSubscription: boolean;
1106
+ subscribedAt: number | undefined;
1107
+ receivedAt: number | undefined;
1108
+ connected: boolean;
1109
+ messageError: WebsocketTransportError | undefined;
1110
+ serverError: WebsocketServerError<unknown> | undefined;
1111
+ }
1112
+
1113
+ /**
1114
+ * Error from the WebSocket transport layer (connection failure, network issues, etc.).
1115
+ * Contains the raw Event from the WebSocket 'error' handler.
1116
+ */
1117
+ export declare interface WebsocketTransportError {
1118
+ readonly type: 'transport';
1119
+ readonly event: Event;
1120
+ }
1121
+
1122
+ export { }