@fluxstack/live-client 0.2.0 → 0.3.1
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/dist/index.d.ts +31 -7
- package/dist/index.js +287 -18
- package/dist/index.js.map +1 -1
- package/dist/live-client.browser.global.js +287 -18
- package/dist/live-client.browser.global.js.map +1 -1
- package/package.json +41 -39
|
@@ -48,6 +48,7 @@ var FluxstackLive = (() => {
|
|
|
48
48
|
__publicField(this, "heartbeatInterval", null);
|
|
49
49
|
__publicField(this, "componentCallbacks", /* @__PURE__ */ new Map());
|
|
50
50
|
__publicField(this, "binaryCallbacks", /* @__PURE__ */ new Map());
|
|
51
|
+
__publicField(this, "roomBinaryHandlers", /* @__PURE__ */ new Set());
|
|
51
52
|
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
52
53
|
__publicField(this, "stateListeners", /* @__PURE__ */ new Set());
|
|
53
54
|
__publicField(this, "_state", {
|
|
@@ -338,18 +339,30 @@ var FluxstackLive = (() => {
|
|
|
338
339
|
}
|
|
339
340
|
});
|
|
340
341
|
}
|
|
341
|
-
/** Parse and route
|
|
342
|
+
/** Parse and route binary frames (state delta, room events, room state) */
|
|
342
343
|
handleBinaryMessage(buffer) {
|
|
343
|
-
if (buffer.length < 3
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
callback(
|
|
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
|
+
}
|
|
351
357
|
}
|
|
352
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
|
+
}
|
|
353
366
|
/** Register a binary message handler for a component */
|
|
354
367
|
registerBinaryHandler(componentId, callback) {
|
|
355
368
|
this.binaryCallbacks.set(componentId, callback);
|
|
@@ -393,6 +406,7 @@ var FluxstackLive = (() => {
|
|
|
393
406
|
this.disconnect();
|
|
394
407
|
this.componentCallbacks.clear();
|
|
395
408
|
this.binaryCallbacks.clear();
|
|
409
|
+
this.roomBinaryHandlers.clear();
|
|
396
410
|
for (const [, req] of this.pendingRequests) {
|
|
397
411
|
clearTimeout(req.timeout);
|
|
398
412
|
req.reject(new Error("Connection destroyed"));
|
|
@@ -403,6 +417,25 @@ var FluxstackLive = (() => {
|
|
|
403
417
|
};
|
|
404
418
|
|
|
405
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
|
+
}
|
|
406
439
|
var LiveComponentHandle = class {
|
|
407
440
|
constructor(connection, componentName, options = {}) {
|
|
408
441
|
__publicField(this, "connection");
|
|
@@ -591,7 +624,7 @@ var FluxstackLive = (() => {
|
|
|
591
624
|
return this.connection.registerBinaryHandler(this._componentId, (payload) => {
|
|
592
625
|
try {
|
|
593
626
|
const delta = decoder(payload);
|
|
594
|
-
this._state =
|
|
627
|
+
this._state = deepMerge(this._state, delta);
|
|
595
628
|
this.notifyStateChange(this._state, delta);
|
|
596
629
|
} catch (e) {
|
|
597
630
|
console.error("Binary decode error:", e);
|
|
@@ -614,7 +647,7 @@ var FluxstackLive = (() => {
|
|
|
614
647
|
case "STATE_UPDATE": {
|
|
615
648
|
const newState = msg.payload?.state;
|
|
616
649
|
if (newState) {
|
|
617
|
-
this._state =
|
|
650
|
+
this._state = deepMerge(this._state, newState);
|
|
618
651
|
this.notifyStateChange(this._state, null);
|
|
619
652
|
}
|
|
620
653
|
break;
|
|
@@ -622,7 +655,7 @@ var FluxstackLive = (() => {
|
|
|
622
655
|
case "STATE_DELTA": {
|
|
623
656
|
const delta = msg.payload?.delta;
|
|
624
657
|
if (delta) {
|
|
625
|
-
this._state =
|
|
658
|
+
this._state = deepMerge(this._state, delta);
|
|
626
659
|
this.notifyStateChange(this._state, delta);
|
|
627
660
|
}
|
|
628
661
|
break;
|
|
@@ -664,6 +697,180 @@ var FluxstackLive = (() => {
|
|
|
664
697
|
};
|
|
665
698
|
|
|
666
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
|
+
}
|
|
667
874
|
var RoomManager = class {
|
|
668
875
|
constructor(options) {
|
|
669
876
|
__publicField(this, "componentId");
|
|
@@ -673,11 +880,28 @@ var FluxstackLive = (() => {
|
|
|
673
880
|
__publicField(this, "sendMessage");
|
|
674
881
|
__publicField(this, "sendMessageAndWait");
|
|
675
882
|
__publicField(this, "globalUnsubscribe", null);
|
|
883
|
+
__publicField(this, "binaryUnsubscribe", null);
|
|
884
|
+
__publicField(this, "onBinaryMessage", null);
|
|
885
|
+
__publicField(this, "onMessageFactory", null);
|
|
676
886
|
this.componentId = options.componentId;
|
|
677
887
|
this.defaultRoom = options.defaultRoom || null;
|
|
678
888
|
this.sendMessage = options.sendMessage;
|
|
679
889
|
this.sendMessageAndWait = options.sendMessageAndWait;
|
|
890
|
+
this.onBinaryMessage = options.onBinaryMessage ?? null;
|
|
891
|
+
this.onMessageFactory = options.onMessage;
|
|
680
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
|
+
}
|
|
681
905
|
}
|
|
682
906
|
handleServerMessage(msg) {
|
|
683
907
|
if (msg.componentId !== this.componentId) return;
|
|
@@ -699,10 +923,11 @@ var FluxstackLive = (() => {
|
|
|
699
923
|
break;
|
|
700
924
|
}
|
|
701
925
|
case "ROOM_STATE": {
|
|
702
|
-
|
|
926
|
+
const stateChanges = msg.data?.state ?? msg.data;
|
|
927
|
+
room.state = deepMerge2(room.state, stateChanges);
|
|
703
928
|
const stateHandlers = room.handlers.get("$state:change");
|
|
704
929
|
if (stateHandlers) {
|
|
705
|
-
for (const handler of stateHandlers) handler(
|
|
930
|
+
for (const handler of stateHandlers) handler(stateChanges);
|
|
706
931
|
}
|
|
707
932
|
break;
|
|
708
933
|
}
|
|
@@ -715,6 +940,34 @@ var FluxstackLive = (() => {
|
|
|
715
940
|
break;
|
|
716
941
|
}
|
|
717
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
|
+
}
|
|
718
971
|
getOrCreateRoom(roomId) {
|
|
719
972
|
if (!this.rooms.has(roomId)) {
|
|
720
973
|
this.rooms.set(roomId, {
|
|
@@ -795,7 +1048,7 @@ var FluxstackLive = (() => {
|
|
|
795
1048
|
},
|
|
796
1049
|
setState: (updates) => {
|
|
797
1050
|
if (!this.componentId) return;
|
|
798
|
-
room.state =
|
|
1051
|
+
room.state = deepMerge2(room.state, updates);
|
|
799
1052
|
this.sendMessage({
|
|
800
1053
|
type: "ROOM_STATE_SET",
|
|
801
1054
|
componentId: this.componentId,
|
|
@@ -805,8 +1058,13 @@ var FluxstackLive = (() => {
|
|
|
805
1058
|
});
|
|
806
1059
|
}
|
|
807
1060
|
};
|
|
808
|
-
|
|
809
|
-
|
|
1061
|
+
const proxied = wrapWithStateProxy(
|
|
1062
|
+
handle,
|
|
1063
|
+
() => room.state,
|
|
1064
|
+
(updates) => handle.setState(updates)
|
|
1065
|
+
);
|
|
1066
|
+
this.handles.set(roomId, proxied);
|
|
1067
|
+
return proxied;
|
|
810
1068
|
}
|
|
811
1069
|
/** Create the $room proxy */
|
|
812
1070
|
createProxy() {
|
|
@@ -856,6 +1114,14 @@ var FluxstackLive = (() => {
|
|
|
856
1114
|
}
|
|
857
1115
|
}
|
|
858
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
|
+
}
|
|
859
1125
|
return proxyFn;
|
|
860
1126
|
}
|
|
861
1127
|
/** List of rooms currently joined */
|
|
@@ -870,9 +1136,12 @@ var FluxstackLive = (() => {
|
|
|
870
1136
|
setComponentId(id) {
|
|
871
1137
|
this.componentId = id;
|
|
872
1138
|
}
|
|
873
|
-
/** Cleanup */
|
|
1139
|
+
/** Cleanup — unsubscribes handlers but keeps factory refs for resubscribe() */
|
|
874
1140
|
destroy() {
|
|
875
1141
|
this.globalUnsubscribe?.();
|
|
1142
|
+
this.globalUnsubscribe = null;
|
|
1143
|
+
this.binaryUnsubscribe?.();
|
|
1144
|
+
this.binaryUnsubscribe = null;
|
|
876
1145
|
for (const [, room] of this.rooms) {
|
|
877
1146
|
room.handlers.clear();
|
|
878
1147
|
}
|