@maxtroost/use-websocket 1.0.0 → 1.1.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 +224 -237
- package/dist/index.d.ts +319 -46
- package/dist/index.js +236 -221
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -1,238 +1,179 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @maxtroost/use-websocket
|
|
2
2
|
|
|
3
3
|
A robust WebSocket connection management package for React applications with automatic reconnection, heartbeat monitoring, URI-based message routing, and React integration via TanStack Store.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @
|
|
8
|
+
npm install @maxtroost/use-websocket
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Peer dependencies:** React 18+, React DOM 18+
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Message Format
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
- [Code Structure](#-code-structure)
|
|
17
|
-
- [Data Flow & Architecture](#-data-flow--architecture)
|
|
18
|
-
- [Key Behaviors](#-key-behaviors)
|
|
19
|
-
- [Usage & Integration](#-usage--integration)
|
|
20
|
-
- [Testing Strategy](#-testing-strategy)
|
|
21
|
-
- [Troubleshooting & Debugging](#-troubleshooting--debugging)
|
|
22
|
-
- [Dependencies](#-dependencies)
|
|
15
|
+
All outgoing WebSocket messages share the same structure and are sent as JSON:
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"method": "subscribe" | "unsubscribe" | "post",
|
|
20
|
+
"uri": "/path/to/endpoint",
|
|
21
|
+
"body": { ... }
|
|
22
|
+
}
|
|
23
|
+
```
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
| Field | Required | Description |
|
|
26
|
+
| ------- | -------- | --------------------------------------------------------------------------- |
|
|
27
|
+
| `method`| Optional | HTTP-like method: `subscribe`, `unsubscribe`, or `post` (default for custom messages) |
|
|
28
|
+
| `uri` | Yes | Path for routing; the server uses this to dispatch to the correct handler |
|
|
29
|
+
| `body` | Optional | Payload sent with the message |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
**Examples:**
|
|
31
32
|
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
34
|
-
- **
|
|
35
|
-
- **Subscription sharing**: Multiple components can share the same subscription via a unique key
|
|
36
|
-
- **Auth-aware URLs**: WebSocket URLs are built from the current auth context (region, role, user)
|
|
33
|
+
- **Subscribe** (streaming): `{ "method": "subscribe", "uri": "/notifications", "body": { "status": "active" } }`
|
|
34
|
+
- **Unsubscribe**: `{ "method": "unsubscribe", "uri": "/notifications" }`
|
|
35
|
+
- **Request/response** (e.g. validate, mark read): `{ "method": "post", "uri": "/voyages/modify/validate", "body": { ... } }`
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
You can add extra fields (e.g. auth headers) via `transformMessagePayload` in `WebsocketClient`.
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
| ---------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
42
|
-
| **Singleton Connection** | One connection per URL shared across all hooks |
|
|
43
|
-
| **Key-Based API Management** | Subscription and Message APIs identified by unique keys; components with the same key share the instance |
|
|
44
|
-
| **Automatic Reconnection** | Three-phase exponential backoff (4s → 30s → 90s) |
|
|
45
|
-
| **Heartbeat Monitoring** | Ping/pong every 40 seconds to detect stale connections |
|
|
46
|
-
| **URI-Based Routing** | Multiple subscriptions over a single connection |
|
|
47
|
-
| **React Integration** | TanStack Store for reactive data updates |
|
|
48
|
-
| **Online/Offline Detection** | Browser connectivity change handling |
|
|
49
|
-
| **Two API Types** | **Subscription** for streaming data; **Message** for request/response commands |
|
|
39
|
+
### Subscription vs Message
|
|
50
40
|
|
|
51
|
-
|
|
41
|
+
The package offers two patterns for different use cases:
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
| | **Subscription** (`useWebsocketSubscription`) | **Message** (`useWebsocketMessage`) |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| **Pattern** | Streaming — subscribe once, receive ongoing updates | Request/response — send a message, get one reply (or none) |
|
|
46
|
+
| **Use case** | Live data feeds (notifications, voyage list, real-time dashboards) | One-off commands (validate, modify, mark read) |
|
|
47
|
+
| **URI** | Fixed per hook — one URI per subscription | Any URI — send to different endpoints per call |
|
|
48
|
+
| **State** | TanStack Store — reactive `message`, `pendingSubscription`, `connected` | No store — returns a Promise or fire-and-forget |
|
|
49
|
+
| **Lifecycle** | Auto-subscribes when connection opens; unsubscribes when last component unmounts | No subscription — just send when needed |
|
|
55
50
|
|
|
56
51
|
---
|
|
57
52
|
|
|
58
|
-
##
|
|
53
|
+
## Basic Setup
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
packages/use-websocket/
|
|
62
|
-
├── src/
|
|
63
|
-
│ ├── index.ts # Public exports
|
|
64
|
-
│ └── lib/
|
|
65
|
-
│ ├── WebsocketHook.ts # React hooks (useWebsocketSubscription, useWebsocketMessage, useWebsocketSubscriptionByKey)
|
|
66
|
-
│ ├── WebsocketConnection.ts # Connection lifecycle, reconnection, heartbeat
|
|
67
|
-
│ ├── WebsocketSubscriptionApi.ts # Streaming subscription per URI
|
|
68
|
-
│ ├── WebsocketMessageApi.ts # Request/response messaging (no subscription)
|
|
69
|
-
│ ├── websocketStores.ts # Global TanStack stores (connections, listeners)
|
|
70
|
-
│ ├── websocketStores.helpers.ts # findOrCreateWebsocketConnection, createWebsocketSubscriptionApi, etc.
|
|
71
|
-
│ ├── types.ts # Types, options, store shapes
|
|
72
|
-
│ ├── constants.ts # Timing, close codes, defaults
|
|
73
|
-
│ ├── WebsocketConnection.helpers.ts # Reconnection, ping, notifications
|
|
74
|
-
│ └── WEBSOCKET_CONNECTION.md # Detailed architecture and flows
|
|
75
|
-
├── README.md
|
|
76
|
-
├── CHART.md # Mermaid flow diagrams
|
|
77
|
-
└── package.json
|
|
78
|
-
```
|
|
55
|
+
1. Create a `WebsocketClient` and wrap your app with `WebsocketClientProvider`:
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```mermaid
|
|
83
|
-
graph TB
|
|
84
|
-
subgraph "React Layer"
|
|
85
|
-
Hook[useWebsocketSubscription / useWebsocketMessage]
|
|
86
|
-
ByKey[useWebsocketSubscriptionByKey]
|
|
87
|
-
Component[React Components]
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
subgraph "Connection Layer"
|
|
91
|
-
Connection[WebsocketConnection<br/>Singleton per URL]
|
|
92
|
-
SubApi[WebsocketSubscriptionApi<br/>One per key]
|
|
93
|
-
MsgApi[WebsocketMessageApi<br/>One per key]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
subgraph "WebSocket API"
|
|
97
|
-
Socket[WebSocket]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
Component -->|uses| Hook
|
|
101
|
-
Component -->|uses| ByKey
|
|
102
|
-
Hook -->|manages| Connection
|
|
103
|
-
Hook -->|creates| SubApi
|
|
104
|
-
Hook -->|creates| MsgApi
|
|
105
|
-
Connection -->|manages| Socket
|
|
106
|
-
Connection -->|routes messages to| SubApi
|
|
107
|
-
Connection -->|routes messages to| MsgApi
|
|
108
|
-
SubApi -->|TanStack Store| Component
|
|
109
|
-
```
|
|
57
|
+
```tsx
|
|
58
|
+
import { WebsocketClient, WebsocketClientProvider } from "@maxtroost/use-websocket";
|
|
110
59
|
|
|
111
|
-
|
|
60
|
+
const websocketClient = new WebsocketClient({
|
|
61
|
+
maxRetryAttempts: 20,
|
|
62
|
+
// Optional: customize heartbeat, timeouts, logging, etc.
|
|
63
|
+
});
|
|
112
64
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| `useWebsocketMessage` | One-off commands (validate, modify, mark read) — request/response |
|
|
121
|
-
| `useWebsocketSubscriptionByKey` | Child component needs parent's subscription data |
|
|
122
|
-
|
|
123
|
-
### Message Flow: Subscription
|
|
124
|
-
|
|
125
|
-
```mermaid
|
|
126
|
-
sequenceDiagram
|
|
127
|
-
participant Component
|
|
128
|
-
participant Hook as useWebsocketSubscription
|
|
129
|
-
participant Connection as WebsocketConnection
|
|
130
|
-
participant SubApi as WebsocketSubscriptionApi
|
|
131
|
-
participant Socket as WebSocket
|
|
132
|
-
participant Server
|
|
133
|
-
|
|
134
|
-
Component->>Hook: useWebsocketSubscription(options)
|
|
135
|
-
Hook->>Connection: findOrCreateWebsocketConnection(url)
|
|
136
|
-
Hook->>Connection: addListener(SubApi)
|
|
137
|
-
Connection->>Socket: new WebSocket(url)
|
|
138
|
-
|
|
139
|
-
Socket-->>Connection: open event
|
|
140
|
-
Connection->>SubApi: onOpen()
|
|
141
|
-
SubApi->>Socket: subscribe message
|
|
142
|
-
Socket->>Server: subscribe
|
|
143
|
-
|
|
144
|
-
Server-->>Socket: message (uri, body)
|
|
145
|
-
Socket-->>Connection: message event
|
|
146
|
-
Connection->>Connection: Route by URI
|
|
147
|
-
Connection->>SubApi: onMessage(body)
|
|
148
|
-
SubApi->>SubApi: Update TanStack Store
|
|
149
|
-
SubApi-->>Component: Store update triggers re-render
|
|
65
|
+
function App() {
|
|
66
|
+
return (
|
|
67
|
+
<WebsocketClientProvider client={websocketClient}>
|
|
68
|
+
<YourApp />
|
|
69
|
+
</WebsocketClientProvider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
150
72
|
```
|
|
151
73
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```mermaid
|
|
155
|
-
sequenceDiagram
|
|
156
|
-
participant Component
|
|
157
|
-
participant MsgApi as WebsocketMessageApi
|
|
158
|
-
participant Connection as WebsocketConnection
|
|
159
|
-
participant Socket as WebSocket
|
|
160
|
-
participant Server
|
|
161
|
-
|
|
162
|
-
Component->>MsgApi: sendMessage(uri, method, body?)
|
|
163
|
-
MsgApi->>Socket: Message with correlation ID
|
|
164
|
-
Socket->>Server: message
|
|
165
|
-
|
|
166
|
-
Server-->>Socket: response (same correlation)
|
|
167
|
-
Socket-->>Connection: message event
|
|
168
|
-
Connection->>MsgApi: deliverMessage(uri, data)
|
|
169
|
-
MsgApi->>MsgApi: resolve Promise
|
|
170
|
-
MsgApi-->>Component: await result
|
|
171
|
-
```
|
|
74
|
+
2. Use the hooks in your components:
|
|
172
75
|
|
|
173
|
-
|
|
76
|
+
```tsx
|
|
77
|
+
import { useWebsocketSubscription } from "@maxtroost/use-websocket";
|
|
78
|
+
import { useStore } from "@tanstack/react-store";
|
|
174
79
|
|
|
175
|
-
|
|
80
|
+
function LiveNotifications() {
|
|
81
|
+
const api = useWebsocketSubscription<Notification[]>({
|
|
82
|
+
key: "notifications",
|
|
83
|
+
url: "wss://api.example.com/ws",
|
|
84
|
+
uri: "/notifications",
|
|
85
|
+
});
|
|
176
86
|
|
|
177
|
-
|
|
87
|
+
const notifications = useStore(api.store, (s) => s.message);
|
|
88
|
+
const loading = useStore(api.store, (s) => s.pendingSubscription);
|
|
89
|
+
|
|
90
|
+
if (loading) return <div>Connecting...</div>;
|
|
91
|
+
return (
|
|
92
|
+
<ul>
|
|
93
|
+
{notifications?.map((n) => (
|
|
94
|
+
<li key={n.id}>{n.text}</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
178
100
|
|
|
179
|
-
|
|
101
|
+
---
|
|
180
102
|
|
|
181
|
-
|
|
103
|
+
## Features
|
|
182
104
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
serverError: WebsocketServerError<unknown> | undefined;
|
|
193
|
-
}
|
|
194
|
-
```
|
|
105
|
+
- **Automatic reconnection** — Exponential backoff (4s → 30s → 90s) with configurable max attempts
|
|
106
|
+
- **Heartbeat monitoring** — Ping/pong to detect stale connections
|
|
107
|
+
- **URI-based routing** — Messages routed by URI; one connection per URL shared across subscriptions
|
|
108
|
+
- **TanStack Store integration** — Reactive state for subscriptions; components re-render on updates
|
|
109
|
+
- **Two patterns** — `useWebsocketSubscription` for streaming data, `useWebsocketMessage` for request/response
|
|
110
|
+
- **Shared stores** — Child components access parent subscriptions via `useWebsocketSubscriptionByKey`
|
|
111
|
+
- **Conditional subscriptions** — `enabled` option to pause when unauthenticated or feature-flagged off
|
|
112
|
+
- **Lifecycle callbacks** — `onSubscribe`, `onMessage`, `onError`, `onClose` for logging and side effects
|
|
113
|
+
- **Connection events** — `connectionEvent` callback for reconnection status, logging, or custom notifications
|
|
195
114
|
|
|
196
|
-
|
|
115
|
+
---
|
|
197
116
|
|
|
198
|
-
|
|
199
|
-
| ------------- | ---------- |
|
|
200
|
-
| 0–4 attempts | 4 seconds |
|
|
201
|
-
| 5–9 attempts | 30 seconds |
|
|
202
|
-
| 10+ attempts | 90 seconds |
|
|
117
|
+
## API Reference
|
|
203
118
|
|
|
204
|
-
|
|
119
|
+
| Export | Description |
|
|
120
|
+
| ------ | ----------- |
|
|
121
|
+
| `useWebsocketSubscription` | Subscribe to a URI and receive streaming data via a reactive store |
|
|
122
|
+
| `useWebsocketSubscriptionByKey` | Access the store of a subscription created elsewhere (by key) |
|
|
123
|
+
| `useWebsocketMessage` | Send request/response messages to any URI |
|
|
124
|
+
| `WebsocketClient` | Client configuration; instantiate and pass to `WebsocketClientProvider`; `reconnectAllConnections()` for manual retry |
|
|
125
|
+
| `WebsocketClientProvider` | Context provider; wrap your app to enable hooks |
|
|
126
|
+
| `WebsocketConnection` | Low-level connection class; `setCustomLogger` for debugging |
|
|
127
|
+
| `ReadyState`, `WebsocketSubscriptionStore`, `WebsocketTransportError`, `WebsocketServerError` | Types |
|
|
205
128
|
|
|
206
129
|
---
|
|
207
130
|
|
|
208
|
-
##
|
|
131
|
+
## Examples
|
|
209
132
|
|
|
210
133
|
### Subscription (Streaming Data)
|
|
211
134
|
|
|
212
|
-
|
|
213
|
-
|
|
135
|
+
Subscribe to a URI and receive streaming data via a reactive TanStack Store.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useWebsocketSubscription } from "@maxtroost/use-websocket";
|
|
214
139
|
import { useStore } from "@tanstack/react-store";
|
|
215
140
|
|
|
141
|
+
interface Voyage {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
status: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
216
147
|
function VoyageList() {
|
|
217
|
-
const voyageApi = useWebsocketSubscription<Voyage[],
|
|
148
|
+
const voyageApi = useWebsocketSubscription<Voyage[], { status: string }>({
|
|
218
149
|
key: "voyages-list",
|
|
219
|
-
url: "/
|
|
150
|
+
url: "wss://api.example.com/ws",
|
|
220
151
|
uri: "/api/voyages",
|
|
221
152
|
body: { status: "active" },
|
|
222
153
|
});
|
|
223
154
|
|
|
224
155
|
const voyages = useStore(voyageApi.store, (s) => s.message);
|
|
225
156
|
const pending = useStore(voyageApi.store, (s) => s.pendingSubscription);
|
|
157
|
+
const connected = useStore(voyageApi.store, (s) => s.connected);
|
|
226
158
|
|
|
227
159
|
if (pending) return <Skeleton />;
|
|
228
|
-
return
|
|
160
|
+
return (
|
|
161
|
+
<div>
|
|
162
|
+
{!connected && <span>Reconnecting...</span>}
|
|
163
|
+
{voyages?.map((v) => (
|
|
164
|
+
<div key={v.id}>{v.name}</div>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
229
168
|
}
|
|
230
169
|
```
|
|
231
170
|
|
|
232
171
|
### Access Store by Key (Child Components)
|
|
233
172
|
|
|
234
|
-
|
|
235
|
-
|
|
173
|
+
When a parent creates the subscription, children can access the same store by key.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { useWebsocketSubscriptionByKey } from "@maxtroost/use-websocket";
|
|
236
177
|
import { useStore } from "@tanstack/react-store";
|
|
237
178
|
|
|
238
179
|
function VoyageCount() {
|
|
@@ -244,13 +185,15 @@ function VoyageCount() {
|
|
|
244
185
|
|
|
245
186
|
### Message API (Request/Response)
|
|
246
187
|
|
|
247
|
-
|
|
248
|
-
|
|
188
|
+
For one-off commands (validate, modify, mark read) — send a message and optionally await a response.
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { useWebsocketMessage } from "@maxtroost/use-websocket";
|
|
249
192
|
|
|
250
193
|
function VoyageActions() {
|
|
251
|
-
const api = useWebsocketMessage<
|
|
194
|
+
const api = useWebsocketMessage<ValidationResult, FormValues>({
|
|
252
195
|
key: "voyages/modify",
|
|
253
|
-
url: "/
|
|
196
|
+
url: "wss://api.example.com/ws",
|
|
254
197
|
responseTimeoutMs: 5000,
|
|
255
198
|
});
|
|
256
199
|
|
|
@@ -260,91 +203,135 @@ function VoyageActions() {
|
|
|
260
203
|
"post",
|
|
261
204
|
formValues
|
|
262
205
|
);
|
|
263
|
-
|
|
206
|
+
if (result.valid) {
|
|
207
|
+
// proceed
|
|
208
|
+
}
|
|
264
209
|
};
|
|
265
210
|
|
|
266
211
|
const handleMarkRead = () => {
|
|
267
212
|
api.sendMessageNoWait(`notifications/${id}/read`, "post");
|
|
268
213
|
};
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<>
|
|
217
|
+
<button onClick={handleValidate}>Validate</button>
|
|
218
|
+
<button onClick={handleMarkRead}>Mark Read</button>
|
|
219
|
+
</>
|
|
220
|
+
);
|
|
269
221
|
}
|
|
270
222
|
```
|
|
271
223
|
|
|
272
|
-
###
|
|
224
|
+
### Conditional Subscription
|
|
273
225
|
|
|
274
|
-
|
|
226
|
+
Disable the subscription when the user is not authenticated or when a feature flag is off.
|
|
275
227
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
| Option | Type | Description |
|
|
288
|
-
| ------------------- | --------- | -------------------------------------------------- |
|
|
289
|
-
| `key` | `string` | Unique identifier |
|
|
290
|
-
| `url` | `string` | Base WebSocket path |
|
|
291
|
-
| `enabled` | `boolean` | When `false`, disconnects |
|
|
292
|
-
| `responseTimeoutMs` | `number` | Default timeout for `sendMessage` (default: 10000) |
|
|
228
|
+
```tsx
|
|
229
|
+
function VoyageList({ isAuthenticated }: { isAuthenticated: boolean }) {
|
|
230
|
+
const api = useWebsocketSubscription<Voyage[]>({
|
|
231
|
+
key: "voyages-list",
|
|
232
|
+
url: "wss://api.example.com/ws",
|
|
233
|
+
uri: "/api/voyages",
|
|
234
|
+
enabled: isAuthenticated,
|
|
235
|
+
});
|
|
236
|
+
// ...
|
|
237
|
+
}
|
|
238
|
+
```
|
|
293
239
|
|
|
294
240
|
---
|
|
295
241
|
|
|
296
|
-
##
|
|
297
|
-
|
|
298
|
-
###
|
|
242
|
+
## Advanced Examples
|
|
243
|
+
|
|
244
|
+
### Custom WebsocketClient Configuration
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
const websocketClient = new WebsocketClient({
|
|
248
|
+
maxRetryAttempts: 10,
|
|
249
|
+
notificationThreshold: 5,
|
|
250
|
+
messageResponseTimeoutMs: 5000,
|
|
251
|
+
heartbeat: { enabled: true, intervalMs: 30000 },
|
|
252
|
+
connectionEvent: (event) => {
|
|
253
|
+
if (event.type === "reconnecting") {
|
|
254
|
+
analytics.track("websocket_reconnecting", { url: event.url });
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
```
|
|
299
259
|
|
|
300
|
-
|
|
260
|
+
### Auth Token in WebSocket URL
|
|
301
261
|
|
|
302
|
-
|
|
303
|
-
- **Possible causes**: Wrong `uri`, server not sending to that URI, connection not open
|
|
304
|
-
- **Debugging**: Check `connected` in store; verify server logs for incoming subscribe; ensure `useWebsocketConnectionConfig` and `useReconnectWebsocketConnections` are called at app root inside auth provider
|
|
305
|
-
- **Solution**: Confirm `uri` matches server route; check network tab for WebSocket frames
|
|
262
|
+
When the WebSocket URL includes an auth token, pass the full URL to the hook. When the token changes, the hook automatically calls `replaceUrl` to reconnect with the new URL.
|
|
306
263
|
|
|
307
|
-
|
|
264
|
+
```tsx
|
|
265
|
+
function VoyageList() {
|
|
266
|
+
const { token } = useAuth();
|
|
267
|
+
const wsUrl = token ? `wss://api.example.com/ws?token=${token}` : null;
|
|
308
268
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
269
|
+
const api = useWebsocketSubscription<Voyage[]>({
|
|
270
|
+
key: "voyages",
|
|
271
|
+
url: wsUrl ?? "", // Hook handles URL changes via replaceUrl
|
|
272
|
+
uri: "/api/voyages",
|
|
273
|
+
enabled: !!token,
|
|
274
|
+
});
|
|
275
|
+
// ...
|
|
276
|
+
}
|
|
277
|
+
```
|
|
313
278
|
|
|
314
|
-
|
|
279
|
+
To manually retry after reconnection stops (e.g. user clicks "Retry"): `websocketClient.reconnectAllConnections()`.
|
|
315
280
|
|
|
316
|
-
|
|
317
|
-
- **Possible causes**: Parent with `useWebsocketSubscription` not mounted yet; different `key` used
|
|
318
|
-
- **Debugging**: Ensure parent mounts first; verify `key` string matches exactly
|
|
319
|
-
- **Solution**: Use same `key` in parent and child; consider lifting subscription higher in tree
|
|
281
|
+
### Transform Outgoing Messages (e.g. Add Auth Header)
|
|
320
282
|
|
|
321
|
-
|
|
283
|
+
```tsx
|
|
284
|
+
const websocketClient = new WebsocketClient({
|
|
285
|
+
transformMessagePayload: (payload) => ({
|
|
286
|
+
...payload,
|
|
287
|
+
headers: {
|
|
288
|
+
...payload.headers,
|
|
289
|
+
Authorization: `Bearer ${getAuthToken()}`,
|
|
290
|
+
},
|
|
291
|
+
}),
|
|
292
|
+
});
|
|
293
|
+
```
|
|
322
294
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
295
|
+
### Lifecycle Callbacks
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
const api = useWebsocketSubscription<Voyage[]>({
|
|
299
|
+
key: "voyages",
|
|
300
|
+
url: "wss://api.example.com/ws",
|
|
301
|
+
uri: "/api/voyages",
|
|
302
|
+
onSubscribe: ({ uri }) => console.log("Subscribed to", uri),
|
|
303
|
+
onMessage: ({ data }) => console.log("Received", data),
|
|
304
|
+
onError: (error) => {
|
|
305
|
+
if (error.type === "transport")
|
|
306
|
+
console.error("Connection error", error.event);
|
|
307
|
+
},
|
|
308
|
+
onMessageError: (error) => {
|
|
309
|
+
if (error.type === "server")
|
|
310
|
+
console.error("Server error", error.message);
|
|
311
|
+
},
|
|
312
|
+
onClose: (event) => console.log("Connection closed", event.code),
|
|
313
|
+
});
|
|
314
|
+
```
|
|
326
315
|
|
|
327
|
-
###
|
|
316
|
+
### Per-Call Timeout Override
|
|
328
317
|
|
|
329
|
-
|
|
330
|
-
|
|
318
|
+
```tsx
|
|
319
|
+
const result = await api.sendMessage("/api/command", "post", body, {
|
|
320
|
+
timeout: 3000,
|
|
321
|
+
});
|
|
322
|
+
```
|
|
331
323
|
|
|
332
324
|
---
|
|
333
325
|
|
|
334
|
-
##
|
|
326
|
+
## Documentation
|
|
327
|
+
|
|
328
|
+
For contributors and deeper architecture details:
|
|
335
329
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
| `@tanstack/react-store` | Reactive state for components |
|
|
339
|
-
| `@tanstack/store` | Core store implementation |
|
|
340
|
-
| `notistack` | User notifications (reconnection errors) |
|
|
341
|
-
| `uuid` | Correlation IDs |
|
|
342
|
-
| `fast-equals` | Deep equality for options |
|
|
343
|
-
| `usehooks-ts` | `useIsomorphicLayoutEffect` |
|
|
330
|
+
- **[WEBSOCKET_CONNECTION.md](https://github.com/max-troost-io/mt-use-websockets/blob/main/src/lib/WEBSOCKET_CONNECTION.md)** — Connection lifecycle, class diagrams, URI API lifecycle, browser online/offline handling, full API reference
|
|
331
|
+
- **[CHART.md](https://github.com/max-troost-io/mt-use-websockets/blob/main/src/lib/CHART.md)** — Mermaid flow diagrams for hooks, connection, and error flows
|
|
344
332
|
|
|
345
333
|
---
|
|
346
334
|
|
|
347
|
-
##
|
|
335
|
+
## License
|
|
348
336
|
|
|
349
|
-
|
|
350
|
-
- **[CHART.md](CHART.md)** — Mermaid flow diagrams for hooks, connection, and error flows
|
|
337
|
+
MIT
|