@decentnetwork/peer 0.1.47 → 0.1.49

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 -13
  2. package/package.json +1 -1
package/dist/peer.js CHANGED
@@ -9,7 +9,7 @@ import { PACKET_TYPE_MESSAGE, PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_USERINFO,
9
9
  import { NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID, createOnionAnnounceRequest, createOnionDataPacket, createOnionDataRequest, createOnionRequest0, openOnionAnnounceResponse, openOnionDataPacket, openOnionDataResponse } from "./compat/tox-onion.js";
10
10
  import { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
11
11
  import { NET_PACKET_PING_REQUEST, NET_PACKET_PING_RESPONSE, NET_PACKET_GET_NODES, NET_PACKET_SEND_NODES, encodeDhtRpc, decodeDhtRpc, buildPingPlain, parsePingPlain, buildGetNodesPlain, parseGetNodesPlain, buildSendNodesPlain, parseSendNodesNodeBytes, packUdpNodeV4 } from "./compat/dht-rpc.js";
12
- import { FileTransferManager } from "./compat/filetransfer.js";
12
+ import { FileTransferManager, PACKET_ID_FILE_SENDREQUEST, PACKET_ID_FILE_CONTROL, PACKET_ID_FILE_DATA } from "./compat/filetransfer.js";
13
13
  import { NET_PACKET_CRYPTO_DATA, NET_PACKET_CRYPTO_HS, NET_PACKET_COOKIE_REQUEST, NET_PACKET_COOKIE_RESPONSE, createCookieRequest, createCookieResponse, createCryptoDataPacket, createCryptoHandshake, openCookieRequest, openCookieResponse, openCryptoDataPacket, openCryptoHandshake, incrementNonce } from "./compat/net-crypto.js";
14
14
  import { LegacyExpressClient } from "./compat/express.js";
15
15
  import { TcpRelayPool } from "./compat/tcp-relay-pool.js";
@@ -1551,14 +1551,26 @@ export class Peer {
1551
1551
  if (!opened) {
1552
1552
  continue;
1553
1553
  }
1554
- // De-duplicate. The dual-send path delivers each lossless packet over
1555
- // BOTH the UDP and relay transports with the SAME packet number, so
1556
- // without this guard every chat message (and every file-data chunk) is
1557
- // dispatched — and stored / shown / appended — twice. Drop anything at or
1558
- // below the receive low-water mark, like toxcore net_crypto drops packets
1559
- // below buffer_start. Must run BEFORE the nonce advance below so a
1560
- // duplicate doesn't desync the receive nonce.
1561
- if (opened.packetNumber < (state.receiveBufferStart ?? 0)) {
1554
+ // De-duplicate — but ONLY for dual-sent channels. The dual-send path
1555
+ // delivers a lossless packet over BOTH UDP and relay with the SAME
1556
+ // packet number, so without this guard a chat message is dispatched
1557
+ // (stored / shown) twice. Drop anything at or below the receive
1558
+ // low-water mark, like toxcore net_crypto drops below buffer_start.
1559
+ //
1560
+ // EXCEPT single-transport channels (bulk IP = bulkDataPacketId, and
1561
+ // file transfer 80-82): they are NOT dual-sent, so they never produce
1562
+ // duplicates — and the low-water mark would then only drop a
1563
+ // legitimately REORDERED packet, which over a high-rate stream (CCTV
1564
+ // over a long-haul China↔US path that reorders) is pure added loss /
1565
+ // jitter. IP forwarding is order-independent (the TUN/TCP above
1566
+ // reassembles), so deliver every packet. Must run BEFORE the nonce
1567
+ // advance so a real duplicate doesn't desync the receive nonce.
1568
+ const innerKind = opened.payload.length > 0 ? opened.payload[0] : -1;
1569
+ const isSingleTransportKind = (this.#opts.bulkDataPacketId !== undefined && innerKind === this.#opts.bulkDataPacketId) ||
1570
+ innerKind === PACKET_ID_FILE_SENDREQUEST ||
1571
+ innerKind === PACKET_ID_FILE_CONTROL ||
1572
+ innerKind === PACKET_ID_FILE_DATA;
1573
+ if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
1562
1574
  continue;
1563
1575
  }
1564
1576
  state.peerBaseNonce[state.peerBaseNonce.length - 2] = packet[1];
@@ -2785,8 +2797,23 @@ export class Peer {
2785
2797
  // e.g. decentlan IP=163) stays bulk / single-transport to avoid the relay
2786
2798
  // backlog + duplicate fan-out that high packet rates cause. Chat is sparse
2787
2799
  // and net_crypto dedups by packet number, so dual-send is safe here.
2788
- const isBulkData = this.#opts.bulkDataPacketId !== undefined && kind === this.#opts.bulkDataPacketId;
2789
- await this.#sendToFriend(friendId, encrypted, session, isBulkData);
2800
+ // File transfer (80 sendrequest / 81 control / 82 data) is ALSO treated as
2801
+ // bulk single-transport. It is high-volume AND order-sensitive: the
2802
+ // receiver reassembles chunks SEQUENTIALLY (position is implicit) and there
2803
+ // is no retransmit queue. Dual-sending it over UDP + relay (two paths with
2804
+ // different latency) reorders the chunks, and the receive-side low-water
2805
+ // dedup then drops the late copies — so a large file (mp4) gets gaps /
2806
+ // wrong order and never completes, while a tiny one (png, 1-2 chunks)
2807
+ // happens to finish before reordering bites. Pinning file transfer to a
2808
+ // single transport keeps the chunks in send order. (Reliability across a
2809
+ // lossy path still wants a reorder buffer + retransmit — tracked separately.)
2810
+ const isFileTransfer = kind === PACKET_ID_FILE_SENDREQUEST || kind === PACKET_ID_FILE_CONTROL || kind === PACKET_ID_FILE_DATA;
2811
+ const isBulkData = (this.#opts.bulkDataPacketId !== undefined && kind === this.#opts.bulkDataPacketId) || isFileTransfer;
2812
+ // File transfer rides the relay RELIABLY (never dropped under backpressure):
2813
+ // a dropped IP/CCTV packet is fine (the stream moves on), but a dropped file
2814
+ // chunk corrupts the file (no retransmit). So bulk IP data stays droppable
2815
+ // while file transfer is queued.
2816
+ await this.#sendToFriend(friendId, encrypted, session, isBulkData, isFileTransfer);
2790
2817
  }
2791
2818
  /**
2792
2819
  * Send `packet` to `friendId` over whichever transport(s) are
@@ -2796,7 +2823,7 @@ export class Peer {
2796
2823
  * receiving end (toxcore does the same when both transports are up).
2797
2824
  * Throws only if zero transports succeeded.
2798
2825
  */
2799
- async #sendToFriend(friendId, packet, session, isBulkData = false) {
2826
+ async #sendToFriend(friendId, packet, session, isBulkData = false, reliableOnRelay = false) {
2800
2827
  const s = session ?? this.#friendSessions.get(friendId);
2801
2828
  let udpOk = false;
2802
2829
  let tcpOk = false;
@@ -2876,7 +2903,9 @@ export class Peer {
2876
2903
  // Bulk IP data is droppable over the relay: under backpressure the
2877
2904
  // relay client drops it (bounded queue) rather than buffering seconds
2878
2905
  // of packets. Control frames pass isBulkData=false and always queue.
2879
- const sent = this.#tcpRelays.sendToFriend(tcpKey, packet, isBulkData);
2906
+ // File transfer is bulk (single-transport routing) but must NOT be
2907
+ // dropped — a lost chunk corrupts the file — so it is queued reliably.
2908
+ const sent = this.#tcpRelays.sendToFriend(tcpKey, packet, isBulkData && !reliableOnRelay);
2880
2909
  if (sent > 0) {
2881
2910
  tcpOk = true;
2882
2911
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
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",