@owlmeans/client-socket 0.1.2 → 0.1.4
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 +29 -549
- package/build/helper.d.ts +4 -4
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +14 -5
- package/build/helper.js.map +1 -1
- package/package.json +11 -10
- package/src/helper.ts +22 -11
- package/tsconfig.json +6 -11
package/README.md
CHANGED
|
@@ -1,582 +1,62 @@
|
|
|
1
1
|
# @owlmeans/client-socket
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React hook and factory for WebSocket connections via OwlMeans module routing.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
- **Module-based Socket Management**: Integration with the OwlMeans module system for WebSocket endpoints
|
|
11
|
-
- **Connection Lifecycle Management**: Automatic connection establishment, maintenance, and cleanup
|
|
12
|
-
- **Authentication Integration**: Built-in authentication support for secure WebSocket connections
|
|
13
|
-
- **Real-time Communication**: Enable real-time features like chat, notifications, and live updates
|
|
14
|
-
- **TypeScript Support**: Full TypeScript support with proper type safety
|
|
15
|
-
|
|
16
|
-
## Key Concepts
|
|
17
|
-
|
|
18
|
-
### Client-side WebSocket Management
|
|
19
|
-
This package provides client-side WebSocket functionality that integrates with the OwlMeans module system, allowing WebSocket connections to be defined as modules.
|
|
20
|
-
|
|
21
|
-
### Connection Abstraction
|
|
22
|
-
Wraps native WebSocket connections in a unified `Connection` interface that provides consistent API for message handling, authentication, and lifecycle management.
|
|
23
|
-
|
|
24
|
-
### React Integration
|
|
25
|
-
Provides React hooks for managing WebSocket connections with automatic cleanup and re-connection logic.
|
|
26
|
-
|
|
27
|
-
### Module-driven Connections
|
|
28
|
-
WebSocket endpoints are defined as modules, ensuring consistent URL generation and authentication handling across the application.
|
|
7
|
+
- `ws(module, request?)` — creates a `Connection` by resolving the module URL and opening a WebSocket
|
|
8
|
+
- `useWs(module, request?)` — React hook wrapping `ws()` with lifecycle management
|
|
9
|
+
- The returned `Connection` implements `@owlmeans/socket`'s `Connection` interface
|
|
29
10
|
|
|
30
11
|
## Installation
|
|
31
12
|
|
|
32
13
|
```bash
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## API Reference
|
|
37
|
-
|
|
38
|
-
### Types
|
|
39
|
-
|
|
40
|
-
#### `Config`
|
|
41
|
-
Configuration interface extending ClientConfig.
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
interface Config extends ClientConfig { }
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
#### `Context<C extends Config = Config>`
|
|
48
|
-
Context interface for client applications with socket support.
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Core Functions
|
|
55
|
-
|
|
56
|
-
#### `ws(module: ClientModule<string>, request?: AbstractRequest<{ token?: string }>): Promise<Connection>`
|
|
57
|
-
|
|
58
|
-
Creates a WebSocket connection using a client module.
|
|
59
|
-
|
|
60
|
-
**Parameters:**
|
|
61
|
-
- `module`: Client module that defines the WebSocket endpoint
|
|
62
|
-
- `request`: Optional request object with authentication token
|
|
63
|
-
|
|
64
|
-
**Returns:** Promise that resolves to a Connection instance
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { ws } from '@owlmeans/client-socket'
|
|
68
|
-
|
|
69
|
-
const socketModule = context.module<ClientModule>('chat-socket')
|
|
70
|
-
const connection = await ws(socketModule, {
|
|
71
|
-
query: { token: authToken }
|
|
72
|
-
})
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### React Hooks
|
|
76
|
-
|
|
77
|
-
#### `useWs(module: string | ClientModule<any>, request?: Partial<AbstractRequest<any>>): Connection | null`
|
|
78
|
-
|
|
79
|
-
React hook for managing WebSocket connections with automatic lifecycle management.
|
|
80
|
-
|
|
81
|
-
**Parameters:**
|
|
82
|
-
- `module`: Module alias or ClientModule instance
|
|
83
|
-
- `request`: Optional request parameters
|
|
84
|
-
|
|
85
|
-
**Returns:** Connection instance or null if not connected
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
89
|
-
|
|
90
|
-
function ChatComponent() {
|
|
91
|
-
const connection = useWs('chat-socket', {
|
|
92
|
-
query: { room: 'general' }
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (connection) {
|
|
97
|
-
connection.on('message', handleMessage)
|
|
98
|
-
}
|
|
99
|
-
}, [connection])
|
|
100
|
-
|
|
101
|
-
return <div>Chat interface</div>
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Utility Functions
|
|
106
|
-
|
|
107
|
-
#### `makeConnection<C extends Config = Config, T extends Context<C> = Context<C>>(conn: WebSocket, context: T): Connection`
|
|
108
|
-
|
|
109
|
-
Creates a Connection wrapper around a native WebSocket instance.
|
|
110
|
-
|
|
111
|
-
**Parameters:**
|
|
112
|
-
- `conn`: Native WebSocket instance
|
|
113
|
-
- `context`: Application context
|
|
114
|
-
|
|
115
|
-
**Returns:** Connection wrapper with enhanced functionality
|
|
116
|
-
|
|
117
|
-
## Usage Examples
|
|
118
|
-
|
|
119
|
-
### Basic WebSocket Connection
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
123
|
-
import { useContext } from '@owlmeans/client'
|
|
124
|
-
import { useEffect, useState } from 'react'
|
|
125
|
-
|
|
126
|
-
function WebSocketExample() {
|
|
127
|
-
const [messages, setMessages] = useState<string[]>([])
|
|
128
|
-
const connection = useWs('websocket-endpoint')
|
|
129
|
-
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (connection) {
|
|
132
|
-
connection.on('message', (message) => {
|
|
133
|
-
setMessages(prev => [...prev, message.payload])
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
// Send a message
|
|
137
|
-
connection.send({
|
|
138
|
-
type: 'text',
|
|
139
|
-
payload: 'Hello WebSocket!'
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
}, [connection])
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div>
|
|
146
|
-
<h3>Messages:</h3>
|
|
147
|
-
{messages.map((msg, idx) => (
|
|
148
|
-
<div key={idx}>{msg}</div>
|
|
149
|
-
))}
|
|
150
|
-
</div>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Authenticated WebSocket Connection
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
159
|
-
import { useAuth } from '@owlmeans/client-auth'
|
|
160
|
-
import { useEffect } from 'react'
|
|
161
|
-
|
|
162
|
-
function AuthenticatedSocket() {
|
|
163
|
-
const auth = useAuth()
|
|
164
|
-
const connection = useWs('secure-socket', {
|
|
165
|
-
query: {
|
|
166
|
-
token: auth.getToken()
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
useEffect(() => {
|
|
171
|
-
if (connection) {
|
|
172
|
-
connection.on('authenticated', () => {
|
|
173
|
-
console.log('Socket authenticated successfully')
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
connection.on('auth-error', (error) => {
|
|
177
|
-
console.error('Socket authentication failed:', error)
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
}, [connection])
|
|
181
|
-
|
|
182
|
-
return <div>Authenticated WebSocket connection</div>
|
|
183
|
-
}
|
|
14
|
+
bun add @owlmeans/client-socket
|
|
184
15
|
```
|
|
185
16
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
190
|
-
import { useState, useEffect } from 'react'
|
|
17
|
+
## Usage
|
|
191
18
|
|
|
192
|
-
|
|
193
|
-
id: string
|
|
194
|
-
user: string
|
|
195
|
-
message: string
|
|
196
|
-
timestamp: number
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function ChatRoom({ roomId }: { roomId: string }) {
|
|
200
|
-
const [messages, setMessages] = useState<ChatMessage[]>([])
|
|
201
|
-
const [inputMessage, setInputMessage] = useState('')
|
|
202
|
-
|
|
203
|
-
const connection = useWs('chat-socket', {
|
|
204
|
-
query: { room: roomId }
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
if (connection) {
|
|
209
|
-
connection.on('chat-message', (event) => {
|
|
210
|
-
const message: ChatMessage = event.payload
|
|
211
|
-
setMessages(prev => [...prev, message])
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
connection.on('user-joined', (event) => {
|
|
215
|
-
console.log(`${event.payload.user} joined the room`)
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
connection.on('user-left', (event) => {
|
|
219
|
-
console.log(`${event.payload.user} left the room`)
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
}, [connection, roomId])
|
|
223
|
-
|
|
224
|
-
const sendMessage = () => {
|
|
225
|
-
if (connection && inputMessage.trim()) {
|
|
226
|
-
connection.send({
|
|
227
|
-
type: 'chat-message',
|
|
228
|
-
payload: {
|
|
229
|
-
message: inputMessage,
|
|
230
|
-
room: roomId
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
setInputMessage('')
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return (
|
|
238
|
-
<div>
|
|
239
|
-
<div className="messages">
|
|
240
|
-
{messages.map(msg => (
|
|
241
|
-
<div key={msg.id}>
|
|
242
|
-
<strong>{msg.user}:</strong> {msg.message}
|
|
243
|
-
</div>
|
|
244
|
-
))}
|
|
245
|
-
</div>
|
|
246
|
-
<div className="input">
|
|
247
|
-
<input
|
|
248
|
-
value={inputMessage}
|
|
249
|
-
onChange={(e) => setInputMessage(e.target.value)}
|
|
250
|
-
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
|
251
|
-
/>
|
|
252
|
-
<button onClick={sendMessage}>Send</button>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Real-time Notifications
|
|
19
|
+
Connect to a WebSocket module and observe events:
|
|
260
20
|
|
|
261
21
|
```typescript
|
|
262
22
|
import { useWs } from '@owlmeans/client-socket'
|
|
263
|
-
import {
|
|
23
|
+
import { MessageType } from '@owlmeans/socket'
|
|
264
24
|
|
|
265
|
-
|
|
266
|
-
id:
|
|
267
|
-
type: 'info' | 'warning' | 'error' | 'success'
|
|
268
|
-
title: string
|
|
269
|
-
message: string
|
|
270
|
-
}
|
|
25
|
+
function ThinkingPanel({ storyId }: { storyId: string }) {
|
|
26
|
+
const conn = useWs('story-thinking', { params: { id: storyId } })
|
|
271
27
|
|
|
272
|
-
function NotificationSystem() {
|
|
273
|
-
const [notifications, setNotifications] = useState<Notification[]>([])
|
|
274
|
-
const connection = useWs('notification-socket')
|
|
275
|
-
|
|
276
28
|
useEffect(() => {
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
setTimeout(() => {
|
|
284
|
-
setNotifications(prev =>
|
|
285
|
-
prev.filter(n => n.id !== notification.id)
|
|
286
|
-
)
|
|
287
|
-
}, 5000)
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
connection.on('notification-clear', (event) => {
|
|
291
|
-
const { notificationId } = event.payload
|
|
292
|
-
setNotifications(prev =>
|
|
293
|
-
prev.filter(n => n.id !== notificationId)
|
|
294
|
-
)
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
}, [connection])
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<div className="notification-container">
|
|
301
|
-
{notifications.map(notification => (
|
|
302
|
-
<div key={notification.id} className={`notification ${notification.type}`}>
|
|
303
|
-
<h4>{notification.title}</h4>
|
|
304
|
-
<p>{notification.message}</p>
|
|
305
|
-
</div>
|
|
306
|
-
))}
|
|
307
|
-
</div>
|
|
308
|
-
)
|
|
29
|
+
if (!conn) return
|
|
30
|
+
const unsubscribe = conn.observe<ThinkingEvent>('thinking-update', async (event) => {
|
|
31
|
+
dispatch({ type: 'update', payload: event.payload })
|
|
32
|
+
})
|
|
33
|
+
return unsubscribe
|
|
34
|
+
}, [conn])
|
|
309
35
|
}
|
|
310
36
|
```
|
|
311
37
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
316
|
-
import { useState, useEffect } from 'react'
|
|
317
|
-
|
|
318
|
-
interface LiveData {
|
|
319
|
-
timestamp: number
|
|
320
|
-
value: number
|
|
321
|
-
status: string
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function LiveDataDashboard() {
|
|
325
|
-
const [data, setData] = useState<LiveData[]>([])
|
|
326
|
-
const connection = useWs('live-data-socket')
|
|
327
|
-
|
|
328
|
-
useEffect(() => {
|
|
329
|
-
if (connection) {
|
|
330
|
-
connection.on('data-update', (event) => {
|
|
331
|
-
const newData: LiveData = event.payload
|
|
332
|
-
setData(prev => {
|
|
333
|
-
const updated = [newData, ...prev.slice(0, 99)] // Keep last 100 entries
|
|
334
|
-
return updated
|
|
335
|
-
})
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
// Request initial data
|
|
339
|
-
connection.send({
|
|
340
|
-
type: 'request-initial-data',
|
|
341
|
-
payload: {}
|
|
342
|
-
})
|
|
343
|
-
}
|
|
344
|
-
}, [connection])
|
|
345
|
-
|
|
346
|
-
return (
|
|
347
|
-
<div>
|
|
348
|
-
<h2>Live Data Stream</h2>
|
|
349
|
-
<div className="data-grid">
|
|
350
|
-
{data.map((item, idx) => (
|
|
351
|
-
<div key={idx} className="data-item">
|
|
352
|
-
<span>Value: {item.value}</span>
|
|
353
|
-
<span>Status: {item.status}</span>
|
|
354
|
-
<span>Time: {new Date(item.timestamp).toLocaleTimeString()}</span>
|
|
355
|
-
</div>
|
|
356
|
-
))}
|
|
357
|
-
</div>
|
|
358
|
-
</div>
|
|
359
|
-
)
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Connection Status Management
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
367
|
-
import { useState, useEffect } from 'react'
|
|
368
|
-
|
|
369
|
-
function ConnectionStatusProvider({ children }: { children: React.ReactNode }) {
|
|
370
|
-
const [isConnected, setIsConnected] = useState(false)
|
|
371
|
-
const [connectionError, setConnectionError] = useState<string | null>(null)
|
|
372
|
-
const connection = useWs('main-socket')
|
|
373
|
-
|
|
374
|
-
useEffect(() => {
|
|
375
|
-
if (connection) {
|
|
376
|
-
setIsConnected(true)
|
|
377
|
-
setConnectionError(null)
|
|
378
|
-
|
|
379
|
-
connection.on('close', () => {
|
|
380
|
-
setIsConnected(false)
|
|
381
|
-
setConnectionError('Connection lost')
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
connection.on('error', (event) => {
|
|
385
|
-
setConnectionError(event.payload.message)
|
|
386
|
-
})
|
|
387
|
-
} else {
|
|
388
|
-
setIsConnected(false)
|
|
389
|
-
}
|
|
390
|
-
}, [connection])
|
|
391
|
-
|
|
392
|
-
return (
|
|
393
|
-
<div>
|
|
394
|
-
<div className={`connection-status ${isConnected ? 'connected' : 'disconnected'}`}>
|
|
395
|
-
{isConnected ? 'Connected' : 'Disconnected'}
|
|
396
|
-
{connectionError && <span>: {connectionError}</span>}
|
|
397
|
-
</div>
|
|
398
|
-
{children}
|
|
399
|
-
</div>
|
|
400
|
-
)
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Manual Connection Management
|
|
38
|
+
Direct connection (non-hook):
|
|
405
39
|
|
|
406
40
|
```typescript
|
|
407
41
|
import { ws } from '@owlmeans/client-socket'
|
|
408
|
-
import {
|
|
409
|
-
import { useEffect, useState } from 'react'
|
|
410
|
-
|
|
411
|
-
function ManualConnectionComponent() {
|
|
412
|
-
const context = useContext()
|
|
413
|
-
const [connection, setConnection] = useState<Connection | null>(null)
|
|
414
|
-
const [status, setStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected')
|
|
415
|
-
|
|
416
|
-
const connect = async () => {
|
|
417
|
-
try {
|
|
418
|
-
setStatus('connecting')
|
|
419
|
-
const socketModule = context.module<ClientModule>('manual-socket')
|
|
420
|
-
const conn = await ws(socketModule)
|
|
421
|
-
|
|
422
|
-
conn.on('close', () => {
|
|
423
|
-
setStatus('disconnected')
|
|
424
|
-
setConnection(null)
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
setConnection(conn)
|
|
428
|
-
setStatus('connected')
|
|
429
|
-
} catch (error) {
|
|
430
|
-
console.error('Connection failed:', error)
|
|
431
|
-
setStatus('disconnected')
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const disconnect = async () => {
|
|
436
|
-
if (connection) {
|
|
437
|
-
await connection.close()
|
|
438
|
-
setConnection(null)
|
|
439
|
-
setStatus('disconnected')
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
return () => {
|
|
445
|
-
if (connection) {
|
|
446
|
-
connection.close()
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}, [])
|
|
450
|
-
|
|
451
|
-
return (
|
|
452
|
-
<div>
|
|
453
|
-
<div>Status: {status}</div>
|
|
454
|
-
<button onClick={connect} disabled={status !== 'disconnected'}>
|
|
455
|
-
Connect
|
|
456
|
-
</button>
|
|
457
|
-
<button onClick={disconnect} disabled={status === 'disconnected'}>
|
|
458
|
-
Disconnect
|
|
459
|
-
</button>
|
|
460
|
-
</div>
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
## Error Handling
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
469
|
-
import { useEffect, useState } from 'react'
|
|
470
|
-
|
|
471
|
-
function ErrorHandlingExample() {
|
|
472
|
-
const [error, setError] = useState<string | null>(null)
|
|
473
|
-
const connection = useWs('error-prone-socket')
|
|
474
|
-
|
|
475
|
-
useEffect(() => {
|
|
476
|
-
if (connection) {
|
|
477
|
-
connection.on('error', (event) => {
|
|
478
|
-
setError(event.payload.message || 'Unknown error occurred')
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
connection.on('close', (event) => {
|
|
482
|
-
if (event.payload.code !== 1000) { // Not a normal closure
|
|
483
|
-
setError(`Connection closed unexpectedly (Code: ${event.payload.code})`)
|
|
484
|
-
}
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
// Clear error when reconnected
|
|
488
|
-
connection.on('open', () => {
|
|
489
|
-
setError(null)
|
|
490
|
-
})
|
|
491
|
-
}
|
|
492
|
-
}, [connection])
|
|
493
|
-
|
|
494
|
-
return (
|
|
495
|
-
<div>
|
|
496
|
-
{error && (
|
|
497
|
-
<div className="error-message">
|
|
498
|
-
Error: {error}
|
|
499
|
-
</div>
|
|
500
|
-
)}
|
|
501
|
-
<div>
|
|
502
|
-
Connection Status: {connection ? 'Connected' : 'Disconnected'}
|
|
503
|
-
</div>
|
|
504
|
-
</div>
|
|
505
|
-
)
|
|
506
|
-
}
|
|
507
|
-
```
|
|
42
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
508
43
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
### Module Definition
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
// Define WebSocket module in your module configuration
|
|
515
|
-
import { module } from '@owlmeans/client-module'
|
|
516
|
-
import { route } from '@owlmeans/route'
|
|
517
|
-
|
|
518
|
-
const chatSocketModule = module(
|
|
519
|
-
route('chat-socket', '/ws/chat'),
|
|
520
|
-
guard('authenticated')
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
// Register the module with your context
|
|
524
|
-
context.registerModule(chatSocketModule)
|
|
44
|
+
const wsModule = context.module<ClientModule<string>>('story-thinking')
|
|
45
|
+
const connection = await ws(wsModule, { params: { id: storyId } })
|
|
525
46
|
```
|
|
526
47
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
```typescript
|
|
530
|
-
import React, { createContext, useContext as useReactContext } from 'react'
|
|
531
|
-
import { Connection } from '@owlmeans/socket'
|
|
532
|
-
import { useWs } from '@owlmeans/client-socket'
|
|
533
|
-
|
|
534
|
-
const SocketContext = createContext<Connection | null>(null)
|
|
535
|
-
|
|
536
|
-
export function SocketProvider({ children }: { children: React.ReactNode }) {
|
|
537
|
-
const connection = useWs('global-socket')
|
|
538
|
-
|
|
539
|
-
return (
|
|
540
|
-
<SocketContext.Provider value={connection}>
|
|
541
|
-
{children}
|
|
542
|
-
</SocketContext.Provider>
|
|
543
|
-
)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
export function useGlobalSocket() {
|
|
547
|
-
const connection = useReactContext(SocketContext)
|
|
548
|
-
if (!connection) {
|
|
549
|
-
throw new Error('useGlobalSocket must be used within SocketProvider')
|
|
550
|
-
}
|
|
551
|
-
return connection
|
|
552
|
-
}
|
|
553
|
-
```
|
|
48
|
+
## API
|
|
554
49
|
|
|
555
|
-
|
|
50
|
+
### `ws(module, request?): Promise<Connection>`
|
|
556
51
|
|
|
557
|
-
|
|
558
|
-
2. **Error Handling**: Implement comprehensive error handling for network issues
|
|
559
|
-
3. **Reconnection Logic**: Consider implementing automatic reconnection for critical connections
|
|
560
|
-
4. **Message Validation**: Validate incoming messages to ensure data integrity
|
|
561
|
-
5. **Performance**: Avoid creating too many simultaneous WebSocket connections
|
|
562
|
-
6. **Authentication**: Securely handle authentication tokens in WebSocket connections
|
|
563
|
-
7. **Memory Leaks**: Ensure proper cleanup of event listeners and connections
|
|
52
|
+
Resolves the module URL, opens a WebSocket, and returns a `Connection` once the socket opens.
|
|
564
53
|
|
|
565
|
-
|
|
54
|
+
### `useWs(module, request?): Connection | null`
|
|
566
55
|
|
|
567
|
-
|
|
568
|
-
- `@owlmeans/auth` - Authentication framework
|
|
569
|
-
- `@owlmeans/client` - Client framework
|
|
570
|
-
- `@owlmeans/client-context` - Client context management
|
|
571
|
-
- `@owlmeans/client-module` - Client module system
|
|
572
|
-
- `@owlmeans/context` - Context management
|
|
573
|
-
- `@owlmeans/module` - Module system
|
|
574
|
-
- `@owlmeans/socket` - Core socket functionality
|
|
575
|
-
- `react` - React framework
|
|
56
|
+
React hook version of `ws()`. Returns `null` while connecting. Manages connection lifecycle (opens on mount, closes on unmount).
|
|
576
57
|
|
|
577
58
|
## Related Packages
|
|
578
59
|
|
|
579
|
-
- [`@owlmeans/socket`](../socket)
|
|
580
|
-
- [`@owlmeans/server-socket`](../server-socket)
|
|
581
|
-
- [`@owlmeans/client`](../client)
|
|
582
|
-
- [`@owlmeans/client-module`](../client-module) - Client module system
|
|
60
|
+
- [`@owlmeans/socket`](../socket) — `Connection` interface with `notify`, `observe`, `call` methods
|
|
61
|
+
- [`@owlmeans/server-socket`](../server-socket) — server-side connection handler
|
|
62
|
+
- [`@owlmeans/client-module`](../client-module) — `ClientModule` passed to `ws()`
|
package/build/helper.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AbstractRequest } from '@owlmeans/
|
|
1
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint';
|
|
2
|
+
import type { AbstractRequest } from '@owlmeans/entrypoint';
|
|
3
3
|
import type { Connection } from '@owlmeans/socket';
|
|
4
|
-
export declare const ws: (module:
|
|
4
|
+
export declare const ws: (module: ClientEntrypoint<string>, request?: AbstractRequest<{
|
|
5
5
|
token?: string;
|
|
6
6
|
}>) => Promise<Connection>;
|
|
7
|
-
export declare const useWs: (module: string |
|
|
7
|
+
export declare const useWs: (module: string | ClientEntrypoint<any>, request?: Partial<AbstractRequest<any>>) => Connection | null;
|
|
8
8
|
//# sourceMappingURL=helper.d.ts.map
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAG3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD,eAAO,MAAM,EAAE,GAAU,QAAQ,gBAAgB,CAAC,MAAM,CAAC,EAAE,UAAU,eAAe,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAAG,OAAO,CAAC,UAAU,CA0B5H,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,UAAU,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,KAAG,UAAU,GAAG,IAyBpH,CAAA"}
|
package/build/helper.js
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
|
-
import { provideRequest } from '@owlmeans/client-
|
|
2
|
-
import {
|
|
1
|
+
import { provideRequest } from '@owlmeans/client-entrypoint';
|
|
2
|
+
import { EntrypointOutcome } from '@owlmeans/entrypoint';
|
|
3
3
|
import { SocketInitializationError } from '@owlmeans/socket';
|
|
4
4
|
import { makeConnection } from './utils/connection.js';
|
|
5
5
|
import { assertContext } from '@owlmeans/context';
|
|
6
6
|
import { useContext, useValue } from '@owlmeans/client';
|
|
7
7
|
import { AUTH_QUERY } from '@owlmeans/auth';
|
|
8
|
-
import { urlCall } from '@owlmeans/client-
|
|
8
|
+
import { urlCall } from '@owlmeans/client-entrypoint/utils';
|
|
9
9
|
import { useEffect, useMemo } from 'react';
|
|
10
10
|
export const ws = async (module, request) => {
|
|
11
11
|
const ctx = assertContext(module.ctx, 'client-ws');
|
|
12
12
|
request = request ?? provideRequest(module.getAlias(), module.getPath());
|
|
13
13
|
const [url, state] = await urlCall({ ref: module })(request);
|
|
14
|
-
if (state !==
|
|
14
|
+
if (state !== EntrypointOutcome.Ok) {
|
|
15
15
|
throw new SocketInitializationError;
|
|
16
16
|
}
|
|
17
17
|
const socket = new WebSocket(url);
|
|
18
18
|
return new Promise(resolve => {
|
|
19
19
|
socket.onopen = () => {
|
|
20
|
+
const heartbeat = setInterval(() => {
|
|
21
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
22
|
+
socket.send(JSON.stringify({ type: 'ping' }));
|
|
23
|
+
}
|
|
24
|
+
}, 30_000);
|
|
25
|
+
socket.addEventListener('close', () => {
|
|
26
|
+
console.info('WebSocket connection closed, clearing heartbeat interval.');
|
|
27
|
+
clearInterval(heartbeat);
|
|
28
|
+
});
|
|
20
29
|
resolve(makeConnection(socket, ctx));
|
|
21
30
|
};
|
|
22
31
|
});
|
|
23
32
|
};
|
|
24
33
|
export const useWs = (module, request) => {
|
|
25
34
|
const ctx = useContext();
|
|
26
|
-
const mod = useMemo(() => typeof module === 'string' ? ctx.
|
|
35
|
+
const mod = useMemo(() => typeof module === 'string' ? ctx.entrypoint(module) : module, [module]);
|
|
27
36
|
const connection = useValue(async () => {
|
|
28
37
|
const _request = provideRequest(mod.getAlias(), mod.getPath());
|
|
29
38
|
Object.assign(_request, request);
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/client-socket",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -24,19 +24,20 @@
|
|
|
24
24
|
"react": "*"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
27
28
|
"@types/react": "^19.2.7",
|
|
28
29
|
"nodemon": "^3.1.11",
|
|
29
|
-
"typescript": "^
|
|
30
|
+
"typescript": "^6.0.2"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@owlmeans/auth": "^0.1.
|
|
33
|
-
"@owlmeans/basic-envelope": "^0.1.
|
|
34
|
-
"@owlmeans/client": "^0.1.
|
|
35
|
-
"@owlmeans/client-context": "^0.1.
|
|
36
|
-
"@owlmeans/client-
|
|
37
|
-
"@owlmeans/context": "^0.1.
|
|
38
|
-
"@owlmeans/
|
|
39
|
-
"@owlmeans/socket": "^0.1.
|
|
33
|
+
"@owlmeans/auth": "^0.1.4",
|
|
34
|
+
"@owlmeans/basic-envelope": "^0.1.4",
|
|
35
|
+
"@owlmeans/client": "^0.1.4",
|
|
36
|
+
"@owlmeans/client-context": "^0.1.4",
|
|
37
|
+
"@owlmeans/client-entrypoint": "^0.1.4",
|
|
38
|
+
"@owlmeans/context": "^0.1.4",
|
|
39
|
+
"@owlmeans/entrypoint": "^0.1.4",
|
|
40
|
+
"@owlmeans/socket": "^0.1.4"
|
|
40
41
|
},
|
|
41
42
|
"publishConfig": {
|
|
42
43
|
"access": "public"
|
package/src/helper.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AbstractRequest } from '@owlmeans/
|
|
3
|
-
import { provideRequest } from '@owlmeans/client-
|
|
4
|
-
import {
|
|
1
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
2
|
+
import type { AbstractRequest } from '@owlmeans/entrypoint'
|
|
3
|
+
import { provideRequest } from '@owlmeans/client-entrypoint'
|
|
4
|
+
import { EntrypointOutcome } from '@owlmeans/entrypoint'
|
|
5
5
|
import type { Connection } from '@owlmeans/socket'
|
|
6
6
|
import { SocketInitializationError } from '@owlmeans/socket'
|
|
7
7
|
import { makeConnection } from './utils/connection.js'
|
|
@@ -9,30 +9,41 @@ import { assertContext } from '@owlmeans/context'
|
|
|
9
9
|
import type { Config, Context } from './types.js'
|
|
10
10
|
import { useContext, useValue } from '@owlmeans/client'
|
|
11
11
|
import { AUTH_QUERY } from '@owlmeans/auth'
|
|
12
|
-
import { urlCall } from '@owlmeans/client-
|
|
12
|
+
import { urlCall } from '@owlmeans/client-entrypoint/utils'
|
|
13
13
|
import { useEffect, useMemo } from 'react'
|
|
14
14
|
|
|
15
|
-
export const ws = async (module:
|
|
15
|
+
export const ws = async (module: ClientEntrypoint<string>, request?: AbstractRequest<{ token?: string }>): Promise<Connection> => {
|
|
16
16
|
const ctx = assertContext<Config, Context>(module.ctx as Context, 'client-ws')
|
|
17
17
|
request = request ?? provideRequest(module.getAlias(), module.getPath())
|
|
18
18
|
const [url, state] = await urlCall({ ref: module })(request)
|
|
19
19
|
|
|
20
|
-
if (state !==
|
|
20
|
+
if (state !== EntrypointOutcome.Ok) {
|
|
21
21
|
throw new SocketInitializationError
|
|
22
22
|
}
|
|
23
23
|
const socket = new WebSocket(url)
|
|
24
24
|
|
|
25
25
|
return new Promise(resolve => {
|
|
26
26
|
socket.onopen = () => {
|
|
27
|
+
const heartbeat = setInterval(() => {
|
|
28
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
29
|
+
socket.send(JSON.stringify({ type: 'ping' }))
|
|
30
|
+
}
|
|
31
|
+
}, 30_000)
|
|
32
|
+
|
|
33
|
+
socket.addEventListener('close', () => {
|
|
34
|
+
console.info('WebSocket connection closed, clearing heartbeat interval.')
|
|
35
|
+
clearInterval(heartbeat)
|
|
36
|
+
})
|
|
37
|
+
|
|
27
38
|
resolve(makeConnection(socket, ctx))
|
|
28
39
|
}
|
|
29
40
|
})
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
export const useWs = (module: string |
|
|
43
|
+
export const useWs = (module: string | ClientEntrypoint<any>, request?: Partial<AbstractRequest<any>>): Connection | null => {
|
|
33
44
|
const ctx = useContext()
|
|
34
45
|
const mod = useMemo(
|
|
35
|
-
() => typeof module === 'string' ? ctx.
|
|
46
|
+
() => typeof module === 'string' ? ctx.entrypoint<ClientEntrypoint>(module) : module,
|
|
36
47
|
[module]
|
|
37
48
|
)
|
|
38
49
|
const connection = useValue<Connection>(async () => {
|
|
@@ -40,8 +51,8 @@ export const useWs = (module: string | ClientModule<any>, request?: Partial<Abst
|
|
|
40
51
|
Object.assign(_request, request)
|
|
41
52
|
return await ws(mod, _request)
|
|
42
53
|
}, [
|
|
43
|
-
mod.getAlias(),
|
|
44
|
-
request?.query?.[AUTH_QUERY],
|
|
54
|
+
mod.getAlias(),
|
|
55
|
+
request?.query?.[AUTH_QUERY],
|
|
45
56
|
request?.params ? JSON.stringify(request.params) : undefined
|
|
46
57
|
])
|
|
47
58
|
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": [
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.react.json"
|
|
5
5
|
],
|
|
6
6
|
"compilerOptions": {
|
|
7
|
-
"rootDir": "./src/",
|
|
8
|
-
"outDir": "./build/"
|
|
9
|
-
"moduleResolution": "Bundler"
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/"
|
|
10
9
|
},
|
|
11
|
-
"exclude": [
|
|
12
|
-
|
|
13
|
-
"./build/**/*",
|
|
14
|
-
"./*.ts"
|
|
15
|
-
]
|
|
16
|
-
}
|
|
10
|
+
"exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
|
|
11
|
+
}
|