@decentnetwork/peer 0.1.4 → 0.1.6

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/peer.d.ts CHANGED
@@ -13,6 +13,31 @@ export declare class Peer {
13
13
  joinNetwork(): Promise<BootstrapResult>;
14
14
  lookup(pubkey: string): Promise<LookupResult>;
15
15
  announceSelf(timeoutMs?: number): Promise<NetworkNode[]>;
16
+ /**
17
+ * Aggregate DHT health snapshot — surfaces the layers that have to
18
+ * work for UDP holepunch / route discovery to succeed.
19
+ * bootstrapsConfigured – nodes the SDK is allowed to talk to
20
+ * knownNodesCount – nodes discovered or persisted in our DHT view
21
+ * lastSelfAnnounceMs – last time we ran a self-announce sweep
22
+ * selfAnnounceStoredOn – how many nodes acknowledged STORING our
23
+ * announce last time. **If 0, our outbound
24
+ * DHT is broken** — peers can't find us,
25
+ * #discoverFriendRoutes returns nothing,
26
+ * UDP holepunch is impossible. Most common
27
+ * failure mode when sessions stay on
28
+ * tcp-relay forever.
29
+ * udpLocalPort – the OS-assigned UDP port (null = no socket)
30
+ * tcpRelayConnected – fallback relay path; if >0, sessions get
31
+ * through even when DHT is dead.
32
+ */
33
+ dhtHealth(): {
34
+ bootstrapsConfigured: number;
35
+ knownNodesCount: number;
36
+ lastSelfAnnounceMs: number;
37
+ selfAnnounceStoredOn: number;
38
+ udpLocalPort: number | null;
39
+ tcpRelayConnected: number;
40
+ };
16
41
  addKnownNodes(nodes: NetworkNode[]): void;
17
42
  knownNodes(): NetworkNode[];
18
43
  sendFriendRequest(pubkey: string, hello?: string): Promise<void>;
package/dist/peer.js CHANGED
@@ -434,6 +434,33 @@ export class Peer {
434
434
  async announceSelf(timeoutMs = 15000) {
435
435
  return this.#runSelfAnnounce(true, Date.now() + timeoutMs);
436
436
  }
437
+ /**
438
+ * Aggregate DHT health snapshot — surfaces the layers that have to
439
+ * work for UDP holepunch / route discovery to succeed.
440
+ * bootstrapsConfigured – nodes the SDK is allowed to talk to
441
+ * knownNodesCount – nodes discovered or persisted in our DHT view
442
+ * lastSelfAnnounceMs – last time we ran a self-announce sweep
443
+ * selfAnnounceStoredOn – how many nodes acknowledged STORING our
444
+ * announce last time. **If 0, our outbound
445
+ * DHT is broken** — peers can't find us,
446
+ * #discoverFriendRoutes returns nothing,
447
+ * UDP holepunch is impossible. Most common
448
+ * failure mode when sessions stay on
449
+ * tcp-relay forever.
450
+ * udpLocalPort – the OS-assigned UDP port (null = no socket)
451
+ * tcpRelayConnected – fallback relay path; if >0, sessions get
452
+ * through even when DHT is dead.
453
+ */
454
+ dhtHealth() {
455
+ return {
456
+ bootstrapsConfigured: this.#opts.bootstrapNodes.length,
457
+ knownNodesCount: this.#knownNodes.length,
458
+ lastSelfAnnounceMs: this.#lastSelfAnnounceMs,
459
+ selfAnnounceStoredOn: this.#lastSelfAnnounceStoredCount,
460
+ udpLocalPort: this.#udp?.localPort() ?? null,
461
+ tcpRelayConnected: this.#tcpRelays?.connectedRelays(99).length ?? 0,
462
+ };
463
+ }
437
464
  addKnownNodes(nodes) {
438
465
  this.#knownNodes = dedupeNodes([...nodes, ...this.#knownNodes]);
439
466
  }
@@ -2788,10 +2815,73 @@ export class Peer {
2788
2815
  // residential ISPs where self-announce doesn't propagate).
2789
2816
  // Mirrors toxcore's send_dht_pk_packet which calls
2790
2817
  // tcp_copy_connected_relays to fill extras.
2791
- let extras = new Uint8Array(0);
2818
+ // Also pack OUR OWN UDP endpoint(s) as extras (family 0x02 =
2819
+ // UDP_FAMILY_IPV4). Without this, peers receiving our DHT-PK push
2820
+ // learn our TCP relays but have NO IDEA where to send UDP cookie
2821
+ // requests — so they never even try UDP holepunch and every
2822
+ // session stays on tcp-relay (~500ms+ RTT) instead of the
2823
+ // ~80ms UDP path. Observed in the wild: even same-LAN peers
2824
+ // (snoopy and mac-dev on the same /24) stuck on tcp-relay
2825
+ // forever because neither side ever announces a UDP endpoint
2826
+ // to the other.
2827
+ //
2828
+ // For peers behind NAT, the LAN address we advertise is
2829
+ // reachable by same-LAN peers and harmless for WAN peers
2830
+ // (they fall through to tcp-relay as before). For peers with
2831
+ // a public IP (e.g. a Vultr VPS), the LAN address IS the
2832
+ // reachable one; same-format works. STUN-style reflexive
2833
+ // discovery is the next layer but isn't required to fix the
2834
+ // LAN case immediately.
2835
+ const packed = [];
2836
+ const ourUdpPort = this.#udp?.localPort();
2837
+ if (ourUdpPort && ourUdpPort > 0) {
2838
+ const ourDhtPk = this.#keyPair?.publicKey;
2839
+ if (ourDhtPk && ourDhtPk.length === 32) {
2840
+ // Enumerate non-loopback IPv4 addresses on this host.
2841
+ // We use os.networkInterfaces() rather than guessing — this
2842
+ // covers a multi-homed peer with several reachable IPs.
2843
+ try {
2844
+ const ifaces = (await import("os")).networkInterfaces();
2845
+ const seenIps = new Set();
2846
+ for (const iface of Object.values(ifaces)) {
2847
+ if (!iface)
2848
+ continue;
2849
+ for (const addr of iface) {
2850
+ if (addr.family !== "IPv4")
2851
+ continue;
2852
+ if (addr.internal)
2853
+ continue;
2854
+ if (addr.address === "0.0.0.0")
2855
+ continue;
2856
+ if (seenIps.has(addr.address))
2857
+ continue;
2858
+ seenIps.add(addr.address);
2859
+ const parts = addr.address
2860
+ .split(".")
2861
+ .map((p) => Number.parseInt(p, 10));
2862
+ if (parts.length !== 4 || parts.some((n) => !(n >= 0 && n <= 255)))
2863
+ continue;
2864
+ const entry = new Uint8Array(1 + 4 + 2 + 32);
2865
+ entry[0] = 0x02; // UDP_FAMILY_IPV4
2866
+ entry[1] = parts[0];
2867
+ entry[2] = parts[1];
2868
+ entry[3] = parts[2];
2869
+ entry[4] = parts[3];
2870
+ entry[5] = (ourUdpPort >> 8) & 0xff;
2871
+ entry[6] = ourUdpPort & 0xff;
2872
+ entry.set(ourDhtPk, 7);
2873
+ packed.push(entry);
2874
+ }
2875
+ }
2876
+ }
2877
+ catch {
2878
+ // os import or interface enumeration failed; skip UDP extras
2879
+ // and let TCP relay carry the session as before.
2880
+ }
2881
+ }
2882
+ }
2792
2883
  if (this.#tcpRelays) {
2793
2884
  const relays = this.#tcpRelays.connectedRelays(3); // MAX_SHARED_RELAYS
2794
- const packed = [];
2795
2885
  for (const r of relays) {
2796
2886
  // Only IPv4 TCP entries supported here; matches what we parse.
2797
2887
  // Format per packed-nodes spec: family(1) + ipv4(4) + port(2 BE) + pk(32)
@@ -2809,10 +2899,8 @@ export class Peer {
2809
2899
  entry.set(r.serverPublicKey, 7);
2810
2900
  packed.push(entry);
2811
2901
  }
2812
- if (packed.length > 0) {
2813
- extras = concatBytes(packed);
2814
- }
2815
2902
  }
2903
+ const extras = packed.length > 0 ? concatBytes(packed) : new Uint8Array(0);
2816
2904
  const innerPayload = concatBytes([
2817
2905
  noReplayBytes,
2818
2906
  this.#keyPair.publicKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",