@decentnetwork/peer 0.1.143 → 0.1.145

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.js CHANGED
@@ -373,6 +373,10 @@ export class Peer {
373
373
  #knownNodes;
374
374
  #announceDataKey;
375
375
  #lastSelfAnnounceMs = 0;
376
+ // Env vars OR an explicit option. A browser has no process.env, so peer
377
+ // debugging there was simply unreachable — which is why the browser client
378
+ // has been a black box every time it misbehaved. The option is the only way
379
+ // in from a page.
376
380
  #debug = process.env.DECENT_DEBUG === "1" || process.env.DECENT_DEBUG_VERBOSE === "1";
377
381
  #debugVerbose = process.env.DECENT_DEBUG_VERBOSE === "1";
378
382
  #packetTrace = process.env.DECENT_PACKET_TRACE === "1";
@@ -538,6 +542,15 @@ export class Peer {
538
542
  ...opts,
539
543
  compatibilityMode: opts.compatibilityMode ?? "legacy"
540
544
  };
545
+ // A browser has no process.env, so the env-var switches above can never
546
+ // be set there and peer debugging was unreachable from a page. That is
547
+ // why every browser misbehaviour has had to be diagnosed by inference.
548
+ if (opts.debug)
549
+ this.#debug = true;
550
+ if (opts.debugVerbose) {
551
+ this.#debug = true;
552
+ this.#debugVerbose = true;
553
+ }
541
554
  this.#knownNodes = [...opts.bootstrapNodes];
542
555
  this.#friendStoreFile = opts.friendStoreFile ?? `${opts.keyFile}.friends.json`;
543
556
  // Enable resumable (断点续传) receives when the app gives us a directory to
@@ -6860,6 +6873,41 @@ export class Peer {
6860
6873
  async #sendThroughOnionPath(payloadForNodeD, nodeD, pathOffset = 0, forcedPath) {
6861
6874
  const path = forcedPath === "direct" ? undefined : forcedPath ?? this.#selectOnionPath(nodeD, pathOffset);
6862
6875
  if (!path) {
6876
+ // No 3-hop UDP path. "Direct" then meant a plain UDP send — which on a
6877
+ // host with no UDP (a browser, where dgram is a socket that drops) goes
6878
+ // straight into the void, silently, forever. Every announce and every
6879
+ // friend-request lookup died here.
6880
+ //
6881
+ // A RELAYED onion request needs only two hops: the relay is node A. So
6882
+ // try that before giving up, and only fall through to the UDP send when
6883
+ // there are no relays or not even two usable hops.
6884
+ if (this.#tcpRelays && this.#tcpRelays.connectedCount() > 0) {
6885
+ const hops = this.#selectTcpOnionHops(nodeD, pathOffset);
6886
+ if (hops) {
6887
+ try {
6888
+ const tcpPacket = createOnionRequest0Tcp({
6889
+ nodeBHost: hops.nodeB.host,
6890
+ nodeBPort: hops.nodeB.port,
6891
+ nodeBPublicKey: hops.nodeB.publicKey,
6892
+ nodeCHost: hops.nodeC.host,
6893
+ nodeCPort: hops.nodeC.port,
6894
+ nodeCPublicKey: hops.nodeC.publicKey,
6895
+ nodeDHost: nodeD.host,
6896
+ nodeDPort: nodeD.port,
6897
+ payloadForNodeD
6898
+ });
6899
+ const sent = this.#tcpRelays.sendOnionRequest(tcpPacket);
6900
+ if (sent > 0) {
6901
+ this.#diagTcpOnionSent += 1;
6902
+ this.#debugLog(`no 3-hop path for ${nodeD.host}:${nodeD.port} — relayed 2-hop onion via ${sent} relay(s) (B=${hops.nodeB.host} C=${hops.nodeC.host})`);
6903
+ return "direct";
6904
+ }
6905
+ }
6906
+ catch {
6907
+ /* malformed hop key — fall through to the UDP attempt */
6908
+ }
6909
+ }
6910
+ }
6863
6911
  this.#debugLog(`no onion path for ${nodeD.host}:${nodeD.port}, sending direct`);
6864
6912
  await this.#sendPacket(payloadForNodeD, nodeD);
6865
6913
  return "direct";
@@ -6910,7 +6958,10 @@ export class Peer {
6910
6958
  }
6911
6959
  return path;
6912
6960
  }
6913
- #selectOnionPath(nodeD, pathOffset = 0) {
6961
+ /** Ranked hop candidates for a path to nodeD. Shared by the 3-hop UDP
6962
+ * selector and the 2-hop TCP one — a relay stands in for node A, so a
6963
+ * relayed path needs one fewer usable node than a direct one. */
6964
+ #onionCandidatePool(nodeD) {
6914
6965
  const nodeDId = `${nodeD.host}:${nodeD.port}`;
6915
6966
  const candidates = [];
6916
6967
  const bootstrapIds = new Set(this.#opts.bootstrapNodes.map((node) => `${node.host}:${node.port}`));
@@ -6959,13 +7010,33 @@ export class Peer {
6959
7010
  // Ignore bad keys.
6960
7011
  }
6961
7012
  }
6962
- if (candidates.length < 3) {
6963
- return undefined;
6964
- }
6965
7013
  candidates.sort((a, b) => b.score - a.score);
6966
7014
  const preferredRelays = candidates.filter((item) => isLikelyStableRelayPort(item.node.port));
6967
7015
  const healthy = preferredRelays.filter((item) => item.score > -2);
6968
- const pool = healthy.length >= 3 ? healthy : preferredRelays.length >= 3 ? preferredRelays : candidates;
7016
+ return healthy.length >= 3 ? healthy : preferredRelays.length >= 3 ? preferredRelays : candidates;
7017
+ }
7018
+ /** Two hops (B and C) for a relayed onion request. The relay is node A, so
7019
+ * this succeeds on node sets too thin for a direct 3-hop path — which is
7020
+ * the browser's normal state, and where every request used to fall through
7021
+ * to a UDP send that goes nowhere. */
7022
+ #selectTcpOnionHops(nodeD, pathOffset = 0) {
7023
+ const pool = this.#onionCandidatePool(nodeD);
7024
+ if (pool.length < 2)
7025
+ return undefined;
7026
+ const i1 = pathOffset % pool.length;
7027
+ const i2 = (pathOffset + 1) % pool.length;
7028
+ if (i1 === i2)
7029
+ return undefined;
7030
+ const hop = (x) => ({
7031
+ node: x.node, host: x.node.host, port: x.node.port, publicKey: x.publicKey
7032
+ });
7033
+ return { nodeB: hop(pool[i1]), nodeC: hop(pool[i2]) };
7034
+ }
7035
+ #selectOnionPath(nodeD, pathOffset = 0) {
7036
+ const pool = this.#onionCandidatePool(nodeD);
7037
+ if (pool.length < 3) {
7038
+ return undefined;
7039
+ }
6969
7040
  const count = pool.length;
6970
7041
  const i0 = pathOffset % count;
6971
7042
  const i1 = (pathOffset + 1) % count;
@@ -100,6 +100,10 @@ export type PeerOptions = {
100
100
  * of it never runs: self-announce (so nobody can find this peer) and the
101
101
  * express pull loop (so offline messages are never collected). */
102
102
  tcpOnlyBootstrap?: boolean;
103
+ /** Turn on peer debug logging without env vars — the only route in from a
104
+ * browser, where process.env does not exist. */
105
+ debug?: boolean;
106
+ debugVerbose?: boolean;
103
107
  compatibilityMode?: CompatibilityMode;
104
108
  debugLabel?: string;
105
109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.143",
3
+ "version": "0.1.145",
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",