@muhkoo/connect 0.10.2-alpha.0 → 0.10.4-alpha.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.
@@ -5057,7 +5057,7 @@ function base64UrlDecode(s) {
5057
5057
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
5058
5058
  * `package.json` `version`.
5059
5059
  */
5060
- const VERSION = "0.10.1-alpha.0";
5060
+ const VERSION = "0.10.4-alpha.0";
5061
5061
 
5062
5062
  /**
5063
5063
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -8812,14 +8812,20 @@ class WebRtcTransport {
8812
8812
  offSignal;
8813
8813
  iceServers;
8814
8814
  maxPeers;
8815
+ debug;
8815
8816
  nextMsgId = 1;
8816
8817
  constructor(signaler, myId, opts = {}) {
8817
8818
  this.signaler = signaler;
8818
8819
  this.myId = myId;
8819
8820
  this.iceServers = opts.iceServers ?? DEFAULT_ICE;
8820
8821
  this.maxPeers = opts.maxPeers ?? 8;
8822
+ this.debug = opts.debug ?? false;
8821
8823
  this.offSignal = this.signaler.onSignal((from, kind, data) => this.onSignal(from, kind, data));
8822
8824
  }
8825
+ log(...a) {
8826
+ if (this.debug)
8827
+ console.info("[muhkoo:p2p]", ...a);
8828
+ }
8823
8829
  peers() {
8824
8830
  return [...this.conns].filter(([, c]) => c.dc?.readyState === "open").map(([id]) => id);
8825
8831
  }
@@ -8870,19 +8876,42 @@ class WebRtcTransport {
8870
8876
  // -- internals -----------------------------------------------------------
8871
8877
  newPc(peerId) {
8872
8878
  const pc = new RTCPeerConnection({ iceServers: this.iceServers });
8873
- const conn = { pc, reasm: new Map() };
8879
+ const conn = { pc, reasm: new Map(), remoteSet: false, pendingIce: [] };
8874
8880
  this.conns.set(peerId, conn);
8881
+ const short = String(peerId).slice(0, 8);
8875
8882
  pc.onicecandidate = (e) => {
8876
- if (e.candidate)
8883
+ if (e.candidate) {
8884
+ this.log(`${short} local candidate ${e.candidate.type ?? "?"} ${(e.candidate.address ?? e.candidate.candidate).toString().slice(0, 40)}`);
8877
8885
  this.signaler.send(peerId, "ice", e.candidate.toJSON());
8886
+ }
8878
8887
  };
8879
8888
  pc.onconnectionstatechange = () => {
8889
+ this.log(`${short} connection=${pc.connectionState}`);
8880
8890
  if (["failed", "closed", "disconnected"].includes(pc.connectionState))
8881
8891
  this.drop(peerId);
8882
8892
  };
8893
+ if (this.debug) {
8894
+ pc.oniceconnectionstatechange = () => this.log(`${short} ice=${pc.iceConnectionState}`);
8895
+ pc.onicegatheringstatechange = () => this.log(`${short} gathering=${pc.iceGatheringState}`);
8896
+ }
8883
8897
  pc.ondatachannel = (e) => this.bindChannel(peerId, conn, e.channel);
8884
8898
  return conn;
8885
8899
  }
8900
+ /** Mark a peer's remote description as set and apply any ICE candidates that
8901
+ * arrived before it (dropping them, the old behavior, breaks LAN dials). */
8902
+ async markRemoteSet(conn) {
8903
+ conn.remoteSet = true;
8904
+ const pending = conn.pendingIce;
8905
+ conn.pendingIce = [];
8906
+ for (const cand of pending) {
8907
+ try {
8908
+ await conn.pc.addIceCandidate(cand);
8909
+ }
8910
+ catch { /* stale/duplicate */ }
8911
+ }
8912
+ if (pending.length)
8913
+ this.log(`flushed ${pending.length} queued ICE candidate(s)`);
8914
+ }
8886
8915
  async initiate(peerId) {
8887
8916
  const conn = this.newPc(peerId);
8888
8917
  const dc = conn.pc.createDataChannel("blocks");
@@ -8904,22 +8933,32 @@ class WebRtcTransport {
8904
8933
  return; // mesh cap
8905
8934
  const conn = this.conns.get(from) ?? this.newPc(from);
8906
8935
  await conn.pc.setRemoteDescription(data);
8936
+ await this.markRemoteSet(conn);
8907
8937
  const answer = await conn.pc.createAnswer();
8908
8938
  await conn.pc.setLocalDescription(answer);
8909
8939
  this.signaler.send(from, "answer", answer);
8910
8940
  }
8911
8941
  else if (kind === "answer") {
8912
8942
  const conn = this.conns.get(from);
8913
- if (conn)
8943
+ if (conn) {
8914
8944
  await conn.pc.setRemoteDescription(data);
8945
+ await this.markRemoteSet(conn);
8946
+ }
8915
8947
  }
8916
8948
  else if (kind === "ice") {
8917
8949
  const conn = this.conns.get(from);
8918
8950
  if (conn && data) {
8951
+ // Queue candidates that arrive before setRemoteDescription — adding
8952
+ // them early throws and (previously) dropped them, which killed the
8953
+ // connection on fast LANs where ICE beats the SDP answer.
8954
+ if (!conn.remoteSet) {
8955
+ conn.pendingIce.push(data);
8956
+ return;
8957
+ }
8919
8958
  try {
8920
8959
  await conn.pc.addIceCandidate(data);
8921
8960
  }
8922
- catch { /* pre-SRD race */ }
8961
+ catch { /* stale */ }
8923
8962
  }
8924
8963
  }
8925
8964
  else if (kind === "bye") {
@@ -9388,6 +9427,7 @@ class PeerNetwork {
9388
9427
  new WebRtcTransport(this.signaler, opts.myId, {
9389
9428
  iceServers: opts.iceServers,
9390
9429
  maxPeers: opts.maxPeers,
9430
+ debug: opts.debug,
9391
9431
  });
9392
9432
  this.host = buildEngineHost(opts);
9393
9433
  // Wire transport ⇄ engine, demuxing block (engine) from gossip (app).