@decentnetwork/peer 0.1.23 → 0.1.25

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.
@@ -51,59 +51,71 @@ export class LegacyExpressClient {
51
51
  if (!this.#nodes.length) {
52
52
  return;
53
53
  }
54
- let lastTimestamp = 0;
55
- const { node, body } = await this.#withAnyNode(async (candidate) => ({
56
- node: candidate,
57
- body: await this.#http(candidate, "GET", encodeURIComponent(this.#selfUserId))
58
- }));
59
- const messages = parseExpressResponseFrames(body);
60
- if (messages.length > 0) {
61
- this.#debugLog(`pullOnce got ${messages.length} offline frame(s) from ${node.host}:${node.port}`);
62
- }
63
- let requestCount = 0;
64
- let messageCount = 0;
65
- for (const encrypted of messages) {
66
- const plain = decrypt(node.sharedKey, encrypted);
67
- if (!plain) {
68
- continue;
54
+ // Pull from EVERY configured relay, not just the first reachable one. A
55
+ // message lives only on the relay its sender posted it to; with a mixed
56
+ // fleet (some peers prefer relay A, others B — e.g. during a rollout that
57
+ // adds a dedicated relay) the recipient must check all relays or it
58
+ // silently misses anything posted elsewhere. Each relay re-encrypts with
59
+ // its own per-recipient key, so we decrypt/ack each one independently.
60
+ for (const node of this.#nodes) {
61
+ let body;
62
+ try {
63
+ body = await this.#http(node, "GET", encodeURIComponent(this.#selfUserId));
64
+ }
65
+ catch {
66
+ continue; // relay unreachable right now — try the next
69
67
  }
70
- const msg = decodePullMessage(plain);
71
- if (!msg) {
68
+ const messages = parseExpressResponseFrames(body);
69
+ if (messages.length === 0) {
72
70
  continue;
73
71
  }
74
- lastTimestamp = Math.max(lastTimestamp, msg.timestamp);
75
- if (msg.type === "R") {
76
- if (!msg.address || msg.address !== this.#selfAddress) {
77
- this.#debugLog(`drop offline request with unmatched address from ${msg.from}`);
72
+ this.#debugLog(`pullOnce got ${messages.length} offline frame(s) from ${node.host}:${node.port}`);
73
+ let lastTimestamp = 0;
74
+ let requestCount = 0;
75
+ let messageCount = 0;
76
+ for (const encrypted of messages) {
77
+ const plain = decrypt(node.sharedKey, encrypted);
78
+ if (!plain) {
78
79
  continue;
79
80
  }
80
- this.#callbacks.onOfflineFriendRequest(msg.from, msg.payload, msg.timestamp);
81
- requestCount += 1;
82
- this.#debugLog(`offline friend request from ${msg.from} ts=${msg.timestamp}`);
83
- }
84
- else if (msg.type === "M") {
85
- try {
86
- const friendPk = base58ToBytes(msg.from);
87
- if (friendPk.length !== 32) {
88
- continue;
89
- }
90
- const friendSharedKey = nacl.box.before(friendPk, this.#selfKeyPair.secretKey);
91
- const packet = decrypt(friendSharedKey, msg.payload);
92
- if (!packet) {
81
+ const msg = decodePullMessage(plain);
82
+ if (!msg) {
83
+ continue;
84
+ }
85
+ lastTimestamp = Math.max(lastTimestamp, msg.timestamp);
86
+ if (msg.type === "R") {
87
+ if (!msg.address || msg.address !== this.#selfAddress) {
88
+ this.#debugLog(`drop offline request with unmatched address from ${msg.from}`);
93
89
  continue;
94
90
  }
95
- this.#callbacks.onOfflineFriendMessage(msg.from, packet, msg.timestamp);
96
- messageCount += 1;
97
- this.#debugLog(`offline message from ${msg.from} ts=${msg.timestamp}`);
91
+ this.#callbacks.onOfflineFriendRequest(msg.from, msg.payload, msg.timestamp);
92
+ requestCount += 1;
93
+ this.#debugLog(`offline friend request from ${msg.from} ts=${msg.timestamp}`);
98
94
  }
99
- catch {
100
- // Skip invalid sender ids.
95
+ else if (msg.type === "M") {
96
+ try {
97
+ const friendPk = base58ToBytes(msg.from);
98
+ if (friendPk.length !== 32) {
99
+ continue;
100
+ }
101
+ const friendSharedKey = nacl.box.before(friendPk, this.#selfKeyPair.secretKey);
102
+ const packet = decrypt(friendSharedKey, msg.payload);
103
+ if (!packet) {
104
+ continue;
105
+ }
106
+ this.#callbacks.onOfflineFriendMessage(msg.from, packet, msg.timestamp);
107
+ messageCount += 1;
108
+ this.#debugLog(`offline message from ${msg.from} ts=${msg.timestamp}`);
109
+ }
110
+ catch {
111
+ // Skip invalid sender ids.
112
+ }
101
113
  }
102
114
  }
103
- }
104
- if (lastTimestamp > 0) {
105
- this.#debugLog(`pull processed: requests=${requestCount} messages=${messageCount}; ack until ts=${lastTimestamp}`);
106
- await this.#deleteUntil(lastTimestamp).catch(() => { });
115
+ if (lastTimestamp > 0) {
116
+ this.#debugLog(`pull processed from ${node.host}: requests=${requestCount} messages=${messageCount}; ack until ts=${lastTimestamp}`);
117
+ await this.#deleteUntilOn(node, lastTimestamp).catch(() => { });
118
+ }
107
119
  }
108
120
  }
109
121
  async #postEncrypted(to, plainData) {
@@ -113,14 +125,12 @@ export class LegacyExpressClient {
113
125
  await this.#http(node, "POST", path, encrypted);
114
126
  });
115
127
  }
116
- async #deleteUntil(timestamp) {
128
+ async #deleteUntilOn(node, timestamp) {
117
129
  const tsBytes = new TextEncoder().encode(String(timestamp));
118
- await this.#withAnyNode(async (node) => {
119
- const encrypted = encrypt(node.sharedKey, tsBytes);
120
- const encoded = bytesToBase58(encrypted);
121
- const path = `${encodeURIComponent(this.#selfUserId)}?until=${encodeURIComponent(encoded)}`;
122
- await this.#http(node, "DELETE", path);
123
- });
130
+ const encrypted = encrypt(node.sharedKey, tsBytes);
131
+ const encoded = bytesToBase58(encrypted);
132
+ const path = `${encodeURIComponent(this.#selfUserId)}?until=${encodeURIComponent(encoded)}`;
133
+ await this.#http(node, "DELETE", path);
124
134
  }
125
135
  async #withAnyNode(fn) {
126
136
  if (!this.#nodes.length) {
package/dist/peer.js CHANGED
@@ -792,6 +792,17 @@ export class Peer {
792
792
  this.#debugLog(`sendText: in-session send failed for ${pubkey}, falling back to express: ${emsg}`);
793
793
  }
794
794
  }
795
+ // Never queue an EMPTY message to express. Empty text is used purely as
796
+ // a connection-trigger (e.g. the dora server's 8s session-kick
797
+ // `sendText(target, "")`, which exists only to nudge #initiateSession on
798
+ // a live transport). Offline it carries no data, and queuing one every 8s
799
+ // for every offline friend floods the relay with junk — the observed
800
+ // 80+-message-per-peer piles that timed out every pull. A kick that finds
801
+ // no session is simply a no-op.
802
+ if (text.length === 0) {
803
+ this.#debugLog(`sendText: empty connection-kick to ${pubkey} with no live session — dropping (not flooding express)`);
804
+ return;
805
+ }
795
806
  // Offline / fallback path via Carrier express HTTP store-and-forward.
796
807
  if (this.#express?.hasNodes()) {
797
808
  await this.#express.sendOfflineText(pubkey, packet);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
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",