onelobby 0.4.1 → 0.6.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.
Files changed (99) hide show
  1. package/README.md +2 -2
  2. package/blueprint.schema.json +244 -0
  3. package/dist/blueprint.d.ts +170 -0
  4. package/dist/blueprint.js +286 -0
  5. package/dist/blueprint.js.map +1 -0
  6. package/dist/codec.d.ts +23 -0
  7. package/dist/codec.js +28 -0
  8. package/dist/codec.js.map +1 -1
  9. package/dist/direct/browser-host.d.ts +46 -0
  10. package/dist/direct/browser-host.js +93 -0
  11. package/dist/direct/browser-host.js.map +1 -0
  12. package/dist/direct/hub-host.d.ts +34 -23
  13. package/dist/direct/hub-host.js +75 -59
  14. package/dist/direct/hub-host.js.map +1 -1
  15. package/dist/direct/hub-local.d.ts +22 -0
  16. package/dist/direct/hub-local.js +46 -0
  17. package/dist/direct/hub-local.js.map +1 -0
  18. package/dist/direct/hub.d.ts +37 -10
  19. package/dist/direct/hub.js +114 -23
  20. package/dist/direct/hub.js.map +1 -1
  21. package/dist/direct/index.d.ts +37 -4
  22. package/dist/direct/index.js +162 -21
  23. package/dist/direct/index.js.map +1 -1
  24. package/dist/direct/metrics.d.ts +12 -0
  25. package/dist/direct/metrics.js +45 -0
  26. package/dist/direct/metrics.js.map +1 -0
  27. package/dist/direct/portmap.d.ts +9 -2
  28. package/dist/direct/portmap.js +7 -2
  29. package/dist/direct/portmap.js.map +1 -1
  30. package/dist/direct/sha1.d.ts +16 -0
  31. package/dist/direct/sha1.js +111 -0
  32. package/dist/direct/sha1.js.map +1 -0
  33. package/dist/direct-address.d.ts +2 -2
  34. package/dist/direct-address.js.map +1 -1
  35. package/dist/friendly.d.ts +28 -0
  36. package/dist/friendly.js +40 -0
  37. package/dist/friendly.js.map +1 -1
  38. package/dist/index.d.ts +11 -3
  39. package/dist/index.js +8 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/internals.d.ts +9 -0
  42. package/dist/internals.js +9 -0
  43. package/dist/internals.js.map +1 -1
  44. package/dist/join-code-kind.d.ts +10 -0
  45. package/dist/join-code-kind.js +10 -0
  46. package/dist/join-code-kind.js.map +1 -0
  47. package/dist/joincode.d.ts +10 -1
  48. package/dist/joincode.js +23 -3
  49. package/dist/joincode.js.map +1 -1
  50. package/dist/lobby.d.ts +65 -4
  51. package/dist/lobby.js +151 -12
  52. package/dist/lobby.js.map +1 -1
  53. package/dist/lockstep.d.ts +8 -0
  54. package/dist/lockstep.js +23 -2
  55. package/dist/lockstep.js.map +1 -1
  56. package/dist/nostr/crypto.d.ts +20 -0
  57. package/dist/nostr/crypto.js +78 -0
  58. package/dist/nostr/crypto.js.map +1 -0
  59. package/dist/nostr/host.d.ts +66 -0
  60. package/dist/nostr/host.js +196 -0
  61. package/dist/nostr/host.js.map +1 -0
  62. package/dist/nostr/limits.d.ts +31 -0
  63. package/dist/nostr/limits.js +36 -0
  64. package/dist/nostr/limits.js.map +1 -0
  65. package/dist/nostr/link.d.ts +70 -0
  66. package/dist/nostr/link.js +180 -0
  67. package/dist/nostr/link.js.map +1 -0
  68. package/dist/nostr/relays.d.ts +79 -0
  69. package/dist/nostr/relays.js +315 -0
  70. package/dist/nostr/relays.js.map +1 -0
  71. package/dist/nostr/room.d.ts +43 -0
  72. package/dist/nostr/room.js +104 -0
  73. package/dist/nostr/room.js.map +1 -0
  74. package/dist/nostr/secp256k1.d.ts +10 -0
  75. package/dist/nostr/secp256k1.js +171 -0
  76. package/dist/nostr/secp256k1.js.map +1 -0
  77. package/dist/nostr/socket.d.ts +48 -0
  78. package/dist/nostr/socket.js +141 -0
  79. package/dist/nostr/socket.js.map +1 -0
  80. package/dist/onelobby.bundle.js +146 -0
  81. package/dist/onelobby.bundle.js.map +7 -0
  82. package/dist/onelobby.bundle.mjs +11 -5
  83. package/dist/onelobby.bundle.mjs.map +4 -4
  84. package/dist/onelobby.d.ts +54 -4
  85. package/dist/onelobby.global.js +2932 -49
  86. package/dist/onelobby.global.js.map +4 -4
  87. package/dist/onelobby.global.min.js +11 -5
  88. package/dist/onelobby.global.min.js.map +4 -4
  89. package/dist/onelobby.js +158 -15
  90. package/dist/onelobby.js.map +1 -1
  91. package/dist/pause.d.ts +85 -0
  92. package/dist/pause.js +160 -0
  93. package/dist/pause.js.map +1 -0
  94. package/dist/transport.d.ts +3 -1
  95. package/dist/transport.js.map +1 -1
  96. package/dist/webrtc.d.ts +1 -0
  97. package/dist/webrtc.js +6 -1
  98. package/dist/webrtc.js.map +1 -1
  99. package/package.json +4 -2
package/dist/lobby.js CHANGED
@@ -4,6 +4,7 @@ import { Reassembler, splitChunks } from "./chunking.js";
4
4
  import { Emitter } from "./emitter.js";
5
5
  import { Lockstep } from "./lockstep.js";
6
6
  import { claimWinsWrapped, pickNewHost } from "./migration.js";
7
+ import { Pause } from "./pause.js";
7
8
  import { OneLobbyError } from "./signaling.js";
8
9
  import { Voice } from "./voice.js";
9
10
  import { hostConnections, joinerSocket } from "./socket.js";
@@ -11,6 +12,8 @@ import { hostConnections, joinerSocket } from "./socket.js";
11
12
  export const MIN_PEER_TIMEOUT_MS = 6000;
12
13
  /** Host: how long a kicked / disconnected peer's link stays open for the hub's notice to reach it first. */
13
14
  export const KICK_LINGER_MS = 3000;
15
+ /** How long an offer held during a possible migration stays answerable. */
16
+ const HELD_OFFER_MS = 20_000;
14
17
  /** The name shown for a peer that chose none: "Player N", N = its seat (peer id, join order). */
15
18
  export function seatName(p) {
16
19
  const n = typeof p.name === "string" ? p.name.trim() : "";
@@ -31,31 +34,62 @@ export class Lobby extends Emitter {
31
34
  tickRate;
32
35
  lockstep;
33
36
  chat;
37
+ /** The shared co-op pause (docs/recipes/pause.md). */
38
+ pause;
34
39
  voice;
35
40
  localPeerId;
36
41
  hostPeerId;
37
42
  phase;
38
43
  meta;
44
+ /** The lobby's name as the server keeps it (host()'s `name`, sanitised); "" if the server sends none. */
45
+ name;
39
46
  resumeToken;
40
47
  /** Last measured round trip to the host in ms (joiners), or 0. */
41
48
  rtt = 0;
42
49
  /**
43
- * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/direct-ip.md
50
+ * Direct-IP host only (onelobby/direct): the code friends paste to join, e.g. "6B01-RGFE-P2B" (docs/design/direct-ip.md
44
51
  * s.2a). Show it with joinCodeKind. Empty when the machine has no IPv4 address, and on joiners.
45
52
  */
46
53
  joinCode = "";
47
- /** "internet": the router forwarded the port and the code carries its public address; "same_network": the LAN address. */
54
+ /**
55
+ * "nostr": a version 1 code that works through public Nostr relays (spec/nostr-signaling.md; the default for a direct
56
+ * host). "internet": the router forwarded the port and the code carries its public address; "same_network": the LAN
57
+ * address. "none": no code.
58
+ */
48
59
  joinCodeKind = "none";
60
+ /**
61
+ * Direct-IP host only: the IP code (address and port, version 0), with `directCodeKind` "internet" or "same_network".
62
+ * Equal to `joinCode` when Nostr is off; otherwise the code to hand out for LAN play or when the router port is open.
63
+ */
64
+ directCode = "";
65
+ directCodeKind = "none";
49
66
  /** Direct-IP host only: this machine's addresses with the port, for an "Advanced" fold-out. */
50
67
  directAddresses = [];
51
68
  /** Direct-IP host only: how the router port-open went ("UPnP", "no UPnP gateway answered; ...", "off"). */
52
69
  portMapping = "";
70
+ /** Direct-IP host only: the one startup line (bound port, join code, port mapping); see `formatHostLog` in onelobby/direct. */
71
+ directHostLog = "";
72
+ /** Browser host only (`kit.hostNostr`): true while the host tab is hidden (see the `hostHiddenChanged` event). */
73
+ hostHidden = false;
74
+ /** @internal kit.hostNostr: the tab's visibility changed. */
75
+ setHostHidden(hidden) {
76
+ if (this.hostHidden === hidden)
77
+ return;
78
+ this.hostHidden = hidden;
79
+ this.emit("hostHiddenChanged", hidden);
80
+ }
81
+ /** @internal onelobby/direct: fires `joinCodeChanged` with the current code. */
82
+ notifyJoinCodeChanged() {
83
+ this.emit("joinCodeChanged", this.joinCode, this.joinCodeKind);
84
+ }
53
85
  signaling;
54
86
  cfg;
55
87
  roster = new Map();
56
88
  links = new Map();
57
89
  /** ICE from a peer that has no link yet: its candidates can overtake its offer (node-datachannel). */
58
90
  earlyIce = new Map();
91
+ /** Offers from roster members that arrived while the host link was open (see offerVerdict), by sender. */
92
+ heldOffers = new Map();
59
93
  connected = new Set(); // host: peers that said hi
60
94
  hiSentTo = new Set();
61
95
  epoch = 0;
@@ -77,6 +111,8 @@ export class Lobby extends Emitter {
77
111
  lastHeard = new Map();
78
112
  /** Host: links of kicked / disconnected peers, kept open briefly (removePeer `linger`). */
79
113
  lingering = new Set();
114
+ /** Host: joiners that joined but are not ready yet, with the cancel of their readyTimeoutMs timer (peerFailed). */
115
+ notReady = new Map();
80
116
  /** Host: per-peer fixed window for rateLimit. */
81
117
  rate = new Map();
82
118
  /**
@@ -107,6 +143,7 @@ export class Lobby extends Emitter {
107
143
  this.hostPeerId = st.hostId;
108
144
  this.phase = st.phase;
109
145
  this.meta = { ...st.meta };
146
+ this.name = st.name ?? "";
110
147
  this.resumeToken = st.resumeToken;
111
148
  for (const p of st.peers)
112
149
  this.roster.set(p.peerId, seatName(p));
@@ -131,6 +168,11 @@ export class Lobby extends Emitter {
131
168
  this.lockstep = new Lockstep(this.hub, { hashEveryTicks: cfg.hashEveryTicks });
132
169
  this.chat = new Chat(this.hub, cfg.wall, (id) => this.voice.isMutedPeer(id));
133
170
  this.voice = new Voice(this.hub, cfg.voice);
171
+ this.pause = new Pause(this.hub, {
172
+ phase: () => this.phase,
173
+ hold: (held) => this.lockstep.hold(held),
174
+ hasPeer: (id) => this.roster.has(id),
175
+ });
134
176
  this.lockstep.on("started", () => this.setPhase("in_game"));
135
177
  this.lockstep.on("error", (e) => this.reportError(e));
136
178
  this.signaling.onMessage = (m) => this.onSignaling(m);
@@ -377,6 +419,8 @@ export class Lobby extends Emitter {
377
419
  this.teardown("ice_failed");
378
420
  }
379
421
  // ---- links -----------------------------------------------------------------------------------------------------
422
+ /** @internal A direct host's hub learns where each joiner's link comes from (Nostr kick bans the address too). */
423
+ linkAddressSink = null;
380
424
  makeLink(peerId, relayOnly = this.cfg.relayOnly) {
381
425
  const old = this.links.get(peerId);
382
426
  if (old) {
@@ -391,6 +435,7 @@ export class Lobby extends Emitter {
391
435
  link.onClose = (reason) => this.onLinkClosed(peerId, link, reason);
392
436
  link.onMessage = (ch, bytes) => this.onData(peerId, ch, bytes);
393
437
  link.onPath = (path) => this.signaling.send({ t: "path", path });
438
+ link.onRemoteAddress = (ip) => this.linkAddressSink?.(peerId, ip);
394
439
  return link;
395
440
  }
396
441
  /** Host side: offer to a joiner. */
@@ -454,6 +499,10 @@ export class Lobby extends Emitter {
454
499
  }
455
500
  if (!d)
456
501
  return;
502
+ // The topology is a star: a joiner hears only its host. Anything else over some other link (a peer that offered to
503
+ // us, or a member that dialled us) is not host traffic and must not reach raw / lockstep / pause / chat handling.
504
+ if (!this.isHost && from !== this.hostPeerId && d.msg.type !== "host_claim")
505
+ return;
457
506
  if (!reassembled)
458
507
  this.lastHeard.set(from, this.cfg.clock.now());
459
508
  const { msg, route } = d;
@@ -505,6 +554,11 @@ export class Lobby extends Emitter {
505
554
  case "chat_reject":
506
555
  this.chat.handle(from, route, msg);
507
556
  break;
557
+ case "pause_request":
558
+ case "pause_state":
559
+ case "pause_refuse":
560
+ this.pause.handle(from, msg);
561
+ break;
508
562
  case "voice":
509
563
  if (channel === "voice")
510
564
  this.voice.handle(from, route, msg); // a voice frame on another channel is ignored
@@ -525,6 +579,8 @@ export class Lobby extends Emitter {
525
579
  }
526
580
  onHi(from, lastTick) {
527
581
  this.connected.add(from);
582
+ this.notReady.get(from)?.();
583
+ this.notReady.delete(from);
528
584
  this.sendTo(from, {
529
585
  type: "welcome_dc",
530
586
  // the tick the snapshot will carry (the game's applied tick), so welcome_dc.tick == snapshot.tick even when
@@ -535,6 +591,7 @@ export class Lobby extends Emitter {
535
591
  hostId: this.localPeerId,
536
592
  }, 0);
537
593
  this.chat.sendHistory(from);
594
+ this.pause.sendState(from);
538
595
  if (this.mode === "lockstep")
539
596
  this.lockstep.onPeerHi(from, lastTick);
540
597
  this.emit("peerReady", this.roster.get(from) ?? { peerId: from, name: "" });
@@ -583,6 +640,12 @@ export class Lobby extends Emitter {
583
640
  return;
584
641
  this.hostPeerId = hostId;
585
642
  this.emit("hostChanged", hostId);
643
+ const held = this.heldOffers.get(hostId);
644
+ if (held) {
645
+ this.heldOffers.delete(hostId);
646
+ if (this.cfg.clock.now() - held.at <= HELD_OFFER_MS)
647
+ void this.acceptOffer(hostId, held.payload);
648
+ }
586
649
  }
587
650
  /** The host is gone (link closed, no frames for 3 s, or the server said so): run the s.9 rule. */
588
651
  handleHostLost(reason) {
@@ -610,6 +673,16 @@ export class Lobby extends Emitter {
610
673
  if (candidate === this.localPeerId)
611
674
  this.becomeHost();
612
675
  // else: wait for the candidate's host_claim (or the server's host_changed); it offers to us and we answer with hi
676
+ this.replayHeldOffers();
677
+ }
678
+ /** Offers that came from roster members while the host link was still open: now the host is lost, answer them. */
679
+ replayHeldOffers() {
680
+ const now = this.cfg.clock.now();
681
+ for (const [from, held] of [...this.heldOffers]) {
682
+ this.heldOffers.delete(from);
683
+ if (now - held.at <= HELD_OFFER_MS && this.roster.has(from))
684
+ void this.acceptOffer(from, held.payload);
685
+ }
613
686
  }
614
687
  becomeHost() {
615
688
  this.epoch = (this.epoch + 1) & 0xffff;
@@ -619,6 +692,7 @@ export class Lobby extends Emitter {
619
692
  this.hiSentTo.clear();
620
693
  this.lastHeard.clear(); // joiners spoke to the old host; their clocks start when they say hi to us
621
694
  this.lockstep.becomeHost();
695
+ this.pause.onBecameHost();
622
696
  this.emit("hostChanged", this.localPeerId);
623
697
  if (this.serverUp)
624
698
  this.signaling.send({ t: "host_changed", hostId: this.localPeerId, epoch: this.epoch });
@@ -628,6 +702,8 @@ export class Lobby extends Emitter {
628
702
  }
629
703
  }
630
704
  onHostClaim(from, epoch, hostId) {
705
+ if (hostId !== from)
706
+ return; // a claim is a peer claiming itself; nobody claims for someone else
631
707
  const mine = { epoch: this.epoch, hostId: this.hostPeerId };
632
708
  const theirs = { epoch, hostId };
633
709
  if (hostId === this.hostPeerId && epoch === this.epoch && !this.isHost) {
@@ -667,8 +743,10 @@ export class Lobby extends Emitter {
667
743
  const peer = seatName(m.peer);
668
744
  this.roster.set(peer.peerId, peer);
669
745
  this.emit("peerJoined", peer);
670
- if (this.isHost)
746
+ if (this.isHost) {
747
+ this.watchReady(peer.peerId);
671
748
  this.offerTo(peer.peerId);
749
+ }
672
750
  break;
673
751
  }
674
752
  case "peer_left":
@@ -702,6 +780,8 @@ export class Lobby extends Emitter {
702
780
  case "lobby": {
703
781
  const st = m;
704
782
  this.meta = { ...st.meta };
783
+ if (st.name)
784
+ this.name = st.name;
705
785
  if (st.resumeToken && st.resumeToken !== this.resumeToken) {
706
786
  this.resumeToken = st.resumeToken;
707
787
  this.seatSink?.(this.resumeToken);
@@ -745,20 +825,21 @@ export class Lobby extends Emitter {
745
825
  if (s.ice)
746
826
  payload.ice = s.ice;
747
827
  if (s.sdp?.type === "offer") {
748
- // We are the answerer; an offer always comes from the (new) host.
749
- link = this.makeLink(from);
750
- const early = this.earlyIce.get(from) ?? [];
751
- this.earlyIce.delete(from);
752
- // the link holds candidates until the offer is set, so hand them over right behind it
753
- const done = link.handleSignal(payload);
754
- for (const e of early)
755
- void link.handleSignal(e);
756
- await done;
828
+ // We are the answerer. An offer comes from the (new) host, so a peer may dial us only as that rule allows.
829
+ const verdict = this.offerVerdict(from);
830
+ if (verdict === "hold") {
831
+ if (this.heldOffers.size < 64 || this.heldOffers.has(from))
832
+ this.heldOffers.set(from, { payload, at: this.cfg.clock.now() });
833
+ }
834
+ else if (verdict === "accept")
835
+ await this.acceptOffer(from, payload);
757
836
  return;
758
837
  }
759
838
  if (!link) {
760
839
  if (!payload.ice || this.ended || !this.roster.has(from))
761
840
  return;
841
+ if (this.cfg.direct && from !== this.hostPeerId)
842
+ return;
762
843
  const q = this.earlyIce.get(from) ?? [];
763
844
  if (q.length < 64)
764
845
  q.push(payload);
@@ -767,6 +848,35 @@ export class Lobby extends Emitter {
767
848
  }
768
849
  await link.handleSignal(payload);
769
850
  }
851
+ /**
852
+ * Whether an SDP offer from `from` may be answered. The host (or a joiner's host) always may. Direct lobbies have no
853
+ * migration, so nobody else. In a server lobby a roster member may dial us when we are the host (a claim race decides
854
+ * who wins) or when our own link to the host is gone (the migration window); while that link is open the offer is
855
+ * held, to be answered if the host turns out to be lost a moment later.
856
+ */
857
+ offerVerdict(from) {
858
+ if (this.ended || from === this.localPeerId)
859
+ return "drop";
860
+ if (!this.isHost && from === this.hostPeerId)
861
+ return "accept";
862
+ if (this.cfg.direct || !this.roster.has(from))
863
+ return "drop";
864
+ if (this.isHost)
865
+ return "accept";
866
+ if (!this.isReady)
867
+ return "drop"; // still joining: only the host's own offer is expected
868
+ return this.links.get(this.hostPeerId)?.isOpen ? "hold" : "accept";
869
+ }
870
+ async acceptOffer(from, payload) {
871
+ const link = this.makeLink(from);
872
+ const early = this.earlyIce.get(from) ?? [];
873
+ this.earlyIce.delete(from);
874
+ // the link holds candidates until the offer is set, so hand them over right behind it
875
+ const done = link.handleSignal(payload);
876
+ for (const e of early)
877
+ void link.handleSignal(e);
878
+ await done;
879
+ }
770
880
  /**
771
881
  * `linger` (host kick / disconnect): stop using the peer's link at once but leave it open until that peer closes it
772
882
  * or KICK_LINGER_MS pass. The hub tells the peer over its WebSocket; closing the data link first would make it look
@@ -781,6 +891,13 @@ export class Lobby extends Emitter {
781
891
  if (peer)
782
892
  this.roster.delete(peerId);
783
893
  this.earlyIce.delete(peerId);
894
+ const cancelWatch = this.notReady.get(peerId);
895
+ if (cancelWatch) {
896
+ cancelWatch();
897
+ this.notReady.delete(peerId);
898
+ if (peer && reason !== "kicked" && reason !== "disconnected")
899
+ this.emit("peerFailed", peer, reason);
900
+ }
784
901
  const link = this.links.get(peerId);
785
902
  if (link && peerId !== this.hostPeerId) {
786
903
  link.onMessage = null;
@@ -810,6 +927,7 @@ export class Lobby extends Emitter {
810
927
  this.lockstep.onPeerLeft(peerId);
811
928
  this.chat.onPeerLeft(peerId);
812
929
  this.voice.onPeerLeft(peerId);
930
+ this.pause.onPeerLeft(peerId);
813
931
  if (peer)
814
932
  this.emit("peerLeft", peer, reason, close);
815
933
  if (peerId === this.hostPeerId && !this.isHost) {
@@ -827,6 +945,8 @@ export class Lobby extends Emitter {
827
945
  if (this.phase === p)
828
946
  return;
829
947
  this.phase = p;
948
+ if (p === "lobby")
949
+ this.pause.onLobbyPhase();
830
950
  if (p === "in_game" && !this.lockstep.started)
831
951
  this.lockstep.markStarted();
832
952
  this.emit("phase", p);
@@ -882,11 +1002,29 @@ export class Lobby extends Emitter {
882
1002
  return;
883
1003
  this.sendTo(this.hostPeerId, { type: "ping", n: ++this.pingN, t: this.cfg.clock.now() >>> 0 }, 0, "fast");
884
1004
  }
1005
+ /** Host: report a joiner that is not ready `readyTimeoutMs` after joining as peerFailed("timeout"), once. */
1006
+ watchReady(peerId) {
1007
+ const ms = this.cfg.readyTimeoutMs ?? 0;
1008
+ let cancel = () => { };
1009
+ if (ms > 0) {
1010
+ cancel = this.cfg.clock.setTimeout(() => {
1011
+ if (!this.notReady.delete(peerId))
1012
+ return;
1013
+ const peer = this.roster.get(peerId);
1014
+ if (peer)
1015
+ this.emit("peerFailed", peer, "timeout");
1016
+ }, ms);
1017
+ }
1018
+ this.notReady.set(peerId, cancel);
1019
+ }
885
1020
  teardown(reason, close) {
886
1021
  if (this.ended)
887
1022
  return;
888
1023
  this.ended = true;
889
1024
  this.cancelReadyTimer?.();
1025
+ for (const cancel of this.notReady.values())
1026
+ cancel();
1027
+ this.notReady.clear();
890
1028
  if (!this.isReady) {
891
1029
  const code = reason === "ice_failed" || reason === "kicked" ? reason : "closed";
892
1030
  this.failReady(new OneLobbyError(code, `the lobby ended before connecting (${reason})`));
@@ -902,6 +1040,7 @@ export class Lobby extends Emitter {
902
1040
  }
903
1041
  this.links.clear();
904
1042
  this.earlyIce.clear();
1043
+ this.heldOffers.clear();
905
1044
  this.lingering.clear();
906
1045
  this.signaling.onClose = null;
907
1046
  this.signaling.onMessage = null;