@decentnetwork/peer 0.1.44 → 0.1.46

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 (2) hide show
  1. package/dist/peer.js +54 -3
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -2812,9 +2812,23 @@ export class Peer {
2812
2812
  firstError = error;
2813
2813
  }
2814
2814
  }
2815
- // Fresh direct path → send over UDP only: no relay fan-out, no
2816
- // duplicates, no relay backlog. Applies to both data and control.
2817
- if (udpFresh && udpOk) {
2815
+ // Fresh direct path → send over UDP only but ONLY for bulk data.
2816
+ // Bulk is high-volume and must avoid relay fan-out / backlog, so once
2817
+ // the direct path is fresh it rides UDP exclusively.
2818
+ //
2819
+ // Low-volume traffic (chat 64 + control keepalives / endpoint offers /
2820
+ // profile) instead ALSO goes over the relay even when the direct path
2821
+ // looks fresh, because `udpFresh` is derived from INBOUND UDP
2822
+ // (lastUdpRecvMs) and inbound reachability does NOT imply outbound
2823
+ // reachability. On a symmetric / CGNAT peer (e.g. cellular T-Mobile
2824
+ // 172.56.x) the peer's pings keep arriving — so udpFresh stays true —
2825
+ // while our packets to the source port we observed are silently
2826
+ // black-holed: the peer never received from us on that port, so its NAT
2827
+ // has no inbound mapping for our endpoint. Returning UDP-only here would
2828
+ // fire chat into a dead endpoint forever — the exact "peer can send to us
2829
+ // but can't receive from us" one-way failure. net_crypto dedups by packet
2830
+ // number, so the extra relay copy is harmless for sparse chat / control.
2831
+ if (isBulkData && udpFresh && udpOk) {
2818
2832
  return;
2819
2833
  }
2820
2834
  // Tier 2: TURN relay (tokyo) — a fast (~280ms) stable fallback that
@@ -3800,6 +3814,27 @@ export class Peer {
3800
3814
  if (typeof this.#dhtMaintenanceTimer.unref === "function")
3801
3815
  this.#dhtMaintenanceTimer.unref();
3802
3816
  void this.#doDhtMaintenance().catch(() => undefined); // become findable right after join
3817
+ // Fast initial population. The steady 15s cadence converges far too
3818
+ // slowly from a cold table — a fresh peer sat at ~14 known nodes, which
3819
+ // is too sparse for the onion announce to store on the nodes truly
3820
+ // closest to our key (the ones native clients query when searching for
3821
+ // us). Run a few quick rounds up front to fill the table within seconds
3822
+ // so the announce can reach storing nodes and we become discoverable.
3823
+ for (const delay of [1200, 3000, 5500, 9000, 13000]) {
3824
+ const t = setTimeout(() => void this.#doDhtMaintenance().catch(() => undefined), delay);
3825
+ if (typeof t.unref === "function")
3826
+ t.unref();
3827
+ }
3828
+ // The join-time announce ran against a near-empty table, so it stored on
3829
+ // few/no nodes. Re-announce once the burst above has populated the table
3830
+ // (~10s) so our FIRST useful announce lands on the genuinely-closest
3831
+ // nodes — the difference between "discoverable within seconds" and "not
3832
+ // until the 20s steady-state timer happens to converge".
3833
+ for (const delay of [10000, 18000]) {
3834
+ const a = setTimeout(() => void this.#runSelfAnnounce(true, Date.now() + 8000).catch(() => undefined), delay);
3835
+ if (typeof a.unref === "function")
3836
+ a.unref();
3837
+ }
3803
3838
  }
3804
3839
  async #doDhtMaintenance() {
3805
3840
  if (!this.#keyPair)
@@ -3810,6 +3845,22 @@ export class Peer {
3810
3845
  for (const node of this.#closestKnownNodes(selfPk, 8)) {
3811
3846
  void this.#sendDhtGetNodes(node, selfPk);
3812
3847
  }
3848
+ // 1b. Fill the routing table ACROSS the keyspace via random-target
3849
+ // searches. toxcore keeps random DHT search slots so the table isn't
3850
+ // clustered only near self; without this a JS peer's table stayed tiny
3851
+ // (~14-97 nodes) and the onion announce could only store on a sparse,
3852
+ // far-from-closest subset that native searchers never query — so they
3853
+ // couldn't discover the JS peer by address. A fuller table lets the
3854
+ // announce reach the genuinely-closest nodes. Search harder while the
3855
+ // table is small, then taper to a steady refresh.
3856
+ const small = this.#knownNodes.length < 150;
3857
+ const randomSearches = this.#knownNodes.length < 60 ? 6 : small ? 4 : 2;
3858
+ for (let i = 0; i < randomSearches; i++) {
3859
+ const target = randomBytes(32);
3860
+ for (const node of this.#closestKnownNodes(target, small ? 6 : 4)) {
3861
+ void this.#sendDhtGetNodes(node, target);
3862
+ }
3863
+ }
3813
3864
  // 2. For each friend not on a confirmed UDP path, get_nodes toward THEIR
3814
3865
  // DHT key → learn their UDP endpoint → punch from our side.
3815
3866
  for (const [, session] of this.#friendSessions.entries()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
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",