@fluxstack/live-client 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,486 @@
1
+ import { WebSocketMessage, WebSocketResponse, FileUploadCompleteResponse, BinaryChunkHeader } from '@fluxstack/live';
2
+
3
+ /** Auth credentials to send during WebSocket connection */
4
+ interface LiveAuthOptions {
5
+ /** JWT or opaque token */
6
+ token?: string;
7
+ /** Provider name (if multiple auth providers configured) */
8
+ provider?: string;
9
+ /** Additional credentials (publicKey, signature, etc.) */
10
+ [key: string]: unknown;
11
+ }
12
+ interface LiveConnectionOptions {
13
+ /** WebSocket URL. Auto-detected from window.location if omitted. */
14
+ url?: string;
15
+ /** Auth credentials to send on connection */
16
+ auth?: LiveAuthOptions;
17
+ /** Auto-connect on creation. Default: true */
18
+ autoConnect?: boolean;
19
+ /** Reconnect interval in ms. Default: 1000 */
20
+ reconnectInterval?: number;
21
+ /** Max reconnect attempts. Default: 5 */
22
+ maxReconnectAttempts?: number;
23
+ /** Heartbeat interval in ms. Default: 30000 */
24
+ heartbeatInterval?: number;
25
+ /** Enable debug logging. Default: false */
26
+ debug?: boolean;
27
+ }
28
+ /** Auth state exposed to the client */
29
+ interface LiveClientAuth {
30
+ authenticated: boolean;
31
+ /** Session data from the server. Shape defined by your LiveAuthProvider. */
32
+ session: Record<string, unknown> | null;
33
+ }
34
+ interface LiveConnectionState {
35
+ connected: boolean;
36
+ connecting: boolean;
37
+ error: string | null;
38
+ connectionId: string | null;
39
+ authenticated: boolean;
40
+ /** Auth context with session data */
41
+ auth: LiveClientAuth;
42
+ }
43
+ type StateChangeCallback$1 = (state: LiveConnectionState) => void;
44
+ type ComponentCallback = (message: WebSocketResponse) => void;
45
+ /**
46
+ * Framework-agnostic WebSocket connection manager.
47
+ * Handles reconnection, heartbeat, request-response pattern, and message routing.
48
+ */
49
+ declare class LiveConnection {
50
+ private ws;
51
+ private options;
52
+ private reconnectAttempts;
53
+ private reconnectTimeout;
54
+ private heartbeatInterval;
55
+ private componentCallbacks;
56
+ private binaryCallbacks;
57
+ private roomBinaryHandlers;
58
+ private pendingRequests;
59
+ private stateListeners;
60
+ private _state;
61
+ constructor(options?: LiveConnectionOptions);
62
+ get state(): LiveConnectionState;
63
+ /** Subscribe to connection state changes */
64
+ onStateChange(callback: StateChangeCallback$1): () => void;
65
+ private setState;
66
+ private getWebSocketUrl;
67
+ private log;
68
+ /** Generate unique request ID */
69
+ generateRequestId(): string;
70
+ /** Connect to WebSocket server */
71
+ connect(): void;
72
+ /** Disconnect from WebSocket server */
73
+ disconnect(): void;
74
+ /** Manual reconnect */
75
+ reconnect(): void;
76
+ private attemptReconnect;
77
+ private startHeartbeat;
78
+ private stopHeartbeat;
79
+ private handleMessage;
80
+ /** Send message without waiting for response */
81
+ sendMessage(message: WebSocketMessage): Promise<void>;
82
+ /** Send message and wait for response */
83
+ sendMessageAndWait(message: WebSocketMessage, timeout?: number): Promise<WebSocketResponse>;
84
+ /** Send binary data and wait for response (for file uploads) */
85
+ sendBinaryAndWait(data: ArrayBuffer, requestId: string, timeout?: number): Promise<WebSocketResponse>;
86
+ /** Parse and route binary frames (state delta, room events, room state) */
87
+ private handleBinaryMessage;
88
+ /** Register a handler for binary room frames (0x02 / 0x03). Returns unsubscribe. */
89
+ registerRoomBinaryHandler(callback: (frame: Uint8Array) => void): () => void;
90
+ /** Register a binary message handler for a component */
91
+ registerBinaryHandler(componentId: string, callback: (payload: Uint8Array) => void): () => void;
92
+ /** Register a component message callback */
93
+ registerComponent(componentId: string, callback: ComponentCallback): () => void;
94
+ /** Unregister a component */
95
+ unregisterComponent(componentId: string): void;
96
+ /** Authenticate (or re-authenticate) the WebSocket connection */
97
+ authenticate(credentials: LiveAuthOptions): Promise<boolean>;
98
+ /** Get the raw WebSocket instance */
99
+ getWebSocket(): WebSocket | null;
100
+ /** Destroy the connection and clean up all resources */
101
+ destroy(): void;
102
+ }
103
+
104
+ interface LiveComponentOptions<TState = Record<string, any>> {
105
+ /** Initial state to merge with server defaults */
106
+ initialState?: Partial<TState>;
107
+ /** Room to join on mount */
108
+ room?: string;
109
+ /** User ID for component isolation */
110
+ userId?: string;
111
+ /** Auto-mount when connection is ready. Default: true */
112
+ autoMount?: boolean;
113
+ /** Enable debug logging. Default: false */
114
+ debug?: boolean;
115
+ }
116
+ type StateChangeCallback<TState> = (state: TState, delta: Partial<TState> | null) => void;
117
+ type ErrorCallback = (error: string) => void;
118
+ /**
119
+ * High-level handle for a live component instance.
120
+ * Manages mount lifecycle, state sync, and action calling.
121
+ * Framework-agnostic — works with vanilla JS, Vue, Svelte, etc.
122
+ */
123
+ declare class LiveComponentHandle<TState extends Record<string, any> = Record<string, any>> {
124
+ private connection;
125
+ private componentName;
126
+ private options;
127
+ private _componentId;
128
+ private _state;
129
+ private _mounted;
130
+ private _mounting;
131
+ private _error;
132
+ private stateListeners;
133
+ private errorListeners;
134
+ private unregisterComponent;
135
+ private unsubConnection;
136
+ constructor(connection: LiveConnection, componentName: string, options?: LiveComponentOptions<TState>);
137
+ /** Current component state */
138
+ get state(): Readonly<TState>;
139
+ /** Server-assigned component ID (null before mount) */
140
+ get componentId(): string | null;
141
+ /** Whether the component has been mounted */
142
+ get mounted(): boolean;
143
+ /** Whether the component is currently mounting */
144
+ get mounting(): boolean;
145
+ /** Last error message */
146
+ get error(): string | null;
147
+ /** Mount the component on the server */
148
+ mount(): Promise<void>;
149
+ /** Unmount the component from the server */
150
+ unmount(): Promise<void>;
151
+ /** Destroy the handle and clean up all resources */
152
+ destroy(): void;
153
+ /**
154
+ * Call an action on the server component.
155
+ * Returns the action's return value.
156
+ */
157
+ call<R = any>(action: string, payload?: Record<string, any>): Promise<R>;
158
+ /**
159
+ * Fire an action without waiting for a response (fire-and-forget).
160
+ * Useful for high-frequency operations like game input where the
161
+ * server doesn't need to send back a result.
162
+ */
163
+ fire(action: string, payload?: Record<string, any>): void;
164
+ /**
165
+ * Subscribe to state changes.
166
+ * Callback receives the full new state and the delta (or null for full updates).
167
+ * Returns an unsubscribe function.
168
+ */
169
+ onStateChange(callback: StateChangeCallback<TState>): () => void;
170
+ /**
171
+ * Register a binary decoder for this component.
172
+ * When the server sends a BINARY_STATE_DELTA frame targeting this component,
173
+ * the decoder converts the raw payload into a delta object which is merged into state.
174
+ * Returns an unsubscribe function.
175
+ */
176
+ setBinaryDecoder(decoder: (buffer: Uint8Array) => Record<string, any>): () => void;
177
+ /**
178
+ * Subscribe to errors.
179
+ * Returns an unsubscribe function.
180
+ */
181
+ onError(callback: ErrorCallback): () => void;
182
+ private handleServerMessage;
183
+ private notifyStateChange;
184
+ private notifyError;
185
+ private cleanup;
186
+ private log;
187
+ }
188
+
189
+ type EventHandler<T = any> = (data: T) => void;
190
+ type Unsubscribe = () => void;
191
+ /** Reserved keys on RoomHandle/RoomProxy — cannot be state fields */
192
+ type RoomReservedKeys = 'id' | 'joined' | 'state' | 'join' | 'leave' | 'emit' | 'on' | 'onSystem' | 'setState';
193
+ /** State fields accessible directly on handle/proxy (excludes reserved method names) */
194
+ type RoomStateFields<TState> = TState extends Record<string, any> ? {
195
+ readonly [K in Exclude<keyof TState, RoomReservedKeys>]: TState[K];
196
+ } : unknown;
197
+ /** Message from client to server */
198
+ interface RoomClientMessage {
199
+ type: 'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_GET' | 'ROOM_STATE_SET';
200
+ componentId: string;
201
+ roomId: string;
202
+ event?: string;
203
+ data?: any;
204
+ timestamp: number;
205
+ }
206
+ /** Message from server to client */
207
+ interface RoomServerMessage {
208
+ type: 'ROOM_EVENT' | 'ROOM_STATE' | 'ROOM_SYSTEM' | 'ROOM_JOINED' | 'ROOM_LEFT';
209
+ componentId: string;
210
+ roomId: string;
211
+ event: string;
212
+ data: any;
213
+ timestamp: number;
214
+ }
215
+ /** Interface of an individual room handle */
216
+ type RoomHandle<TState = any, TEvents extends Record<string, any> = Record<string, any>> = {
217
+ readonly id: string;
218
+ readonly joined: boolean;
219
+ readonly state: TState;
220
+ join: (initialState?: TState) => Promise<void>;
221
+ leave: () => Promise<void>;
222
+ emit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => void;
223
+ on: <K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>) => Unsubscribe;
224
+ onSystem: (event: string, handler: EventHandler) => Unsubscribe;
225
+ setState: (updates: Partial<TState>) => void;
226
+ } & RoomStateFields<TState>;
227
+ /** Infer TEvents from a LiveRoom class (via $events brand) or use T directly as events map */
228
+ type InferRoomEvents<T> = T extends {
229
+ $events: infer E extends Record<string, any>;
230
+ } ? E : T extends Record<string, any> ? T : Record<string, any>;
231
+ /** Proxy interface for $room - callable as function or object */
232
+ type RoomProxy<TState = any, TEvents extends Record<string, any> = Record<string, any>> = {
233
+ /** Get a typed room handle. Pass the Room class or events interface as generic:
234
+ * `$room<CounterRoom>('counter:global').on('counter:updated', data => ...)` */
235
+ <T = TEvents>(roomId: string): RoomHandle<any, InferRoomEvents<T>>;
236
+ readonly id: string | null;
237
+ readonly joined: boolean;
238
+ readonly state: TState;
239
+ join: (initialState?: TState) => Promise<void>;
240
+ leave: () => Promise<void>;
241
+ emit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => void;
242
+ on: <K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>) => Unsubscribe;
243
+ onSystem: (event: string, handler: EventHandler) => Unsubscribe;
244
+ setState: (updates: Partial<TState>) => void;
245
+ } & RoomStateFields<TState>;
246
+ interface RoomManagerOptions {
247
+ componentId: string | null;
248
+ defaultRoom?: string;
249
+ sendMessage: (msg: any) => void;
250
+ sendMessageAndWait: (msg: any, timeout?: number) => Promise<any>;
251
+ onMessage: (handler: (msg: RoomServerMessage) => void) => Unsubscribe;
252
+ /** Optional: register for binary room frames (0x02 ROOM_EVENT, 0x03 ROOM_STATE) */
253
+ onBinaryMessage?: (handler: (frame: Uint8Array) => void) => Unsubscribe;
254
+ }
255
+ /** Client-side room manager. Framework-agnostic. */
256
+ declare class RoomManager<TState = any, TEvents extends Record<string, any> = Record<string, any>> {
257
+ private componentId;
258
+ private defaultRoom;
259
+ private rooms;
260
+ private handles;
261
+ private sendMessage;
262
+ private sendMessageAndWait;
263
+ private globalUnsubscribe;
264
+ private binaryUnsubscribe;
265
+ private onBinaryMessage;
266
+ private onMessageFactory;
267
+ constructor(options: RoomManagerOptions);
268
+ /** Re-subscribe message and binary handlers (needed after destroy/remount in React Strict Mode) */
269
+ resubscribe(): void;
270
+ private handleServerMessage;
271
+ /** Handle binary room frames (0x02 ROOM_EVENT, 0x03 ROOM_STATE) */
272
+ private handleBinaryFrame;
273
+ private getOrCreateRoom;
274
+ /** Create handle for a specific room (cached) */
275
+ createHandle(roomId: string): RoomHandle<TState, TEvents>;
276
+ /** Create the $room proxy */
277
+ createProxy(): RoomProxy<TState, TEvents>;
278
+ /** List of rooms currently joined */
279
+ getJoinedRooms(): string[];
280
+ /** Update componentId (when component mounts) */
281
+ setComponentId(id: string | null): void;
282
+ /** Cleanup — unsubscribes handlers but keeps factory refs for resubscribe() */
283
+ destroy(): void;
284
+ }
285
+
286
+ interface AdaptiveChunkConfig {
287
+ minChunkSize: number;
288
+ maxChunkSize: number;
289
+ initialChunkSize: number;
290
+ targetLatency: number;
291
+ adjustmentFactor: number;
292
+ measurementWindow: number;
293
+ }
294
+ interface ChunkMetrics {
295
+ chunkIndex: number;
296
+ chunkSize: number;
297
+ startTime: number;
298
+ endTime: number;
299
+ latency: number;
300
+ throughput: number;
301
+ success: boolean;
302
+ }
303
+ declare class AdaptiveChunkSizer {
304
+ private config;
305
+ private currentChunkSize;
306
+ private metrics;
307
+ private consecutiveErrors;
308
+ private consecutiveSuccesses;
309
+ constructor(config?: Partial<AdaptiveChunkConfig>);
310
+ getChunkSize(): number;
311
+ recordChunkStart(_chunkIndex: number): number;
312
+ recordChunkComplete(chunkIndex: number, chunkSize: number, startTime: number, success: boolean): void;
313
+ private adjustUp;
314
+ private adjustDown;
315
+ getAverageThroughput(): number;
316
+ getStats(): {
317
+ currentChunkSize: number;
318
+ averageThroughput: number;
319
+ consecutiveSuccesses: number;
320
+ consecutiveErrors: number;
321
+ totalMeasurements: number;
322
+ };
323
+ reset(): void;
324
+ }
325
+ /**
326
+ * Creates a binary message with header + data
327
+ * Format: [4 bytes header length LE][JSON header][binary data]
328
+ */
329
+ declare function createBinaryChunkMessage(header: BinaryChunkHeader, chunkData: Uint8Array): ArrayBuffer;
330
+ interface ChunkedUploadOptions {
331
+ chunkSize?: number;
332
+ maxFileSize?: number;
333
+ allowedTypes?: string[];
334
+ sendMessageAndWait: (message: any, timeout?: number) => Promise<any>;
335
+ sendBinaryAndWait?: (data: ArrayBuffer, requestId: string, timeout?: number) => Promise<any>;
336
+ onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void;
337
+ onComplete?: (response: FileUploadCompleteResponse) => void;
338
+ onError?: (error: string) => void;
339
+ adaptiveChunking?: boolean;
340
+ adaptiveConfig?: Partial<AdaptiveChunkConfig>;
341
+ useBinaryProtocol?: boolean;
342
+ }
343
+ interface ChunkedUploadState {
344
+ uploading: boolean;
345
+ progress: number;
346
+ error: string | null;
347
+ uploadId: string | null;
348
+ bytesUploaded: number;
349
+ totalBytes: number;
350
+ }
351
+ /**
352
+ * Framework-agnostic chunked file uploader.
353
+ * Manages the upload lifecycle without any UI framework dependency.
354
+ */
355
+ declare class ChunkedUploader {
356
+ private componentId;
357
+ private options;
358
+ private abortController;
359
+ private adaptiveSizer;
360
+ private _state;
361
+ private stateListeners;
362
+ constructor(componentId: string, options: ChunkedUploadOptions);
363
+ get state(): ChunkedUploadState;
364
+ onStateChange(callback: (state: ChunkedUploadState) => void): () => void;
365
+ private setState;
366
+ uploadFile(file: File): Promise<void>;
367
+ cancelUpload(): void;
368
+ reset(): void;
369
+ }
370
+
371
+ interface PersistedState {
372
+ componentName: string;
373
+ signedState: any;
374
+ room?: string;
375
+ userId?: string;
376
+ lastUpdate: number;
377
+ }
378
+ declare function persistState(enabled: boolean, name: string, signedState: any, room?: string, userId?: string): void;
379
+ declare function getPersistedState(enabled: boolean, name: string): PersistedState | null;
380
+ declare function clearPersistedState(enabled: boolean, name: string): void;
381
+
382
+ interface StateValidation {
383
+ checksum: string;
384
+ version: number;
385
+ timestamp: number;
386
+ source: 'client' | 'server' | 'mount';
387
+ }
388
+ interface StateConflict {
389
+ property: string;
390
+ clientValue: any;
391
+ serverValue: any;
392
+ timestamp: number;
393
+ resolved: boolean;
394
+ }
395
+ interface HybridState<T> {
396
+ data: T;
397
+ validation: StateValidation;
398
+ status: 'synced' | 'pending' | 'conflict';
399
+ }
400
+ declare class StateValidator {
401
+ static generateChecksum(state: any): string;
402
+ static createValidation(state: any, source?: 'client' | 'server' | 'mount'): StateValidation;
403
+ static detectConflicts<T>(clientState: T, serverState: T, excludeFields?: string[]): StateConflict[];
404
+ static mergeStates<T>(clientState: T, serverState: T, conflicts: StateConflict[], strategy?: 'client' | 'server' | 'smart'): T;
405
+ static validateState<T>(hybridState: HybridState<T>): boolean;
406
+ static updateValidation<T>(hybridState: HybridState<T>, source?: 'client' | 'server' | 'mount'): HybridState<T>;
407
+ }
408
+
409
+ /** Status listeners for the shared connection */
410
+ type ConnectionStatusCallback = (connected: boolean) => void;
411
+ interface UseLiveOptions {
412
+ /** WebSocket URL. Auto-detected from window.location if omitted. */
413
+ url?: string;
414
+ /** Room to join on mount */
415
+ room?: string;
416
+ /** User ID for component isolation */
417
+ userId?: string;
418
+ /** Auto-mount when connected. Default: true */
419
+ autoMount?: boolean;
420
+ /** Enable debug logging. Default: false */
421
+ debug?: boolean;
422
+ }
423
+ interface UseLiveHandle<TState extends Record<string, any> = Record<string, any>> {
424
+ /** Call a server action */
425
+ call: <R = any>(action: string, payload?: Record<string, any>) => Promise<R>;
426
+ /** Subscribe to state changes. Returns unsubscribe function. */
427
+ on: (callback: (state: TState, delta: Partial<TState> | null) => void) => () => void;
428
+ /** Subscribe to errors. Returns unsubscribe function. */
429
+ onError: (callback: (error: string) => void) => () => void;
430
+ /** Current state (read-only snapshot) */
431
+ readonly state: Readonly<TState>;
432
+ /** Whether the component is mounted on the server */
433
+ readonly mounted: boolean;
434
+ /** Server-assigned component ID */
435
+ readonly componentId: string | null;
436
+ /** Last error message */
437
+ readonly error: string | null;
438
+ /** Destroy the component and clean up */
439
+ destroy: () => void;
440
+ /** Access the underlying LiveComponentHandle */
441
+ readonly handle: LiveComponentHandle<TState>;
442
+ }
443
+ /**
444
+ * Create a live component with minimal boilerplate.
445
+ * Manages the WebSocket connection automatically (singleton).
446
+ *
447
+ * @example Browser IIFE
448
+ * ```html
449
+ * <script src="/live-client.js"></script>
450
+ * <script>
451
+ * const counter = FluxstackLive.useLive('Counter', { count: 0 })
452
+ * counter.on(state => {
453
+ * document.getElementById('count').textContent = state.count
454
+ * })
455
+ * document.querySelector('.inc').onclick = () => counter.call('increment')
456
+ * </script>
457
+ * ```
458
+ *
459
+ * @example ES modules
460
+ * ```ts
461
+ * import { useLive } from '@fluxstack/live-client'
462
+ * const counter = useLive('Counter', { count: 0 }, { url: 'ws://localhost:3000/api/live/ws' })
463
+ * counter.on(state => console.log(state.count))
464
+ * counter.call('increment')
465
+ * ```
466
+ */
467
+ declare function useLive<TState extends Record<string, any> = Record<string, any>>(componentName: string, initialState: TState, options?: UseLiveOptions): UseLiveHandle<TState>;
468
+ /**
469
+ * Subscribe to the shared connection status (connected/disconnected).
470
+ * Useful for showing a global status indicator.
471
+ *
472
+ * @example
473
+ * ```js
474
+ * FluxstackLive.onConnectionChange(connected => {
475
+ * statusEl.textContent = connected ? 'Connected' : 'Disconnected'
476
+ * })
477
+ * ```
478
+ */
479
+ declare function onConnectionChange(callback: ConnectionStatusCallback): () => void;
480
+ /**
481
+ * Get or create the shared connection instance.
482
+ * Useful when you need direct access to the connection.
483
+ */
484
+ declare function getConnection(url?: string): LiveConnection;
485
+
486
+ export { type AdaptiveChunkConfig, AdaptiveChunkSizer, type ChunkMetrics, type ChunkedUploadOptions, type ChunkedUploadState, ChunkedUploader, type EventHandler, type HybridState, type LiveAuthOptions, type LiveClientAuth, 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.d.ts CHANGED
@@ -25,12 +25,20 @@ interface LiveConnectionOptions {
25
25
  /** Enable debug logging. Default: false */
26
26
  debug?: boolean;
27
27
  }
28
+ /** Auth state exposed to the client */
29
+ interface LiveClientAuth {
30
+ authenticated: boolean;
31
+ /** Session data from the server. Shape defined by your LiveAuthProvider. */
32
+ session: Record<string, unknown> | null;
33
+ }
28
34
  interface LiveConnectionState {
29
35
  connected: boolean;
30
36
  connecting: boolean;
31
37
  error: string | null;
32
38
  connectionId: string | null;
33
39
  authenticated: boolean;
40
+ /** Auth context with session data */
41
+ auth: LiveClientAuth;
34
42
  }
35
43
  type StateChangeCallback$1 = (state: LiveConnectionState) => void;
36
44
  type ComponentCallback = (message: WebSocketResponse) => void;
@@ -475,4 +483,4 @@ declare function onConnectionChange(callback: ConnectionStatusCallback): () => v
475
483
  */
476
484
  declare function getConnection(url?: string): LiveConnection;
477
485
 
478
- 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 };
486
+ export { type AdaptiveChunkConfig, AdaptiveChunkSizer, type ChunkMetrics, type ChunkedUploadOptions, type ChunkedUploadState, ChunkedUploader, type EventHandler, type HybridState, type LiveAuthOptions, type LiveClientAuth, 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
@@ -15,7 +15,8 @@ var LiveConnection = class {
15
15
  connecting: false,
16
16
  error: null,
17
17
  connectionId: null,
18
- authenticated: false
18
+ authenticated: false,
19
+ auth: { authenticated: false, session: null }
19
20
  };
20
21
  constructor(options = {}) {
21
22
  this.options = {
@@ -48,21 +49,14 @@ var LiveConnection = class {
48
49
  }
49
50
  }
50
51
  getWebSocketUrl() {
51
- const auth = this.options.auth;
52
- let baseUrl;
53
52
  if (this.options.url) {
54
- baseUrl = this.options.url;
53
+ return this.options.url;
55
54
  } else if (typeof window === "undefined") {
56
- baseUrl = "ws://localhost:3000/api/live/ws";
55
+ return "ws://localhost:3000/api/live/ws";
57
56
  } else {
58
57
  const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
59
- baseUrl = `${protocol}//${window.location.host}/api/live/ws`;
58
+ return `${protocol}//${window.location.host}/api/live/ws`;
60
59
  }
61
- if (auth?.token) {
62
- const separator = baseUrl.includes("?") ? "&" : "?";
63
- return `${baseUrl}${separator}token=${encodeURIComponent(auth.token)}`;
64
- }
65
- return baseUrl;
66
60
  }
67
61
  log(message, data) {
68
62
  if (this.options.debug) {
@@ -119,7 +113,7 @@ var LiveConnection = class {
119
113
  };
120
114
  ws.onclose = (event) => {
121
115
  this.log("Disconnected", { code: event.code, reason: event.reason });
122
- this.setState({ connected: false, connecting: false, connectionId: null });
116
+ this.setState({ connected: false, connecting: false, connectionId: null, authenticated: false, auth: { authenticated: false, session: null } });
123
117
  this.stopHeartbeat();
124
118
  if (event.code === 4003) {
125
119
  this.setState({ error: "Connection rejected: origin not allowed" });
@@ -195,17 +189,29 @@ var LiveConnection = class {
195
189
  authenticated: response.authenticated || false
196
190
  });
197
191
  const auth = this.options.auth;
198
- if (auth && !auth.token && Object.keys(auth).some((k) => auth[k])) {
192
+ if (auth && Object.keys(auth).some((k) => auth[k])) {
199
193
  this.sendMessageAndWait({ type: "AUTH", payload: auth }).then((authResp) => {
200
- if (authResp.authenticated) {
201
- this.setState({ authenticated: true });
194
+ const payload = authResp.payload;
195
+ if (payload?.authenticated) {
196
+ this.setState({
197
+ authenticated: true,
198
+ auth: { authenticated: true, session: payload.session || null }
199
+ });
202
200
  }
203
201
  }).catch(() => {
204
202
  });
205
203
  }
206
204
  }
207
205
  if (response.type === "AUTH_RESPONSE") {
208
- this.setState({ authenticated: response.authenticated || false });
206
+ const payload = response.payload;
207
+ const authenticated = payload?.authenticated || false;
208
+ this.setState({
209
+ authenticated,
210
+ auth: {
211
+ authenticated,
212
+ session: authenticated ? payload?.session || null : null
213
+ }
214
+ });
209
215
  }
210
216
  if (response.requestId && this.pendingRequests.has(response.requestId)) {
211
217
  const request = this.pendingRequests.get(response.requestId);