@decentnetwork/peer 0.1.156 → 0.1.157

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 +61 -2
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -527,6 +527,10 @@ export class Peer {
527
527
  // #initiateSession every few seconds for every friend; without this
528
528
  // dedupe, lower-pubkey side floods the log with "deferring to peer".
529
529
  #initiateSkipLogged = new Set();
530
+ /** Recently COMPLETED bulkmsg assemblies, same key, kept for the same 60s
531
+ * the assembly itself lives. Without it a retransmitted message reassembles
532
+ * a second time and is delivered twice. */
533
+ #bulkCompleted = new Map();
530
534
  // In-flight Carrier BULKMSG reassemblies, keyed `${friendId}:${tid}`.
531
535
  // Fragments are appended in arrival order (the lossless stream keeps
532
536
  // them ordered) and the message completes when totalsz bytes arrived.
@@ -1613,6 +1617,29 @@ export class Peer {
1613
1617
  return;
1614
1618
  }
1615
1619
  }
1620
+ // Inline files ride the TEXT channel, and the delivery envelope wraps them.
1621
+ //
1622
+ // The call sites check for an inline file BEFORE this function, i.e. before
1623
+ // the envelope comes off — so they were looking at "\x1eDNPACK1:<base64>",
1624
+ // which does not start with "{", and every inline file sent with delivery
1625
+ // tracking was reported as a text message instead. Measured 2026-08-31,
1626
+ // peer to peer: a 95-byte PNG arrived as 200 characters of JSON, and a
1627
+ // 300 KB one as 409,672 characters. Both transferred perfectly; both were
1628
+ // handed to the application as a wall of base64.
1629
+ //
1630
+ // The check belongs here, on the unwrapped text, where it sees what the
1631
+ // sender actually wrote. The call-site checks stay for envelope-less
1632
+ // senders (iOS and Android do not speak this envelope).
1633
+ //
1634
+ // ACK first: an inline file is delivered, and skipping the ACK because we
1635
+ // did not run the text handlers would make the sender retransmit forever.
1636
+ if (this.#tryEmitInlineFile(msg.pubkey, text, msg.via === "offline" ? "offline" : "online")) {
1637
+ if (deliveryId) {
1638
+ this.#rememberDeliveredTextId(deliveryId);
1639
+ void this.#sendTextAck(msg.pubkey, deliveryId).catch(() => undefined);
1640
+ }
1641
+ return;
1642
+ }
1616
1643
  if (this.#textHandlers.size === 0) {
1617
1644
  if (deliveryId)
1618
1645
  this.#debugLog(`text delivery ${deliveryId} from ${msg.pubkey} has no onText handlers; not ACKing`);
@@ -3179,13 +3206,22 @@ export class Peer {
3179
3206
  try {
3180
3207
  const decoded = decodeCarrierPacket(carrierPacket);
3181
3208
  if (decoded.type !== PACKET_TYPE_FRIEND_REQUEST) {
3209
+ // Was a bare `return`. An onion friend-request that arrived and did not
3210
+ // parse left NOTHING anywhere — the sender saw success, the recipient
3211
+ // saw no request, and there was no way to tell "never arrived" from
3212
+ // "arrived and was dropped". That is the single most-reported symptom
3213
+ // on this project and it was undiagnosable by construction.
3214
+ this.#debugLog(`friend-request from ${carrierIdFromPublicKey(senderPublicKey)} DROPPED: ` +
3215
+ `decoded as packet type ${decoded.type}, expected ${PACKET_TYPE_FRIEND_REQUEST}`);
3182
3216
  return;
3183
3217
  }
3184
3218
  hello = decoded.hello;
3185
3219
  name = decoded.name;
3186
3220
  description = decoded.descr;
3187
3221
  }
3188
- catch {
3222
+ catch (error) {
3223
+ this.#debugLog(`friend-request from ${carrierIdFromPublicKey(senderPublicKey)} DROPPED: ` +
3224
+ `undecodable (${carrierPacket.length} bytes): ${error.message}`);
3189
3225
  return;
3190
3226
  }
3191
3227
  const userid = carrierIdFromPublicKey(senderPublicKey);
@@ -3227,13 +3263,17 @@ export class Peer {
3227
3263
  try {
3228
3264
  const decoded = decodeCarrierPacket(packet);
3229
3265
  if (decoded.type !== PACKET_TYPE_FRIEND_REQUEST) {
3266
+ this.#debugLog(`offline friend-request from ${fromUserId} DROPPED: decoded as packet ` +
3267
+ `type ${decoded.type}, expected ${PACKET_TYPE_FRIEND_REQUEST}`);
3230
3268
  return;
3231
3269
  }
3232
3270
  helloText = decoded.hello;
3233
3271
  name = decoded.name;
3234
3272
  description = decoded.descr;
3235
3273
  }
3236
- catch {
3274
+ catch (error) {
3275
+ this.#debugLog(`offline friend-request from ${fromUserId} DROPPED: undecodable ` +
3276
+ `(${packet.length} bytes): ${error.message}`);
3237
3277
  return;
3238
3278
  }
3239
3279
  // Already an established friend? Ignore this (re-)request. Auto-friend
@@ -4763,6 +4803,24 @@ export class Peer {
4763
4803
  this.#bulkAssembly.delete(key);
4764
4804
  }
4765
4805
  const key = `${friendId}:${frag.tid.toString()}`;
4806
+ // A completed assembly is deleted, so a second copy of the same fragments
4807
+ // starts a fresh entry and completes AGAIN — the application gets the same
4808
+ // message twice. Duplicate FRAGMENTS were already ignored; a duplicate
4809
+ // whole MESSAGE was not.
4810
+ //
4811
+ // It shows up on the plain send path (a native peer, or a JS peer before
4812
+ // userinfo has settled), because that path carries no delivery id and the
4813
+ // text-level dedupe has nothing to key on. Measured 2026-08-31: one
4814
+ // sendInlineFile call, two `bulkmsg complete (409672 bytes)` and two
4815
+ // identical 300 KB images delivered.
4816
+ for (const [k, at] of this.#bulkCompleted) {
4817
+ if (at < now - 60_000)
4818
+ this.#bulkCompleted.delete(k);
4819
+ }
4820
+ if (this.#bulkCompleted.has(key)) {
4821
+ this.#debugLog(`bulkmsg ${key} already completed — dropping duplicate`);
4822
+ return undefined;
4823
+ }
4766
4824
  let entry = this.#bulkAssembly.get(key);
4767
4825
  if (!entry) {
4768
4826
  entry = { total: 0, frags: new Map(), got: 0, expireAtMs: now + 60_000 };
@@ -4787,6 +4845,7 @@ export class Peer {
4787
4845
  if (entry.total === 0 || entry.got < entry.total)
4788
4846
  return undefined;
4789
4847
  this.#bulkAssembly.delete(key);
4848
+ this.#bulkCompleted.set(key, now);
4790
4849
  if (entry.got > entry.total) {
4791
4850
  this.#debugLog(`bulkmsg from ${friendId} got ${entry.got} > total ${entry.total} — dropped`);
4792
4851
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.156",
3
+ "version": "0.1.157",
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",