@owlmeans/client-socket 0.1.1 → 0.1.3

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
3
+ Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,582 +1,62 @@
1
1
  # @owlmeans/client-socket
2
2
 
3
- The **@owlmeans/client-socket** package provides client-side WebSocket integration for OwlMeans Common Libraries, enabling real-time communication between React frontend applications and backend services through WebSocket connections.
3
+ React hook and factory for WebSocket connections via OwlMeans module routing.
4
4
 
5
- ## Purpose
5
+ ## Overview
6
6
 
7
- This package serves as the client-side WebSocket implementation, specifically designed for:
8
-
9
- - **React WebSocket Integration**: Seamless WebSocket connections in React applications
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
- npm install @owlmeans/client-socket
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
- ### Chat Application
187
-
188
- ```typescript
189
- import { useWs } from '@owlmeans/client-socket'
190
- import { useState, useEffect } from 'react'
17
+ ## Usage
191
18
 
192
- interface ChatMessage {
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 { useEffect, useState } from 'react'
23
+ import { MessageType } from '@owlmeans/socket'
264
24
 
265
- interface Notification {
266
- id: string
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 (connection) {
278
- connection.on('notification', (event) => {
279
- const notification: Notification = event.payload
280
- setNotifications(prev => [notification, ...prev])
281
-
282
- // Auto-remove after 5 seconds
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
- ### Live Data Updates
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 { useContext } from '@owlmeans/client'
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
- ## Integration Patterns
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
- ### Context Provider Pattern
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
- ## Best Practices
50
+ ### `ws(module, request?): Promise<Connection>`
556
51
 
557
- 1. **Connection Management**: Always handle connection lifecycle properly with cleanup
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
- ## Dependencies
54
+ ### `useWs(module, request?): Connection | null`
566
55
 
567
- This package depends on:
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) - Core socket functionality
580
- - [`@owlmeans/server-socket`](../server-socket) - Server-side WebSocket implementation
581
- - [`@owlmeans/client`](../client) - Client framework
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 { ClientModule } from '@owlmeans/client-module';
2
- import type { AbstractRequest } from '@owlmeans/module';
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: ClientModule<string>, request?: AbstractRequest<{
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 | ClientModule<any>, request?: Partial<AbstractRequest<any>>) => Connection | null;
7
+ export declare const useWs: (module: string | ClientEntrypoint<any>, request?: Partial<AbstractRequest<any>>) => Connection | null;
8
8
  //# sourceMappingURL=helper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD,eAAO,MAAM,EAAE,GAAU,QAAQ,YAAY,CAAC,MAAM,CAAC,EAAE,UAAU,eAAe,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAAG,OAAO,CAAC,UAAU,CAexH,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,UAAU,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,KAAG,UAAU,GAAG,IAyBhH,CAAA"}
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-module';
2
- import { ModuleOutcome } from '@owlmeans/module';
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-module/utils';
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 !== ModuleOutcome.Ok) {
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.module(module) : module, [module]);
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);
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEhD,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE1C,MAAM,CAAC,MAAM,EAAE,GAAG,KAAK,EAAE,MAA4B,EAAE,OAA6C,EAAuB,EAAE;IAC3H,MAAM,GAAG,GAAG,aAAa,CAAkB,MAAM,CAAC,GAAc,EAAE,WAAW,CAAC,CAAA;IAC9E,OAAO,GAAG,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IACxE,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IAE5D,IAAI,KAAK,KAAK,aAAa,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,yBAAyB,CAAA;IACrC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAA;IAEjC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;QACtC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,MAAkC,EAAE,OAAuC,EAAqB,EAAE;IACtH,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;IACxB,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAe,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAC5E,CAAC,MAAM,CAAC,CACT,CAAA;IACD,MAAM,UAAU,GAAG,QAAQ,CAAa,KAAK,IAAI,EAAE;QACjD,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChC,OAAO,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAChC,CAAC,EAAE;QACD,GAAG,CAAC,QAAQ,EAAE;QACd,OAAO,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;KAC7D,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,GAAG,EAAE;gBACV,KAAK,UAAU,CAAC,KAAK,EAAE,CAAA;YACzB,CAAC,CAAA;QACH,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAExD,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE1C,MAAM,CAAC,MAAM,EAAE,GAAG,KAAK,EAAE,MAAgC,EAAE,OAA6C,EAAuB,EAAE;IAC/H,MAAM,GAAG,GAAG,aAAa,CAAkB,MAAM,CAAC,GAAc,EAAE,WAAW,CAAC,CAAA;IAC9E,OAAO,GAAG,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IACxE,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IAE5D,IAAI,KAAK,KAAK,iBAAiB,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,IAAI,yBAAyB,CAAA;IACrC,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAA;IAEjC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;gBACjC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;oBACzC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;gBAC/C,CAAC;YACH,CAAC,EAAE,MAAM,CAAC,CAAA;YAEV,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBACpC,OAAO,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAA;gBACzE,aAAa,CAAC,SAAS,CAAC,CAAA;YAC1B,CAAC,CAAC,CAAA;YAEF,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;QACtC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,MAAsC,EAAE,OAAuC,EAAqB,EAAE;IAC1H,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;IACxB,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAmB,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EACpF,CAAC,MAAM,CAAC,CACT,CAAA;IACD,MAAM,UAAU,GAAG,QAAQ,CAAa,KAAK,IAAI,EAAE;QACjD,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChC,OAAO,MAAM,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAChC,CAAC,EAAE;QACD,GAAG,CAAC,QAAQ,EAAE;QACd,OAAO,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;KAC7D,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,GAAG,EAAE;gBACV,KAAK,UAAU,CAAC,KAAK,EAAE,CAAA;YACzB,CAAC,CAAA;QACH,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@owlmeans/client-socket",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "scripts": {
6
7
  "build": "tsc -b",
@@ -23,19 +24,20 @@
23
24
  "react": "*"
24
25
  },
25
26
  "devDependencies": {
27
+ "@owlmeans/dep-config": "workspace:*",
26
28
  "@types/react": "^19.2.7",
27
29
  "nodemon": "^3.1.11",
28
- "typescript": "^5.8.3"
30
+ "typescript": "^6.0.2"
29
31
  },
30
32
  "dependencies": {
31
- "@owlmeans/auth": "^0.1.1",
32
- "@owlmeans/basic-envelope": "^0.1.1",
33
- "@owlmeans/client": "^0.1.1",
34
- "@owlmeans/client-context": "^0.1.1",
35
- "@owlmeans/client-module": "^0.1.1",
36
- "@owlmeans/context": "^0.1.1",
37
- "@owlmeans/module": "^0.1.1",
38
- "@owlmeans/socket": "^0.1.1"
33
+ "@owlmeans/auth": "^0.1.3",
34
+ "@owlmeans/basic-envelope": "^0.1.3",
35
+ "@owlmeans/client": "^0.1.3",
36
+ "@owlmeans/client-context": "^0.1.3",
37
+ "@owlmeans/client-entrypoint": "^0.1.3",
38
+ "@owlmeans/context": "^0.1.3",
39
+ "@owlmeans/entrypoint": "^0.1.3",
40
+ "@owlmeans/socket": "^0.1.3"
39
41
  },
40
42
  "publishConfig": {
41
43
  "access": "public"
package/src/helper.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { ClientModule } from '@owlmeans/client-module'
2
- import type { AbstractRequest } from '@owlmeans/module'
3
- import { provideRequest } from '@owlmeans/client-module'
4
- import { ModuleOutcome } from '@owlmeans/module'
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-module/utils'
12
+ import { urlCall } from '@owlmeans/client-entrypoint/utils'
13
13
  import { useEffect, useMemo } from 'react'
14
14
 
15
- export const ws = async (module: ClientModule<string>, request?: AbstractRequest<{ token?: string }>): Promise<Connection> => {
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 !== ModuleOutcome.Ok) {
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 | ClientModule<any>, request?: Partial<AbstractRequest<any>>): Connection | null => {
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.module<ClientModule>(module) : module,
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
- "../tsconfig.default.json",
4
- "../tsconfig.react.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.react.json"
5
5
  ],
6
6
  "compilerOptions": {
7
- "rootDir": "./src/", /* Specify the root folder within your source files. */
8
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
9
- "moduleResolution": "Bundler"
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
10
9
  },
11
- "exclude": [
12
- "./dist/**/*",
13
- "./build/**/*",
14
- "./*.ts"
15
- ]
16
- }
10
+ "exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
11
+ }