@fluxstack/live-client 0.1.0 → 0.3.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.
@@ -31,8 +31,11 @@ var FluxstackLive = (() => {
31
31
  StateValidator: () => StateValidator,
32
32
  clearPersistedState: () => clearPersistedState,
33
33
  createBinaryChunkMessage: () => createBinaryChunkMessage,
34
+ getConnection: () => getConnection,
34
35
  getPersistedState: () => getPersistedState,
35
- persistState: () => persistState
36
+ onConnectionChange: () => onConnectionChange,
37
+ persistState: () => persistState,
38
+ useLive: () => useLive
36
39
  });
37
40
 
38
41
  // src/connection.ts
@@ -44,6 +47,8 @@ var FluxstackLive = (() => {
44
47
  __publicField(this, "reconnectTimeout", null);
45
48
  __publicField(this, "heartbeatInterval", null);
46
49
  __publicField(this, "componentCallbacks", /* @__PURE__ */ new Map());
50
+ __publicField(this, "binaryCallbacks", /* @__PURE__ */ new Map());
51
+ __publicField(this, "roomBinaryHandlers", /* @__PURE__ */ new Set());
47
52
  __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
48
53
  __publicField(this, "stateListeners", /* @__PURE__ */ new Set());
49
54
  __publicField(this, "_state", {
@@ -123,6 +128,7 @@ var FluxstackLive = (() => {
123
128
  this.log("Connecting...", { url });
124
129
  try {
125
130
  const ws = new WebSocket(url);
131
+ ws.binaryType = "arraybuffer";
126
132
  this.ws = ws;
127
133
  ws.onopen = () => {
128
134
  this.log("Connected");
@@ -131,19 +137,34 @@ var FluxstackLive = (() => {
131
137
  this.startHeartbeat();
132
138
  };
133
139
  ws.onmessage = (event) => {
140
+ if (event.data instanceof ArrayBuffer) {
141
+ this.handleBinaryMessage(new Uint8Array(event.data));
142
+ return;
143
+ }
134
144
  try {
135
- const response = JSON.parse(event.data);
136
- this.log("Received", { type: response.type, componentId: response.componentId });
137
- this.handleMessage(response);
145
+ const parsed = JSON.parse(event.data);
146
+ if (Array.isArray(parsed)) {
147
+ for (const msg of parsed) {
148
+ this.log("Received", { type: msg.type, componentId: msg.componentId });
149
+ this.handleMessage(msg);
150
+ }
151
+ } else {
152
+ this.log("Received", { type: parsed.type, componentId: parsed.componentId });
153
+ this.handleMessage(parsed);
154
+ }
138
155
  } catch {
139
156
  this.log("Failed to parse message");
140
157
  this.setState({ error: "Failed to parse message" });
141
158
  }
142
159
  };
143
- ws.onclose = () => {
144
- this.log("Disconnected");
160
+ ws.onclose = (event) => {
161
+ this.log("Disconnected", { code: event.code, reason: event.reason });
145
162
  this.setState({ connected: false, connecting: false, connectionId: null });
146
163
  this.stopHeartbeat();
164
+ if (event.code === 4003) {
165
+ this.setState({ error: "Connection rejected: origin not allowed" });
166
+ return;
167
+ }
147
168
  this.attemptReconnect();
148
169
  };
149
170
  ws.onerror = () => {
@@ -318,6 +339,37 @@ var FluxstackLive = (() => {
318
339
  }
319
340
  });
320
341
  }
342
+ /** Parse and route binary frames (state delta, room events, room state) */
343
+ handleBinaryMessage(buffer) {
344
+ if (buffer.length < 3) return;
345
+ const frameType = buffer[0];
346
+ if (frameType === 1) {
347
+ const idLen = buffer[1];
348
+ if (buffer.length < 2 + idLen) return;
349
+ const componentId = new TextDecoder().decode(buffer.subarray(2, 2 + idLen));
350
+ const payload = buffer.subarray(2 + idLen);
351
+ const callback = this.binaryCallbacks.get(componentId);
352
+ if (callback) callback(payload);
353
+ } else if (frameType === 2 || frameType === 3) {
354
+ for (const callback of this.roomBinaryHandlers) {
355
+ callback(buffer);
356
+ }
357
+ }
358
+ }
359
+ /** Register a handler for binary room frames (0x02 / 0x03). Returns unsubscribe. */
360
+ registerRoomBinaryHandler(callback) {
361
+ this.roomBinaryHandlers.add(callback);
362
+ return () => {
363
+ this.roomBinaryHandlers.delete(callback);
364
+ };
365
+ }
366
+ /** Register a binary message handler for a component */
367
+ registerBinaryHandler(componentId, callback) {
368
+ this.binaryCallbacks.set(componentId, callback);
369
+ return () => {
370
+ this.binaryCallbacks.delete(componentId);
371
+ };
372
+ }
321
373
  /** Register a component message callback */
322
374
  registerComponent(componentId, callback) {
323
375
  this.log("Registering component", componentId);
@@ -353,6 +405,8 @@ var FluxstackLive = (() => {
353
405
  destroy() {
354
406
  this.disconnect();
355
407
  this.componentCallbacks.clear();
408
+ this.binaryCallbacks.clear();
409
+ this.roomBinaryHandlers.clear();
356
410
  for (const [, req] of this.pendingRequests) {
357
411
  clearTimeout(req.timeout);
358
412
  req.reject(new Error("Connection destroyed"));
@@ -363,6 +417,25 @@ var FluxstackLive = (() => {
363
417
  };
364
418
 
365
419
  // src/component.ts
420
+ function isPlainObject(v) {
421
+ return v !== null && typeof v === "object" && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
422
+ }
423
+ function deepMerge(target, source, seen) {
424
+ if (!seen) seen = /* @__PURE__ */ new Set();
425
+ if (seen.has(source)) return target;
426
+ seen.add(source);
427
+ const result = { ...target };
428
+ for (const key of Object.keys(source)) {
429
+ const newVal = source[key];
430
+ const oldVal = result[key];
431
+ if (isPlainObject(oldVal) && isPlainObject(newVal)) {
432
+ result[key] = deepMerge(oldVal, newVal, seen);
433
+ } else {
434
+ result[key] = newVal;
435
+ }
436
+ }
437
+ return result;
438
+ }
366
439
  var LiveComponentHandle = class {
367
440
  constructor(connection, componentName, options = {}) {
368
441
  __publicField(this, "connection");
@@ -511,6 +584,21 @@ var FluxstackLive = (() => {
511
584
  }
512
585
  return response.result;
513
586
  }
587
+ /**
588
+ * Fire an action without waiting for a response (fire-and-forget).
589
+ * Useful for high-frequency operations like game input where the
590
+ * server doesn't need to send back a result.
591
+ */
592
+ fire(action, payload = {}) {
593
+ if (!this._mounted || !this._componentId) return;
594
+ this.connection.sendMessage({
595
+ type: "CALL_ACTION",
596
+ componentId: this._componentId,
597
+ action,
598
+ payload,
599
+ expectResponse: false
600
+ });
601
+ }
514
602
  // ── State ──
515
603
  /**
516
604
  * Subscribe to state changes.
@@ -523,6 +611,26 @@ var FluxstackLive = (() => {
523
611
  this.stateListeners.delete(callback);
524
612
  };
525
613
  }
614
+ /**
615
+ * Register a binary decoder for this component.
616
+ * When the server sends a BINARY_STATE_DELTA frame targeting this component,
617
+ * the decoder converts the raw payload into a delta object which is merged into state.
618
+ * Returns an unsubscribe function.
619
+ */
620
+ setBinaryDecoder(decoder) {
621
+ if (!this._componentId) {
622
+ throw new Error("Component must be mounted before setting binary decoder");
623
+ }
624
+ return this.connection.registerBinaryHandler(this._componentId, (payload) => {
625
+ try {
626
+ const delta = decoder(payload);
627
+ this._state = deepMerge(this._state, delta);
628
+ this.notifyStateChange(this._state, delta);
629
+ } catch (e) {
630
+ console.error("Binary decode error:", e);
631
+ }
632
+ });
633
+ }
526
634
  /**
527
635
  * Subscribe to errors.
528
636
  * Returns an unsubscribe function.
@@ -539,7 +647,7 @@ var FluxstackLive = (() => {
539
647
  case "STATE_UPDATE": {
540
648
  const newState = msg.payload?.state;
541
649
  if (newState) {
542
- this._state = { ...this._state, ...newState };
650
+ this._state = deepMerge(this._state, newState);
543
651
  this.notifyStateChange(this._state, null);
544
652
  }
545
653
  break;
@@ -547,7 +655,7 @@ var FluxstackLive = (() => {
547
655
  case "STATE_DELTA": {
548
656
  const delta = msg.payload?.delta;
549
657
  if (delta) {
550
- this._state = { ...this._state, ...delta };
658
+ this._state = deepMerge(this._state, delta);
551
659
  this.notifyStateChange(this._state, delta);
552
660
  }
553
661
  break;
@@ -589,6 +697,180 @@ var FluxstackLive = (() => {
589
697
  };
590
698
 
591
699
  // src/rooms.ts
700
+ function isPlainObject2(v) {
701
+ return v !== null && typeof v === "object" && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
702
+ }
703
+ function deepMerge2(target, source, seen) {
704
+ if (!seen) seen = /* @__PURE__ */ new Set();
705
+ if (seen.has(source)) return target;
706
+ seen.add(source);
707
+ const result = { ...target };
708
+ for (const key of Object.keys(source)) {
709
+ const newVal = source[key];
710
+ const oldVal = result[key];
711
+ if (isPlainObject2(oldVal) && isPlainObject2(newVal)) {
712
+ result[key] = deepMerge2(oldVal, newVal, seen);
713
+ } else {
714
+ result[key] = newVal;
715
+ }
716
+ }
717
+ return result;
718
+ }
719
+ var BINARY_ROOM_EVENT = 2;
720
+ var BINARY_ROOM_STATE = 3;
721
+ var _decoder = new TextDecoder();
722
+ function msgpackDecode(buf) {
723
+ return _decodeAt(buf, 0).value;
724
+ }
725
+ function _decodeAt(buf, offset) {
726
+ if (offset >= buf.length) return { value: null, offset };
727
+ const byte = buf[offset];
728
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
729
+ if (byte < 128) return { value: byte, offset: offset + 1 };
730
+ if (byte >= 128 && byte <= 143) return _decodeMap(buf, offset + 1, byte & 15);
731
+ if (byte >= 144 && byte <= 159) return _decodeArr(buf, offset + 1, byte & 15);
732
+ if (byte >= 160 && byte <= 191) {
733
+ const len = byte & 31;
734
+ return { value: _decoder.decode(buf.subarray(offset + 1, offset + 1 + len)), offset: offset + 1 + len };
735
+ }
736
+ if (byte >= 224) return { value: byte - 256, offset: offset + 1 };
737
+ switch (byte) {
738
+ case 192:
739
+ return { value: null, offset: offset + 1 };
740
+ case 194:
741
+ return { value: false, offset: offset + 1 };
742
+ case 195:
743
+ return { value: true, offset: offset + 1 };
744
+ case 196: {
745
+ const l = buf[offset + 1];
746
+ return { value: buf.slice(offset + 2, offset + 2 + l), offset: offset + 2 + l };
747
+ }
748
+ case 197: {
749
+ const l = view.getUint16(offset + 1, false);
750
+ return { value: buf.slice(offset + 3, offset + 3 + l), offset: offset + 3 + l };
751
+ }
752
+ case 198: {
753
+ const l = view.getUint32(offset + 1, false);
754
+ return { value: buf.slice(offset + 5, offset + 5 + l), offset: offset + 5 + l };
755
+ }
756
+ case 203:
757
+ return { value: view.getFloat64(offset + 1, false), offset: offset + 9 };
758
+ case 204:
759
+ return { value: buf[offset + 1], offset: offset + 2 };
760
+ case 205:
761
+ return { value: view.getUint16(offset + 1, false), offset: offset + 3 };
762
+ case 206:
763
+ return { value: view.getUint32(offset + 1, false), offset: offset + 5 };
764
+ case 208:
765
+ return { value: view.getInt8(offset + 1), offset: offset + 2 };
766
+ case 209:
767
+ return { value: view.getInt16(offset + 1, false), offset: offset + 3 };
768
+ case 210:
769
+ return { value: view.getInt32(offset + 1, false), offset: offset + 5 };
770
+ case 217: {
771
+ const l = buf[offset + 1];
772
+ return { value: _decoder.decode(buf.subarray(offset + 2, offset + 2 + l)), offset: offset + 2 + l };
773
+ }
774
+ case 218: {
775
+ const l = view.getUint16(offset + 1, false);
776
+ return { value: _decoder.decode(buf.subarray(offset + 3, offset + 3 + l)), offset: offset + 3 + l };
777
+ }
778
+ case 219: {
779
+ const l = view.getUint32(offset + 1, false);
780
+ return { value: _decoder.decode(buf.subarray(offset + 5, offset + 5 + l)), offset: offset + 5 + l };
781
+ }
782
+ case 220:
783
+ return _decodeArr(buf, offset + 3, view.getUint16(offset + 1, false));
784
+ case 221:
785
+ return _decodeArr(buf, offset + 5, view.getUint32(offset + 1, false));
786
+ case 222:
787
+ return _decodeMap(buf, offset + 3, view.getUint16(offset + 1, false));
788
+ case 223:
789
+ return _decodeMap(buf, offset + 5, view.getUint32(offset + 1, false));
790
+ }
791
+ return { value: null, offset: offset + 1 };
792
+ }
793
+ function _decodeArr(buf, offset, count) {
794
+ const arr = new Array(count);
795
+ for (let i = 0; i < count; i++) {
796
+ const r = _decodeAt(buf, offset);
797
+ arr[i] = r.value;
798
+ offset = r.offset;
799
+ }
800
+ return { value: arr, offset };
801
+ }
802
+ function _decodeMap(buf, offset, count) {
803
+ const obj = {};
804
+ for (let i = 0; i < count; i++) {
805
+ const k = _decodeAt(buf, offset);
806
+ offset = k.offset;
807
+ const v = _decodeAt(buf, offset);
808
+ offset = v.offset;
809
+ obj[String(k.value)] = v.value;
810
+ }
811
+ return { value: obj, offset };
812
+ }
813
+ function parseRoomFrame(buf) {
814
+ if (buf.length < 6) return null;
815
+ let offset = 0;
816
+ const frameType = buf[offset++];
817
+ const compIdLen = buf[offset++];
818
+ if (offset + compIdLen > buf.length) return null;
819
+ const componentId = _decoder.decode(buf.subarray(offset, offset + compIdLen));
820
+ offset += compIdLen;
821
+ const roomIdLen = buf[offset++];
822
+ if (offset + roomIdLen > buf.length) return null;
823
+ const roomId = _decoder.decode(buf.subarray(offset, offset + roomIdLen));
824
+ offset += roomIdLen;
825
+ if (offset + 2 > buf.length) return null;
826
+ const eventLen = buf[offset] << 8 | buf[offset + 1];
827
+ offset += 2;
828
+ if (offset + eventLen > buf.length) return null;
829
+ const event = _decoder.decode(buf.subarray(offset, offset + eventLen));
830
+ offset += eventLen;
831
+ return { frameType, componentId, roomId, event, payload: buf.subarray(offset) };
832
+ }
833
+ var ROOM_RESERVED_KEYS = /* @__PURE__ */ new Set([
834
+ "id",
835
+ "joined",
836
+ "state",
837
+ "join",
838
+ "leave",
839
+ "emit",
840
+ "on",
841
+ "onSystem",
842
+ "setState",
843
+ "call",
844
+ "apply",
845
+ "bind",
846
+ "prototype",
847
+ "length",
848
+ "name",
849
+ "arguments",
850
+ "caller",
851
+ Symbol.toPrimitive,
852
+ Symbol.toStringTag,
853
+ Symbol.hasInstance
854
+ ]);
855
+ function wrapWithStateProxy(target, getState, setStateFn) {
856
+ return new Proxy(target, {
857
+ get(obj, prop, receiver) {
858
+ if (ROOM_RESERVED_KEYS.has(prop) || typeof prop === "symbol") {
859
+ return Reflect.get(obj, prop, receiver);
860
+ }
861
+ const desc = Object.getOwnPropertyDescriptor(obj, prop);
862
+ if (desc) return Reflect.get(obj, prop, receiver);
863
+ if (prop in obj) return Reflect.get(obj, prop, receiver);
864
+ const st = getState();
865
+ return st?.[prop];
866
+ },
867
+ set(_obj, prop, value) {
868
+ if (typeof prop === "symbol") return false;
869
+ setStateFn({ [prop]: value });
870
+ return true;
871
+ }
872
+ });
873
+ }
592
874
  var RoomManager = class {
593
875
  constructor(options) {
594
876
  __publicField(this, "componentId");
@@ -598,11 +880,28 @@ var FluxstackLive = (() => {
598
880
  __publicField(this, "sendMessage");
599
881
  __publicField(this, "sendMessageAndWait");
600
882
  __publicField(this, "globalUnsubscribe", null);
883
+ __publicField(this, "binaryUnsubscribe", null);
884
+ __publicField(this, "onBinaryMessage", null);
885
+ __publicField(this, "onMessageFactory", null);
601
886
  this.componentId = options.componentId;
602
887
  this.defaultRoom = options.defaultRoom || null;
603
888
  this.sendMessage = options.sendMessage;
604
889
  this.sendMessageAndWait = options.sendMessageAndWait;
890
+ this.onBinaryMessage = options.onBinaryMessage ?? null;
891
+ this.onMessageFactory = options.onMessage;
605
892
  this.globalUnsubscribe = options.onMessage((msg) => this.handleServerMessage(msg));
893
+ if (options.onBinaryMessage) {
894
+ this.binaryUnsubscribe = options.onBinaryMessage((frame) => this.handleBinaryFrame(frame));
895
+ }
896
+ }
897
+ /** Re-subscribe message and binary handlers (needed after destroy/remount in React Strict Mode) */
898
+ resubscribe() {
899
+ if (!this.globalUnsubscribe && this.onMessageFactory) {
900
+ this.globalUnsubscribe = this.onMessageFactory((msg) => this.handleServerMessage(msg));
901
+ }
902
+ if (!this.binaryUnsubscribe && this.onBinaryMessage) {
903
+ this.binaryUnsubscribe = this.onBinaryMessage((frame) => this.handleBinaryFrame(frame));
904
+ }
606
905
  }
607
906
  handleServerMessage(msg) {
608
907
  if (msg.componentId !== this.componentId) return;
@@ -624,10 +923,11 @@ var FluxstackLive = (() => {
624
923
  break;
625
924
  }
626
925
  case "ROOM_STATE": {
627
- room.state = { ...room.state, ...msg.data };
926
+ const stateChanges = msg.data?.state ?? msg.data;
927
+ room.state = deepMerge2(room.state, stateChanges);
628
928
  const stateHandlers = room.handlers.get("$state:change");
629
929
  if (stateHandlers) {
630
- for (const handler of stateHandlers) handler(msg.data);
930
+ for (const handler of stateHandlers) handler(stateChanges);
631
931
  }
632
932
  break;
633
933
  }
@@ -640,6 +940,34 @@ var FluxstackLive = (() => {
640
940
  break;
641
941
  }
642
942
  }
943
+ /** Handle binary room frames (0x02 ROOM_EVENT, 0x03 ROOM_STATE) */
944
+ handleBinaryFrame(frame) {
945
+ const parsed = parseRoomFrame(frame);
946
+ if (!parsed) return;
947
+ if (parsed.componentId !== this.componentId) return;
948
+ const room = this.rooms.get(parsed.roomId);
949
+ if (!room) return;
950
+ const data = msgpackDecode(parsed.payload);
951
+ if (parsed.frameType === BINARY_ROOM_EVENT) {
952
+ const handlers = room.handlers.get(parsed.event);
953
+ if (handlers) {
954
+ for (const handler of handlers) {
955
+ try {
956
+ handler(data);
957
+ } catch (error) {
958
+ console.error(`[Room:${parsed.roomId}] Handler error for '${parsed.event}':`, error);
959
+ }
960
+ }
961
+ }
962
+ } else if (parsed.frameType === BINARY_ROOM_STATE) {
963
+ const stateChanges = data?.state ?? data;
964
+ room.state = deepMerge2(room.state, stateChanges);
965
+ const stateHandlers = room.handlers.get("$state:change");
966
+ if (stateHandlers) {
967
+ for (const handler of stateHandlers) handler(stateChanges);
968
+ }
969
+ }
970
+ }
643
971
  getOrCreateRoom(roomId) {
644
972
  if (!this.rooms.has(roomId)) {
645
973
  this.rooms.set(roomId, {
@@ -720,7 +1048,7 @@ var FluxstackLive = (() => {
720
1048
  },
721
1049
  setState: (updates) => {
722
1050
  if (!this.componentId) return;
723
- room.state = { ...room.state, ...updates };
1051
+ room.state = deepMerge2(room.state, updates);
724
1052
  this.sendMessage({
725
1053
  type: "ROOM_STATE_SET",
726
1054
  componentId: this.componentId,
@@ -730,8 +1058,13 @@ var FluxstackLive = (() => {
730
1058
  });
731
1059
  }
732
1060
  };
733
- this.handles.set(roomId, handle);
734
- return handle;
1061
+ const proxied = wrapWithStateProxy(
1062
+ handle,
1063
+ () => room.state,
1064
+ (updates) => handle.setState(updates)
1065
+ );
1066
+ this.handles.set(roomId, proxied);
1067
+ return proxied;
735
1068
  }
736
1069
  /** Create the $room proxy */
737
1070
  createProxy() {
@@ -781,6 +1114,14 @@ var FluxstackLive = (() => {
781
1114
  }
782
1115
  }
783
1116
  });
1117
+ if (this.defaultRoom && defaultHandle) {
1118
+ const room = this.getOrCreateRoom(this.defaultRoom);
1119
+ return wrapWithStateProxy(
1120
+ proxyFn,
1121
+ () => room.state,
1122
+ (updates) => defaultHandle.setState(updates)
1123
+ );
1124
+ }
784
1125
  return proxyFn;
785
1126
  }
786
1127
  /** List of rooms currently joined */
@@ -795,9 +1136,12 @@ var FluxstackLive = (() => {
795
1136
  setComponentId(id) {
796
1137
  this.componentId = id;
797
1138
  }
798
- /** Cleanup */
1139
+ /** Cleanup — unsubscribes handlers but keeps factory refs for resubscribe() */
799
1140
  destroy() {
800
1141
  this.globalUnsubscribe?.();
1142
+ this.globalUnsubscribe = null;
1143
+ this.binaryUnsubscribe?.();
1144
+ this.binaryUnsubscribe = null;
801
1145
  for (const [, room] of this.rooms) {
802
1146
  room.handlers.clear();
803
1147
  }
@@ -1170,6 +1514,70 @@ var FluxstackLive = (() => {
1170
1514
  };
1171
1515
  }
1172
1516
  };
1517
+
1518
+ // src/index.ts
1519
+ var _sharedConnection = null;
1520
+ var _sharedConnectionUrl = null;
1521
+ var _statusListeners = /* @__PURE__ */ new Set();
1522
+ function getOrCreateConnection(url) {
1523
+ const resolvedUrl = url ?? `ws://${typeof location !== "undefined" ? location.host : "localhost:3000"}/api/live/ws`;
1524
+ if (_sharedConnection && _sharedConnectionUrl === resolvedUrl) {
1525
+ return _sharedConnection;
1526
+ }
1527
+ if (_sharedConnection) {
1528
+ _sharedConnection.destroy();
1529
+ }
1530
+ _sharedConnection = new LiveConnection({ url: resolvedUrl });
1531
+ _sharedConnectionUrl = resolvedUrl;
1532
+ _sharedConnection.onStateChange((state) => {
1533
+ for (const cb of _statusListeners) {
1534
+ cb(state.connected);
1535
+ }
1536
+ });
1537
+ return _sharedConnection;
1538
+ }
1539
+ function useLive(componentName, initialState, options = {}) {
1540
+ const { url, room, userId, autoMount = true, debug = false } = options;
1541
+ const connection = getOrCreateConnection(url);
1542
+ const handle = new LiveComponentHandle(connection, componentName, {
1543
+ initialState,
1544
+ room,
1545
+ userId,
1546
+ autoMount,
1547
+ debug
1548
+ });
1549
+ return {
1550
+ call: (action, payload) => handle.call(action, payload ?? {}),
1551
+ on: (callback) => handle.onStateChange(callback),
1552
+ onError: (callback) => handle.onError(callback),
1553
+ get state() {
1554
+ return handle.state;
1555
+ },
1556
+ get mounted() {
1557
+ return handle.mounted;
1558
+ },
1559
+ get componentId() {
1560
+ return handle.componentId;
1561
+ },
1562
+ get error() {
1563
+ return handle.error;
1564
+ },
1565
+ destroy: () => handle.destroy(),
1566
+ handle
1567
+ };
1568
+ }
1569
+ function onConnectionChange(callback) {
1570
+ _statusListeners.add(callback);
1571
+ if (_sharedConnection) {
1572
+ callback(_sharedConnection.state.connected);
1573
+ }
1574
+ return () => {
1575
+ _statusListeners.delete(callback);
1576
+ };
1577
+ }
1578
+ function getConnection(url) {
1579
+ return getOrCreateConnection(url);
1580
+ }
1173
1581
  return __toCommonJS(src_exports);
1174
1582
  })();
1175
1583
  //# sourceMappingURL=live-client.browser.global.js.map