@decentnetwork/peer 0.1.144 → 0.1.146

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.
@@ -12,6 +12,7 @@ export declare class LegacyExpressClient {
12
12
  selfUserId: string;
13
13
  selfAddress: string;
14
14
  callbacks: PullCallbacks;
15
+ debug?: boolean;
15
16
  });
16
17
  hasNodes(): boolean;
17
18
  sendOfflineFriendRequest(address: string, hello: Uint8Array): Promise<void>;
@@ -12,8 +12,13 @@ export class LegacyExpressClient {
12
12
  #selfAddress;
13
13
  #currNode = 0;
14
14
  #callbacks;
15
+ // Own gate, and env-only until now — so in a browser this client was mute
16
+ // whatever the peer's debug setting was. That is why a stalled pull looked
17
+ // identical to a pull that never ran.
15
18
  #debug = process.env.DECENT_DEBUG === "1";
16
19
  constructor(opts) {
20
+ if (opts.debug)
21
+ this.#debug = true;
17
22
  this.#selfKeyPair = opts.selfKeyPair;
18
23
  this.#selfUserId = opts.selfUserId;
19
24
  this.#selfAddress = opts.selfAddress;
@@ -63,8 +68,12 @@ export class LegacyExpressClient {
63
68
  try {
64
69
  body = await this.#http(node, "GET", encodeURIComponent(this.#selfUserId));
65
70
  }
66
- catch {
67
- continue; // relay unreachable right now try the next
71
+ catch (error) {
72
+ // Say so. Swallowing this made an unreachable relay indistinguishable
73
+ // from an empty inbox, and offline mail piled up on the server with
74
+ // nothing anywhere reporting why.
75
+ this.#debugLog(`pull from ${node.host}:${node.port} failed: ${error?.message ?? error}`);
76
+ continue;
68
77
  }
69
78
  const messages = parseExpressResponseFrames(body);
70
79
  if (messages.length === 0) {
package/dist/peer.js CHANGED
@@ -598,6 +598,7 @@ export class Peer {
598
598
  selfKeyPair: this.#keyPair,
599
599
  selfUserId: this.userid(),
600
600
  selfAddress: this.address(),
601
+ debug: this.#debug,
601
602
  callbacks: {
602
603
  onOfflineFriendRequest: (fromUserId, packet) => {
603
604
  this.#emitOfflineFriendRequest(fromUserId, packet);
@@ -6873,6 +6874,41 @@ export class Peer {
6873
6874
  async #sendThroughOnionPath(payloadForNodeD, nodeD, pathOffset = 0, forcedPath) {
6874
6875
  const path = forcedPath === "direct" ? undefined : forcedPath ?? this.#selectOnionPath(nodeD, pathOffset);
6875
6876
  if (!path) {
6877
+ // No 3-hop UDP path. "Direct" then meant a plain UDP send — which on a
6878
+ // host with no UDP (a browser, where dgram is a socket that drops) goes
6879
+ // straight into the void, silently, forever. Every announce and every
6880
+ // friend-request lookup died here.
6881
+ //
6882
+ // A RELAYED onion request needs only two hops: the relay is node A. So
6883
+ // try that before giving up, and only fall through to the UDP send when
6884
+ // there are no relays or not even two usable hops.
6885
+ if (this.#tcpRelays && this.#tcpRelays.connectedCount() > 0) {
6886
+ const hops = this.#selectTcpOnionHops(nodeD, pathOffset);
6887
+ if (hops) {
6888
+ try {
6889
+ const tcpPacket = createOnionRequest0Tcp({
6890
+ nodeBHost: hops.nodeB.host,
6891
+ nodeBPort: hops.nodeB.port,
6892
+ nodeBPublicKey: hops.nodeB.publicKey,
6893
+ nodeCHost: hops.nodeC.host,
6894
+ nodeCPort: hops.nodeC.port,
6895
+ nodeCPublicKey: hops.nodeC.publicKey,
6896
+ nodeDHost: nodeD.host,
6897
+ nodeDPort: nodeD.port,
6898
+ payloadForNodeD
6899
+ });
6900
+ const sent = this.#tcpRelays.sendOnionRequest(tcpPacket);
6901
+ if (sent > 0) {
6902
+ this.#diagTcpOnionSent += 1;
6903
+ 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})`);
6904
+ return "direct";
6905
+ }
6906
+ }
6907
+ catch {
6908
+ /* malformed hop key — fall through to the UDP attempt */
6909
+ }
6910
+ }
6911
+ }
6876
6912
  this.#debugLog(`no onion path for ${nodeD.host}:${nodeD.port}, sending direct`);
6877
6913
  await this.#sendPacket(payloadForNodeD, nodeD);
6878
6914
  return "direct";
@@ -6923,7 +6959,10 @@ export class Peer {
6923
6959
  }
6924
6960
  return path;
6925
6961
  }
6926
- #selectOnionPath(nodeD, pathOffset = 0) {
6962
+ /** Ranked hop candidates for a path to nodeD. Shared by the 3-hop UDP
6963
+ * selector and the 2-hop TCP one — a relay stands in for node A, so a
6964
+ * relayed path needs one fewer usable node than a direct one. */
6965
+ #onionCandidatePool(nodeD) {
6927
6966
  const nodeDId = `${nodeD.host}:${nodeD.port}`;
6928
6967
  const candidates = [];
6929
6968
  const bootstrapIds = new Set(this.#opts.bootstrapNodes.map((node) => `${node.host}:${node.port}`));
@@ -6972,13 +7011,33 @@ export class Peer {
6972
7011
  // Ignore bad keys.
6973
7012
  }
6974
7013
  }
6975
- if (candidates.length < 3) {
6976
- return undefined;
6977
- }
6978
7014
  candidates.sort((a, b) => b.score - a.score);
6979
7015
  const preferredRelays = candidates.filter((item) => isLikelyStableRelayPort(item.node.port));
6980
7016
  const healthy = preferredRelays.filter((item) => item.score > -2);
6981
- const pool = healthy.length >= 3 ? healthy : preferredRelays.length >= 3 ? preferredRelays : candidates;
7017
+ return healthy.length >= 3 ? healthy : preferredRelays.length >= 3 ? preferredRelays : candidates;
7018
+ }
7019
+ /** Two hops (B and C) for a relayed onion request. The relay is node A, so
7020
+ * this succeeds on node sets too thin for a direct 3-hop path — which is
7021
+ * the browser's normal state, and where every request used to fall through
7022
+ * to a UDP send that goes nowhere. */
7023
+ #selectTcpOnionHops(nodeD, pathOffset = 0) {
7024
+ const pool = this.#onionCandidatePool(nodeD);
7025
+ if (pool.length < 2)
7026
+ return undefined;
7027
+ const i1 = pathOffset % pool.length;
7028
+ const i2 = (pathOffset + 1) % pool.length;
7029
+ if (i1 === i2)
7030
+ return undefined;
7031
+ const hop = (x) => ({
7032
+ node: x.node, host: x.node.host, port: x.node.port, publicKey: x.publicKey
7033
+ });
7034
+ return { nodeB: hop(pool[i1]), nodeC: hop(pool[i2]) };
7035
+ }
7036
+ #selectOnionPath(nodeD, pathOffset = 0) {
7037
+ const pool = this.#onionCandidatePool(nodeD);
7038
+ if (pool.length < 3) {
7039
+ return undefined;
7040
+ }
6982
7041
  const count = pool.length;
6983
7042
  const i0 = pathOffset % count;
6984
7043
  const i1 = (pathOffset + 1) % count;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.144",
3
+ "version": "0.1.146",
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",