@muhkoo/connect 0.10.3-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.
package/dist/connect.d.ts CHANGED
@@ -5591,6 +5591,8 @@ interface WebRtcTransportOptions {
5591
5591
  iceServers?: RTCIceServer[];
5592
5592
  /** Cap on simultaneous peer connections (mesh size). Default 8. */
5593
5593
  maxPeers?: number;
5594
+ /** Log ICE/connection state transitions for diagnosing peer connectivity. */
5595
+ debug?: boolean;
5594
5596
  }
5595
5597
  declare class WebRtcTransport implements PeerTransport {
5596
5598
  private readonly signaler;
@@ -5601,8 +5603,10 @@ declare class WebRtcTransport implements PeerTransport {
5601
5603
  private readonly offSignal;
5602
5604
  private readonly iceServers;
5603
5605
  private readonly maxPeers;
5606
+ private readonly debug;
5604
5607
  private nextMsgId;
5605
5608
  constructor(signaler: SpaceSignaler, myId: PeerId, opts?: WebRtcTransportOptions);
5609
+ private log;
5606
5610
  peers(): PeerId[];
5607
5611
  /** Dial a peer (no-op unless we're the deterministic initiator for the pair). */
5608
5612
  connectTo(peerId: PeerId): void;
@@ -5612,6 +5616,9 @@ declare class WebRtcTransport implements PeerTransport {
5612
5616
  onPeerChange(cb: (peers: PeerId[]) => void): () => void;
5613
5617
  close(): void;
5614
5618
  private newPc;
5619
+ /** Mark a peer's remote description as set and apply any ICE candidates that
5620
+ * arrived before it (dropping them, the old behavior, breaks LAN dials). */
5621
+ private markRemoteSet;
5615
5622
  private initiate;
5616
5623
  private bindChannel;
5617
5624
  private onSignal;
@@ -2976,7 +2976,7 @@ function base64UrlDecode(s) {
2976
2976
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
2977
2977
  * `package.json` `version`.
2978
2978
  */
2979
- const VERSION = "0.10.3-alpha.0";
2979
+ const VERSION = "0.10.4-alpha.0";
2980
2980
 
2981
2981
  /**
2982
2982
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -8543,14 +8543,20 @@ class WebRtcTransport {
8543
8543
  offSignal;
8544
8544
  iceServers;
8545
8545
  maxPeers;
8546
+ debug;
8546
8547
  nextMsgId = 1;
8547
8548
  constructor(signaler, myId, opts = {}) {
8548
8549
  this.signaler = signaler;
8549
8550
  this.myId = myId;
8550
8551
  this.iceServers = opts.iceServers ?? DEFAULT_ICE;
8551
8552
  this.maxPeers = opts.maxPeers ?? 8;
8553
+ this.debug = opts.debug ?? false;
8552
8554
  this.offSignal = this.signaler.onSignal((from, kind, data) => this.onSignal(from, kind, data));
8553
8555
  }
8556
+ log(...a) {
8557
+ if (this.debug)
8558
+ console.info("[muhkoo:p2p]", ...a);
8559
+ }
8554
8560
  peers() {
8555
8561
  return [...this.conns].filter(([, c]) => c.dc?.readyState === "open").map(([id]) => id);
8556
8562
  }
@@ -8601,19 +8607,42 @@ class WebRtcTransport {
8601
8607
  // -- internals -----------------------------------------------------------
8602
8608
  newPc(peerId) {
8603
8609
  const pc = new RTCPeerConnection({ iceServers: this.iceServers });
8604
- const conn = { pc, reasm: new Map() };
8610
+ const conn = { pc, reasm: new Map(), remoteSet: false, pendingIce: [] };
8605
8611
  this.conns.set(peerId, conn);
8612
+ const short = String(peerId).slice(0, 8);
8606
8613
  pc.onicecandidate = (e) => {
8607
- if (e.candidate)
8614
+ if (e.candidate) {
8615
+ this.log(`${short} local candidate ${e.candidate.type ?? "?"} ${(e.candidate.address ?? e.candidate.candidate).toString().slice(0, 40)}`);
8608
8616
  this.signaler.send(peerId, "ice", e.candidate.toJSON());
8617
+ }
8609
8618
  };
8610
8619
  pc.onconnectionstatechange = () => {
8620
+ this.log(`${short} connection=${pc.connectionState}`);
8611
8621
  if (["failed", "closed", "disconnected"].includes(pc.connectionState))
8612
8622
  this.drop(peerId);
8613
8623
  };
8624
+ if (this.debug) {
8625
+ pc.oniceconnectionstatechange = () => this.log(`${short} ice=${pc.iceConnectionState}`);
8626
+ pc.onicegatheringstatechange = () => this.log(`${short} gathering=${pc.iceGatheringState}`);
8627
+ }
8614
8628
  pc.ondatachannel = (e) => this.bindChannel(peerId, conn, e.channel);
8615
8629
  return conn;
8616
8630
  }
8631
+ /** Mark a peer's remote description as set and apply any ICE candidates that
8632
+ * arrived before it (dropping them, the old behavior, breaks LAN dials). */
8633
+ async markRemoteSet(conn) {
8634
+ conn.remoteSet = true;
8635
+ const pending = conn.pendingIce;
8636
+ conn.pendingIce = [];
8637
+ for (const cand of pending) {
8638
+ try {
8639
+ await conn.pc.addIceCandidate(cand);
8640
+ }
8641
+ catch { /* stale/duplicate */ }
8642
+ }
8643
+ if (pending.length)
8644
+ this.log(`flushed ${pending.length} queued ICE candidate(s)`);
8645
+ }
8617
8646
  async initiate(peerId) {
8618
8647
  const conn = this.newPc(peerId);
8619
8648
  const dc = conn.pc.createDataChannel("blocks");
@@ -8635,22 +8664,32 @@ class WebRtcTransport {
8635
8664
  return; // mesh cap
8636
8665
  const conn = this.conns.get(from) ?? this.newPc(from);
8637
8666
  await conn.pc.setRemoteDescription(data);
8667
+ await this.markRemoteSet(conn);
8638
8668
  const answer = await conn.pc.createAnswer();
8639
8669
  await conn.pc.setLocalDescription(answer);
8640
8670
  this.signaler.send(from, "answer", answer);
8641
8671
  }
8642
8672
  else if (kind === "answer") {
8643
8673
  const conn = this.conns.get(from);
8644
- if (conn)
8674
+ if (conn) {
8645
8675
  await conn.pc.setRemoteDescription(data);
8676
+ await this.markRemoteSet(conn);
8677
+ }
8646
8678
  }
8647
8679
  else if (kind === "ice") {
8648
8680
  const conn = this.conns.get(from);
8649
8681
  if (conn && data) {
8682
+ // Queue candidates that arrive before setRemoteDescription — adding
8683
+ // them early throws and (previously) dropped them, which killed the
8684
+ // connection on fast LANs where ICE beats the SDP answer.
8685
+ if (!conn.remoteSet) {
8686
+ conn.pendingIce.push(data);
8687
+ return;
8688
+ }
8650
8689
  try {
8651
8690
  await conn.pc.addIceCandidate(data);
8652
8691
  }
8653
- catch { /* pre-SRD race */ }
8692
+ catch { /* stale */ }
8654
8693
  }
8655
8694
  }
8656
8695
  else if (kind === "bye") {
@@ -9119,6 +9158,7 @@ class PeerNetwork {
9119
9158
  new WebRtcTransport(this.signaler, opts.myId, {
9120
9159
  iceServers: opts.iceServers,
9121
9160
  maxPeers: opts.maxPeers,
9161
+ debug: opts.debug,
9122
9162
  });
9123
9163
  this.host = buildEngineHost(opts);
9124
9164
  // Wire transport ⇄ engine, demuxing block (engine) from gossip (app).