@decentnetwork/peer 0.1.90 → 0.1.91

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 +42 -33
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -174,7 +174,7 @@ const AGENTNET_PROTO_VERSION = 1;
174
174
  // Peer package version, advertised as the default appVersion when the embedder
175
175
  // doesn't override it. Read lazily so a bundler that inlines this file doesn't
176
176
  // need the package.json at runtime.
177
- const PEER_PKG_VERSION = "0.1.90";
177
+ const PEER_PKG_VERSION = "0.1.91";
178
178
  // Toxcore Messenger.h packet IDs (live inside encrypted 0x1b crypto data plain payload)
179
179
  const PACKET_ID_PADDING = 0;
180
180
  const PACKET_ID_REQUEST = 1; // request retransmission of unreceived packets
@@ -2033,23 +2033,25 @@ export class Peer {
2033
2033
  // packet number. The transport confirmation above already ran, so a
2034
2034
  // duplicate still keeps the path fresh.
2035
2035
  //
2036
- // EXCEPTION: a BULKMSG fragment (inline file from a native peer) can
2037
- // arrive REORDERED the C SDK puts totalsz on the FIRST fragment, but
2036
+ // EXCEPTION: multi-fragment reliable messages that reassemble by tid
2037
+ // BULKMSG (inline files) and INVITE_REQUEST/RESPONSE (WebRTC call SDPs)
2038
+ // can arrive REORDERED. The C SDK puts totalsz on the FIRST fragment, but
2038
2039
  // over a dual/UDP path a later fragment can land first, advancing the
2039
- // low-water past the first fragment's number. Dropping the first
2040
- // fragment here makes every fragment read totalsz 0 → the whole file is
2041
- // lost (the classic "bulkmsg totalsz 0 dropped"). Let bulkmsg through;
2042
- // #assembleBulkMsg dedups AND orders fragments by packet number, so a
2043
- // reordered/duplicate fragment is handled correctly there instead.
2040
+ // low-water past the first fragment's number. Dropping the first fragment
2041
+ // here makes every fragment read totalsz 0 → the message is lost or (for
2042
+ // an SDP) scrambled, so a call gets stuck "connecting" and files vanish
2043
+ // ("bulkmsg totalsz 0 dropped"). Let those through; #assembleBulkMsg /
2044
+ // #assembleInvite dedup AND order fragments by packet number.
2044
2045
  if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
2045
- let isBulk = false;
2046
+ let reorderable = false;
2046
2047
  if (innerKind === PACKET_ID_MESSAGE) {
2047
2048
  try {
2048
- isBulk = decodeCarrierPacket(opened.payload.slice(1)).type === PACKET_TYPE_BULKMSG;
2049
+ const t = decodeCarrierPacket(opened.payload.slice(1)).type;
2050
+ reorderable = t === PACKET_TYPE_BULKMSG || t === PACKET_TYPE_INVITE_REQUEST || t === PACKET_TYPE_INVITE_RESPONSE;
2049
2051
  }
2050
2052
  catch { /* not a carrier packet */ }
2051
2053
  }
2052
- if (!isBulk)
2054
+ if (!reorderable)
2053
2055
  continue;
2054
2056
  }
2055
2057
  // Track the receive nonce from the EXACT nonce this packet used
@@ -3346,7 +3348,7 @@ export class Peer {
3346
3348
  // signals (SDP offers) fragment by tid like bulkmsg; reassemble then
3347
3349
  // surface as an "invite" event (NOT chat text).
3348
3350
  if (carrier?.type === PACKET_TYPE_INVITE_REQUEST) {
3349
- const complete = this.#assembleInvite(friendId, carrier);
3351
+ const complete = this.#assembleInvite(friendId, carrier, packetNumber);
3350
3352
  if (!complete)
3351
3353
  return; // more fragments pending
3352
3354
  this.#events.emit("invite", {
@@ -3359,7 +3361,7 @@ export class Peer {
3359
3361
  return;
3360
3362
  }
3361
3363
  if (carrier?.type === PACKET_TYPE_INVITE_RESPONSE) {
3362
- const complete = carrier.status === 0 ? this.#assembleInvite(friendId, carrier) : new Uint8Array();
3364
+ const complete = carrier.status === 0 ? this.#assembleInvite(friendId, carrier, packetNumber) : new Uint8Array();
3363
3365
  if (carrier.status === 0 && !complete)
3364
3366
  return; // more fragments pending
3365
3367
  this.#events.emit("inviteResponse", {
@@ -3556,12 +3558,14 @@ export class Peer {
3556
3558
  const nums = [...entry.frags.keys()].sort((a, b) => a - b);
3557
3559
  return concatBytes(nums.map((n) => entry.frags.get(n)));
3558
3560
  }
3559
- /** Append a Carrier invite fragment (INVITE_REQUEST/RESPONSE); returns the
3560
- * full reassembled payload when the last fragment lands, undefined while
3561
- * pending. Mirrors #assembleBulkMsg but caps at CARRIER_MAX_INVITE_DATA_LEN
3562
- * (8192) and uses a separate buffer. Single-fragment invites (the common
3563
- * case for small WebRTC signals) complete on the first call. */
3564
- #assembleInvite(friendId, frag) {
3561
+ /** Append a Carrier invite fragment (INVITE_REQUEST/RESPONSE) and return the
3562
+ * full reassembled payload once every fragment has landed. Like
3563
+ * #assembleBulkMsg, fragments are keyed + ordered by PACKET NUMBER so a
3564
+ * reordered multi-fragment WebRTC SDP (offer/answer >1280 bytes) reassembles
3565
+ * correctly instead of scrambling a scrambled SDP left calls stuck
3566
+ * "connecting". Caps at CARRIER_MAX_INVITE_DATA_LEN (8192). Single-fragment
3567
+ * invites (the common case) complete on the first call. */
3568
+ #assembleInvite(friendId, frag, packetNumber) {
3565
3569
  const now = Date.now();
3566
3570
  for (const [key, entry] of this.#inviteAssembly) {
3567
3571
  if (entry.expireAtMs < now)
@@ -3570,26 +3574,31 @@ export class Peer {
3570
3574
  const key = `${friendId}:${frag.tid.toString()}`;
3571
3575
  let entry = this.#inviteAssembly.get(key);
3572
3576
  if (!entry) {
3573
- // First fragment carries totalsz. A lone fragment whose data already
3574
- // equals totalsz completes immediately below.
3575
- if (!frag.totalsz || frag.totalsz > CARRIER_MAX_INVITE_DATA_LEN) {
3576
- this.#debugLog(`invite from ${friendId} with invalid/missing totalsz ${frag.totalsz} dropped`);
3577
+ entry = { total: 0, frags: new Map(), got: 0, expireAtMs: now + 60_000 };
3578
+ this.#inviteAssembly.set(key, entry);
3579
+ }
3580
+ // The first fragment carries totalsz; it may arrive out of order.
3581
+ if (frag.totalsz > 0) {
3582
+ if (frag.totalsz > CARRIER_MAX_INVITE_DATA_LEN) {
3583
+ this.#debugLog(`invite from ${friendId} totalsz ${frag.totalsz} exceeds cap — dropped`);
3584
+ this.#inviteAssembly.delete(key);
3577
3585
  return undefined;
3578
3586
  }
3579
- entry = { total: frag.totalsz, chunks: [], got: 0, expireAtMs: now + 60_000 };
3580
- this.#inviteAssembly.set(key, entry);
3587
+ entry.total = frag.totalsz;
3581
3588
  }
3582
- if (frag.data.length === 0 || entry.got + frag.data.length > entry.total) {
3583
- this.#debugLog(`invite fragment from ${friendId} overflows totalsz — dropped`);
3584
- this.#inviteAssembly.delete(key);
3585
- return undefined;
3589
+ if (frag.data.length > 0 && !entry.frags.has(packetNumber)) {
3590
+ entry.frags.set(packetNumber, frag.data);
3591
+ entry.got += frag.data.length;
3586
3592
  }
3587
- entry.chunks.push(frag.data);
3588
- entry.got += frag.data.length;
3589
- if (entry.got < entry.total)
3593
+ if (entry.total === 0 || entry.got < entry.total)
3590
3594
  return undefined;
3591
3595
  this.#inviteAssembly.delete(key);
3592
- return concatBytes(entry.chunks);
3596
+ if (entry.got > entry.total) {
3597
+ this.#debugLog(`invite from ${friendId} got ${entry.got} > total ${entry.total} — dropped`);
3598
+ return undefined;
3599
+ }
3600
+ const nums = [...entry.frags.keys()].sort((a, b) => a - b);
3601
+ return concatBytes(nums.map((n) => entry.frags.get(n)));
3593
3602
  }
3594
3603
  /** iOS Beagle sends files inline as a JSON envelope over (bulk)messages:
3595
3604
  * {"data":"<base64>","fileExtension":".jpg","fileName":"…","type":"image"}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
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",