@fluxstack/live-client 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/dist/index.d.ts +96 -1
- package/dist/index.js +145 -6
- package/dist/index.js.map +1 -1
- package/dist/live-client.browser.global.js +145 -6
- package/dist/live-client.browser.global.js.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @fluxstack/live-client
|
|
2
|
+
|
|
3
|
+
Framework-agnostic browser client for `@fluxstack/live`.
|
|
4
|
+
|
|
5
|
+
Works with any UI framework (React, Vue, Svelte, vanilla JS) or no framework at all. For React-specific bindings, see `@fluxstack/live-react`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### NPM/Bun
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @fluxstack/live-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Browser (IIFE)
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="/live-client.js"></script>
|
|
19
|
+
<script>
|
|
20
|
+
const counter = FluxstackLive.useLive('Counter', { count: 0 })
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The IIFE bundle is served automatically by any transport adapter at `/live-client.js`.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Vanilla JS (useLive)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { useLive, onConnectionChange } from '@fluxstack/live-client'
|
|
32
|
+
|
|
33
|
+
// Mount a component (uses singleton connection)
|
|
34
|
+
const counter = useLive('Counter', { count: 0 })
|
|
35
|
+
|
|
36
|
+
// Listen for state changes
|
|
37
|
+
counter.on(state => {
|
|
38
|
+
document.getElementById('count').textContent = state.count
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Call server actions
|
|
42
|
+
document.getElementById('btn').onclick = () => counter.call('increment')
|
|
43
|
+
|
|
44
|
+
// Connection status
|
|
45
|
+
onConnectionChange(connected => {
|
|
46
|
+
console.log(connected ? 'Online' : 'Offline')
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Manual Connection
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { LiveConnection, LiveComponentHandle } from '@fluxstack/live-client'
|
|
54
|
+
|
|
55
|
+
const conn = new LiveConnection({
|
|
56
|
+
url: 'ws://localhost:3000/api/live/ws',
|
|
57
|
+
autoReconnect: true,
|
|
58
|
+
reconnectInterval: 1000,
|
|
59
|
+
})
|
|
60
|
+
await conn.connect()
|
|
61
|
+
|
|
62
|
+
const counter = new LiveComponentHandle(conn, {
|
|
63
|
+
componentName: 'Counter',
|
|
64
|
+
defaultState: { count: 0 },
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
counter.on(state => console.log('Count:', state.count))
|
|
68
|
+
await counter.call('increment')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
### `useLive(componentName, defaultState, options?)`
|
|
74
|
+
|
|
75
|
+
High-level API using a shared connection singleton:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const handle = useLive('Counter', { count: 0 }, {
|
|
79
|
+
url: 'ws://localhost:3000/api/live/ws', // Auto-detected if omitted
|
|
80
|
+
room: 'my-room', // Optional room
|
|
81
|
+
singleton: true, // Singleton mount
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
handle.on(state => { ... }) // State change listener
|
|
85
|
+
handle.call('action', payload) // Call server action
|
|
86
|
+
handle.destroy() // Unmount component
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `onConnectionChange(callback)`
|
|
90
|
+
|
|
91
|
+
Subscribe to connection status changes:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const unsubscribe = onConnectionChange(connected => { ... })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `getConnection()`
|
|
98
|
+
|
|
99
|
+
Access the shared connection singleton:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const conn = getConnection()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `LiveConnection`
|
|
106
|
+
|
|
107
|
+
WebSocket connection manager with auto-reconnect, state rehydration, and message routing:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const conn = new LiveConnection({
|
|
111
|
+
url: 'ws://localhost:3000/api/live/ws',
|
|
112
|
+
autoReconnect: true,
|
|
113
|
+
reconnectInterval: 1000,
|
|
114
|
+
auth: {
|
|
115
|
+
token: 'my-jwt-token',
|
|
116
|
+
provider: 'jwt',
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `LiveComponentHandle`
|
|
122
|
+
|
|
123
|
+
Handle to a remote component. Manages mounting, state updates, and action calls:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const handle = new LiveComponentHandle(conn, {
|
|
127
|
+
componentName: 'Counter',
|
|
128
|
+
defaultState: { count: 0 },
|
|
129
|
+
room: 'optional-room',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
handle.on(state => { ... }) // State listener
|
|
133
|
+
handle.call('action', payload) // Call action
|
|
134
|
+
handle.$state // Current state
|
|
135
|
+
handle.$connected // Connection status
|
|
136
|
+
handle.destroy() // Unmount
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `RoomManager`
|
|
140
|
+
|
|
141
|
+
Client-side room event management:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const rooms = new RoomManager(conn)
|
|
145
|
+
const room = rooms.join('chat-room')
|
|
146
|
+
room.on('message:new', data => { ... })
|
|
147
|
+
room.emit('typing', { user: 'Alice' })
|
|
148
|
+
room.leave()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `ChunkedUploader`
|
|
152
|
+
|
|
153
|
+
Stream file uploads over WebSocket:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const uploader = new ChunkedUploader(conn, {
|
|
157
|
+
chunkSize: 64 * 1024,
|
|
158
|
+
onProgress: (progress) => { ... },
|
|
159
|
+
})
|
|
160
|
+
await uploader.upload(file)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### State Persistence
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { persistState, getPersistedState, clearPersistedState } from '@fluxstack/live-client'
|
|
167
|
+
|
|
168
|
+
persistState('Counter', componentId, state, signature)
|
|
169
|
+
const saved = getPersistedState('Counter', componentId)
|
|
170
|
+
clearPersistedState('Counter', componentId)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Browser IIFE Build
|
|
174
|
+
|
|
175
|
+
The package includes a pre-built browser bundle at `dist/live-client.browser.global.js` that exposes a `FluxstackLive` global with all exports. Transport adapters serve this automatically at `/live-client.js`.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ declare class LiveConnection {
|
|
|
45
45
|
private reconnectTimeout;
|
|
46
46
|
private heartbeatInterval;
|
|
47
47
|
private componentCallbacks;
|
|
48
|
+
private binaryCallbacks;
|
|
48
49
|
private pendingRequests;
|
|
49
50
|
private stateListeners;
|
|
50
51
|
private _state;
|
|
@@ -73,6 +74,10 @@ declare class LiveConnection {
|
|
|
73
74
|
sendMessageAndWait(message: WebSocketMessage, timeout?: number): Promise<WebSocketResponse>;
|
|
74
75
|
/** Send binary data and wait for response (for file uploads) */
|
|
75
76
|
sendBinaryAndWait(data: ArrayBuffer, requestId: string, timeout?: number): Promise<WebSocketResponse>;
|
|
77
|
+
/** Parse and route a binary BINARY_STATE_DELTA frame */
|
|
78
|
+
private handleBinaryMessage;
|
|
79
|
+
/** Register a binary message handler for a component */
|
|
80
|
+
registerBinaryHandler(componentId: string, callback: (payload: Uint8Array) => void): () => void;
|
|
76
81
|
/** Register a component message callback */
|
|
77
82
|
registerComponent(componentId: string, callback: ComponentCallback): () => void;
|
|
78
83
|
/** Unregister a component */
|
|
@@ -139,12 +144,25 @@ declare class LiveComponentHandle<TState extends Record<string, any> = Record<st
|
|
|
139
144
|
* Returns the action's return value.
|
|
140
145
|
*/
|
|
141
146
|
call<R = any>(action: string, payload?: Record<string, any>): Promise<R>;
|
|
147
|
+
/**
|
|
148
|
+
* Fire an action without waiting for a response (fire-and-forget).
|
|
149
|
+
* Useful for high-frequency operations like game input where the
|
|
150
|
+
* server doesn't need to send back a result.
|
|
151
|
+
*/
|
|
152
|
+
fire(action: string, payload?: Record<string, any>): void;
|
|
142
153
|
/**
|
|
143
154
|
* Subscribe to state changes.
|
|
144
155
|
* Callback receives the full new state and the delta (or null for full updates).
|
|
145
156
|
* Returns an unsubscribe function.
|
|
146
157
|
*/
|
|
147
158
|
onStateChange(callback: StateChangeCallback<TState>): () => void;
|
|
159
|
+
/**
|
|
160
|
+
* Register a binary decoder for this component.
|
|
161
|
+
* When the server sends a BINARY_STATE_DELTA frame targeting this component,
|
|
162
|
+
* the decoder converts the raw payload into a delta object which is merged into state.
|
|
163
|
+
* Returns an unsubscribe function.
|
|
164
|
+
*/
|
|
165
|
+
setBinaryDecoder(decoder: (buffer: Uint8Array) => Record<string, any>): () => void;
|
|
148
166
|
/**
|
|
149
167
|
* Subscribe to errors.
|
|
150
168
|
* Returns an unsubscribe function.
|
|
@@ -356,4 +374,81 @@ declare class StateValidator {
|
|
|
356
374
|
static updateValidation<T>(hybridState: HybridState<T>, source?: 'client' | 'server' | 'mount'): HybridState<T>;
|
|
357
375
|
}
|
|
358
376
|
|
|
359
|
-
|
|
377
|
+
/** Status listeners for the shared connection */
|
|
378
|
+
type ConnectionStatusCallback = (connected: boolean) => void;
|
|
379
|
+
interface UseLiveOptions {
|
|
380
|
+
/** WebSocket URL. Auto-detected from window.location if omitted. */
|
|
381
|
+
url?: string;
|
|
382
|
+
/** Room to join on mount */
|
|
383
|
+
room?: string;
|
|
384
|
+
/** User ID for component isolation */
|
|
385
|
+
userId?: string;
|
|
386
|
+
/** Auto-mount when connected. Default: true */
|
|
387
|
+
autoMount?: boolean;
|
|
388
|
+
/** Enable debug logging. Default: false */
|
|
389
|
+
debug?: boolean;
|
|
390
|
+
}
|
|
391
|
+
interface UseLiveHandle<TState extends Record<string, any> = Record<string, any>> {
|
|
392
|
+
/** Call a server action */
|
|
393
|
+
call: <R = any>(action: string, payload?: Record<string, any>) => Promise<R>;
|
|
394
|
+
/** Subscribe to state changes. Returns unsubscribe function. */
|
|
395
|
+
on: (callback: (state: TState, delta: Partial<TState> | null) => void) => () => void;
|
|
396
|
+
/** Subscribe to errors. Returns unsubscribe function. */
|
|
397
|
+
onError: (callback: (error: string) => void) => () => void;
|
|
398
|
+
/** Current state (read-only snapshot) */
|
|
399
|
+
readonly state: Readonly<TState>;
|
|
400
|
+
/** Whether the component is mounted on the server */
|
|
401
|
+
readonly mounted: boolean;
|
|
402
|
+
/** Server-assigned component ID */
|
|
403
|
+
readonly componentId: string | null;
|
|
404
|
+
/** Last error message */
|
|
405
|
+
readonly error: string | null;
|
|
406
|
+
/** Destroy the component and clean up */
|
|
407
|
+
destroy: () => void;
|
|
408
|
+
/** Access the underlying LiveComponentHandle */
|
|
409
|
+
readonly handle: LiveComponentHandle<TState>;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Create a live component with minimal boilerplate.
|
|
413
|
+
* Manages the WebSocket connection automatically (singleton).
|
|
414
|
+
*
|
|
415
|
+
* @example Browser IIFE
|
|
416
|
+
* ```html
|
|
417
|
+
* <script src="/live-client.js"></script>
|
|
418
|
+
* <script>
|
|
419
|
+
* const counter = FluxstackLive.useLive('Counter', { count: 0 })
|
|
420
|
+
* counter.on(state => {
|
|
421
|
+
* document.getElementById('count').textContent = state.count
|
|
422
|
+
* })
|
|
423
|
+
* document.querySelector('.inc').onclick = () => counter.call('increment')
|
|
424
|
+
* </script>
|
|
425
|
+
* ```
|
|
426
|
+
*
|
|
427
|
+
* @example ES modules
|
|
428
|
+
* ```ts
|
|
429
|
+
* import { useLive } from '@fluxstack/live-client'
|
|
430
|
+
* const counter = useLive('Counter', { count: 0 }, { url: 'ws://localhost:3000/api/live/ws' })
|
|
431
|
+
* counter.on(state => console.log(state.count))
|
|
432
|
+
* counter.call('increment')
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
declare function useLive<TState extends Record<string, any> = Record<string, any>>(componentName: string, initialState: TState, options?: UseLiveOptions): UseLiveHandle<TState>;
|
|
436
|
+
/**
|
|
437
|
+
* Subscribe to the shared connection status (connected/disconnected).
|
|
438
|
+
* Useful for showing a global status indicator.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```js
|
|
442
|
+
* FluxstackLive.onConnectionChange(connected => {
|
|
443
|
+
* statusEl.textContent = connected ? 'Connected' : 'Disconnected'
|
|
444
|
+
* })
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
declare function onConnectionChange(callback: ConnectionStatusCallback): () => void;
|
|
448
|
+
/**
|
|
449
|
+
* Get or create the shared connection instance.
|
|
450
|
+
* Useful when you need direct access to the connection.
|
|
451
|
+
*/
|
|
452
|
+
declare function getConnection(url?: string): LiveConnection;
|
|
453
|
+
|
|
454
|
+
export { type AdaptiveChunkConfig, AdaptiveChunkSizer, type ChunkMetrics, type ChunkedUploadOptions, type ChunkedUploadState, ChunkedUploader, type EventHandler, type HybridState, type LiveAuthOptions, LiveComponentHandle, type LiveComponentOptions, LiveConnection, type LiveConnectionOptions, type LiveConnectionState, type PersistedState, type RoomClientMessage, type RoomHandle, RoomManager, type RoomManagerOptions, type RoomProxy, type RoomServerMessage, type StateConflict, type StateValidation, StateValidator, type Unsubscribe, type UseLiveHandle, type UseLiveOptions, clearPersistedState, createBinaryChunkMessage, getConnection, getPersistedState, onConnectionChange, persistState, useLive };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var LiveConnection = class {
|
|
|
6
6
|
reconnectTimeout = null;
|
|
7
7
|
heartbeatInterval = null;
|
|
8
8
|
componentCallbacks = /* @__PURE__ */ new Map();
|
|
9
|
+
binaryCallbacks = /* @__PURE__ */ new Map();
|
|
9
10
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
10
11
|
stateListeners = /* @__PURE__ */ new Set();
|
|
11
12
|
_state = {
|
|
@@ -86,6 +87,7 @@ var LiveConnection = class {
|
|
|
86
87
|
this.log("Connecting...", { url });
|
|
87
88
|
try {
|
|
88
89
|
const ws = new WebSocket(url);
|
|
90
|
+
ws.binaryType = "arraybuffer";
|
|
89
91
|
this.ws = ws;
|
|
90
92
|
ws.onopen = () => {
|
|
91
93
|
this.log("Connected");
|
|
@@ -94,19 +96,34 @@ var LiveConnection = class {
|
|
|
94
96
|
this.startHeartbeat();
|
|
95
97
|
};
|
|
96
98
|
ws.onmessage = (event) => {
|
|
99
|
+
if (event.data instanceof ArrayBuffer) {
|
|
100
|
+
this.handleBinaryMessage(new Uint8Array(event.data));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
97
103
|
try {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
const parsed = JSON.parse(event.data);
|
|
105
|
+
if (Array.isArray(parsed)) {
|
|
106
|
+
for (const msg of parsed) {
|
|
107
|
+
this.log("Received", { type: msg.type, componentId: msg.componentId });
|
|
108
|
+
this.handleMessage(msg);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
this.log("Received", { type: parsed.type, componentId: parsed.componentId });
|
|
112
|
+
this.handleMessage(parsed);
|
|
113
|
+
}
|
|
101
114
|
} catch {
|
|
102
115
|
this.log("Failed to parse message");
|
|
103
116
|
this.setState({ error: "Failed to parse message" });
|
|
104
117
|
}
|
|
105
118
|
};
|
|
106
|
-
ws.onclose = () => {
|
|
107
|
-
this.log("Disconnected");
|
|
119
|
+
ws.onclose = (event) => {
|
|
120
|
+
this.log("Disconnected", { code: event.code, reason: event.reason });
|
|
108
121
|
this.setState({ connected: false, connecting: false, connectionId: null });
|
|
109
122
|
this.stopHeartbeat();
|
|
123
|
+
if (event.code === 4003) {
|
|
124
|
+
this.setState({ error: "Connection rejected: origin not allowed" });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
110
127
|
this.attemptReconnect();
|
|
111
128
|
};
|
|
112
129
|
ws.onerror = () => {
|
|
@@ -281,6 +298,25 @@ var LiveConnection = class {
|
|
|
281
298
|
}
|
|
282
299
|
});
|
|
283
300
|
}
|
|
301
|
+
/** Parse and route a binary BINARY_STATE_DELTA frame */
|
|
302
|
+
handleBinaryMessage(buffer) {
|
|
303
|
+
if (buffer.length < 3 || buffer[0] !== 1) return;
|
|
304
|
+
const idLen = buffer[1];
|
|
305
|
+
if (buffer.length < 2 + idLen) return;
|
|
306
|
+
const componentId = new TextDecoder().decode(buffer.subarray(2, 2 + idLen));
|
|
307
|
+
const payload = buffer.subarray(2 + idLen);
|
|
308
|
+
const callback = this.binaryCallbacks.get(componentId);
|
|
309
|
+
if (callback) {
|
|
310
|
+
callback(payload);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/** Register a binary message handler for a component */
|
|
314
|
+
registerBinaryHandler(componentId, callback) {
|
|
315
|
+
this.binaryCallbacks.set(componentId, callback);
|
|
316
|
+
return () => {
|
|
317
|
+
this.binaryCallbacks.delete(componentId);
|
|
318
|
+
};
|
|
319
|
+
}
|
|
284
320
|
/** Register a component message callback */
|
|
285
321
|
registerComponent(componentId, callback) {
|
|
286
322
|
this.log("Registering component", componentId);
|
|
@@ -316,6 +352,7 @@ var LiveConnection = class {
|
|
|
316
352
|
destroy() {
|
|
317
353
|
this.disconnect();
|
|
318
354
|
this.componentCallbacks.clear();
|
|
355
|
+
this.binaryCallbacks.clear();
|
|
319
356
|
for (const [, req] of this.pendingRequests) {
|
|
320
357
|
clearTimeout(req.timeout);
|
|
321
358
|
req.reject(new Error("Connection destroyed"));
|
|
@@ -474,6 +511,21 @@ var LiveComponentHandle = class {
|
|
|
474
511
|
}
|
|
475
512
|
return response.result;
|
|
476
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Fire an action without waiting for a response (fire-and-forget).
|
|
516
|
+
* Useful for high-frequency operations like game input where the
|
|
517
|
+
* server doesn't need to send back a result.
|
|
518
|
+
*/
|
|
519
|
+
fire(action, payload = {}) {
|
|
520
|
+
if (!this._mounted || !this._componentId) return;
|
|
521
|
+
this.connection.sendMessage({
|
|
522
|
+
type: "CALL_ACTION",
|
|
523
|
+
componentId: this._componentId,
|
|
524
|
+
action,
|
|
525
|
+
payload,
|
|
526
|
+
expectResponse: false
|
|
527
|
+
});
|
|
528
|
+
}
|
|
477
529
|
// ── State ──
|
|
478
530
|
/**
|
|
479
531
|
* Subscribe to state changes.
|
|
@@ -486,6 +538,26 @@ var LiveComponentHandle = class {
|
|
|
486
538
|
this.stateListeners.delete(callback);
|
|
487
539
|
};
|
|
488
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Register a binary decoder for this component.
|
|
543
|
+
* When the server sends a BINARY_STATE_DELTA frame targeting this component,
|
|
544
|
+
* the decoder converts the raw payload into a delta object which is merged into state.
|
|
545
|
+
* Returns an unsubscribe function.
|
|
546
|
+
*/
|
|
547
|
+
setBinaryDecoder(decoder) {
|
|
548
|
+
if (!this._componentId) {
|
|
549
|
+
throw new Error("Component must be mounted before setting binary decoder");
|
|
550
|
+
}
|
|
551
|
+
return this.connection.registerBinaryHandler(this._componentId, (payload) => {
|
|
552
|
+
try {
|
|
553
|
+
const delta = decoder(payload);
|
|
554
|
+
this._state = { ...this._state, ...delta };
|
|
555
|
+
this.notifyStateChange(this._state, delta);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
console.error("Binary decode error:", e);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
}
|
|
489
561
|
/**
|
|
490
562
|
* Subscribe to errors.
|
|
491
563
|
* Returns an unsubscribe function.
|
|
@@ -1133,6 +1205,70 @@ var StateValidator = class {
|
|
|
1133
1205
|
};
|
|
1134
1206
|
}
|
|
1135
1207
|
};
|
|
1208
|
+
|
|
1209
|
+
// src/index.ts
|
|
1210
|
+
var _sharedConnection = null;
|
|
1211
|
+
var _sharedConnectionUrl = null;
|
|
1212
|
+
var _statusListeners = /* @__PURE__ */ new Set();
|
|
1213
|
+
function getOrCreateConnection(url) {
|
|
1214
|
+
const resolvedUrl = url ?? `ws://${typeof location !== "undefined" ? location.host : "localhost:3000"}/api/live/ws`;
|
|
1215
|
+
if (_sharedConnection && _sharedConnectionUrl === resolvedUrl) {
|
|
1216
|
+
return _sharedConnection;
|
|
1217
|
+
}
|
|
1218
|
+
if (_sharedConnection) {
|
|
1219
|
+
_sharedConnection.destroy();
|
|
1220
|
+
}
|
|
1221
|
+
_sharedConnection = new LiveConnection({ url: resolvedUrl });
|
|
1222
|
+
_sharedConnectionUrl = resolvedUrl;
|
|
1223
|
+
_sharedConnection.onStateChange((state) => {
|
|
1224
|
+
for (const cb of _statusListeners) {
|
|
1225
|
+
cb(state.connected);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
return _sharedConnection;
|
|
1229
|
+
}
|
|
1230
|
+
function useLive(componentName, initialState, options = {}) {
|
|
1231
|
+
const { url, room, userId, autoMount = true, debug = false } = options;
|
|
1232
|
+
const connection = getOrCreateConnection(url);
|
|
1233
|
+
const handle = new LiveComponentHandle(connection, componentName, {
|
|
1234
|
+
initialState,
|
|
1235
|
+
room,
|
|
1236
|
+
userId,
|
|
1237
|
+
autoMount,
|
|
1238
|
+
debug
|
|
1239
|
+
});
|
|
1240
|
+
return {
|
|
1241
|
+
call: (action, payload) => handle.call(action, payload ?? {}),
|
|
1242
|
+
on: (callback) => handle.onStateChange(callback),
|
|
1243
|
+
onError: (callback) => handle.onError(callback),
|
|
1244
|
+
get state() {
|
|
1245
|
+
return handle.state;
|
|
1246
|
+
},
|
|
1247
|
+
get mounted() {
|
|
1248
|
+
return handle.mounted;
|
|
1249
|
+
},
|
|
1250
|
+
get componentId() {
|
|
1251
|
+
return handle.componentId;
|
|
1252
|
+
},
|
|
1253
|
+
get error() {
|
|
1254
|
+
return handle.error;
|
|
1255
|
+
},
|
|
1256
|
+
destroy: () => handle.destroy(),
|
|
1257
|
+
handle
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function onConnectionChange(callback) {
|
|
1261
|
+
_statusListeners.add(callback);
|
|
1262
|
+
if (_sharedConnection) {
|
|
1263
|
+
callback(_sharedConnection.state.connected);
|
|
1264
|
+
}
|
|
1265
|
+
return () => {
|
|
1266
|
+
_statusListeners.delete(callback);
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function getConnection(url) {
|
|
1270
|
+
return getOrCreateConnection(url);
|
|
1271
|
+
}
|
|
1136
1272
|
export {
|
|
1137
1273
|
AdaptiveChunkSizer,
|
|
1138
1274
|
ChunkedUploader,
|
|
@@ -1142,7 +1278,10 @@ export {
|
|
|
1142
1278
|
StateValidator,
|
|
1143
1279
|
clearPersistedState,
|
|
1144
1280
|
createBinaryChunkMessage,
|
|
1281
|
+
getConnection,
|
|
1145
1282
|
getPersistedState,
|
|
1146
|
-
|
|
1283
|
+
onConnectionChange,
|
|
1284
|
+
persistState,
|
|
1285
|
+
useLive
|
|
1147
1286
|
};
|
|
1148
1287
|
//# sourceMappingURL=index.js.map
|