@decentnetwork/peer 0.1.88 → 0.1.90
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.
- package/dist/peer.d.ts +1 -0
- package/dist/peer.js +64 -24
- package/package.json +2 -1
package/dist/peer.d.ts
CHANGED
|
@@ -131,6 +131,7 @@ export declare class Peer {
|
|
|
131
131
|
sendInvite(pubkey: string, data: Uint8Array | string, opts?: {
|
|
132
132
|
ext?: string | null;
|
|
133
133
|
bundle?: string;
|
|
134
|
+
establishTimeoutMs?: number;
|
|
134
135
|
}): Promise<bigint>;
|
|
135
136
|
/**
|
|
136
137
|
* Send an application-defined custom packet to a friend. `id` selects the
|
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.
|
|
177
|
+
const PEER_PKG_VERSION = "0.1.90";
|
|
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
|
|
@@ -1217,10 +1217,14 @@ export class Peer {
|
|
|
1217
1217
|
// extension the native WebRTC SDK registers under.
|
|
1218
1218
|
const ext = opts.ext === null ? undefined : (opts.ext ?? CARRIER_EXTENSION_NAME);
|
|
1219
1219
|
const bundle = opts.bundle;
|
|
1220
|
+
// How long to wait for a session to (re)establish before giving up. A caller
|
|
1221
|
+
// that wants to FAIL FAST (e.g. to fall back to an offline transport instead
|
|
1222
|
+
// of blocking the UI ~5s per signal) passes establishTimeoutMs: 0.
|
|
1223
|
+
const establishTimeoutMs = opts.establishTimeoutMs ?? 5000;
|
|
1220
1224
|
let session = this.#friendSessions.get(pubkey);
|
|
1221
|
-
if (!session?.established) {
|
|
1225
|
+
if (!session?.established && establishTimeoutMs > 0) {
|
|
1222
1226
|
await this.#initiateSession(pubkey).catch(() => { });
|
|
1223
|
-
const established = await this.#waitForFriendConnected(pubkey,
|
|
1227
|
+
const established = await this.#waitForFriendConnected(pubkey, establishTimeoutMs).catch(() => false);
|
|
1224
1228
|
if (established)
|
|
1225
1229
|
session = this.#friendSessions.get(pubkey);
|
|
1226
1230
|
}
|
|
@@ -2028,8 +2032,25 @@ export class Peer {
|
|
|
2028
2032
|
// De-duplicate: skip the DISPATCH + nonce advance for an already-seen
|
|
2029
2033
|
// packet number. The transport confirmation above already ran, so a
|
|
2030
2034
|
// duplicate still keeps the path fresh.
|
|
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
|
|
2038
|
+
// 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.
|
|
2031
2044
|
if (!isSingleTransportKind && opened.packetNumber < (state.receiveBufferStart ?? 0)) {
|
|
2032
|
-
|
|
2045
|
+
let isBulk = false;
|
|
2046
|
+
if (innerKind === PACKET_ID_MESSAGE) {
|
|
2047
|
+
try {
|
|
2048
|
+
isBulk = decodeCarrierPacket(opened.payload.slice(1)).type === PACKET_TYPE_BULKMSG;
|
|
2049
|
+
}
|
|
2050
|
+
catch { /* not a carrier packet */ }
|
|
2051
|
+
}
|
|
2052
|
+
if (!isBulk)
|
|
2053
|
+
continue;
|
|
2033
2054
|
}
|
|
2034
2055
|
// Track the receive nonce from the EXACT nonce this packet used
|
|
2035
2056
|
// (openCryptoDataPacket reconstructs it with full carry/borrow).
|
|
@@ -2074,7 +2095,7 @@ export class Peer {
|
|
|
2074
2095
|
if (consumesReliableNumber) {
|
|
2075
2096
|
state.receiveBufferStart = Math.max(state.receiveBufferStart ?? 0, (opened.packetNumber + 1) >>> 0);
|
|
2076
2097
|
}
|
|
2077
|
-
this.#deliverLosslessPayload(friendId, state, kind, inner, remote);
|
|
2098
|
+
this.#deliverLosslessPayload(friendId, state, kind, inner, remote, opened.packetNumber);
|
|
2078
2099
|
return;
|
|
2079
2100
|
}
|
|
2080
2101
|
// Reverted: we used to DELETE a "desynced" session here to force a clean
|
|
@@ -3170,7 +3191,7 @@ export class Peer {
|
|
|
3170
3191
|
* out of the datagram handler so the receive reorder buffer can replay
|
|
3171
3192
|
* buffered packets through the exact same dispatch. `kind` is the first
|
|
3172
3193
|
* payload byte (never undefined here — the caller defaults it). */
|
|
3173
|
-
#deliverLosslessPayload(friendId, state, kind, inner, remote) {
|
|
3194
|
+
#deliverLosslessPayload(friendId, state, kind, inner, remote, packetNumber = 0) {
|
|
3174
3195
|
// Toxcore file transfer (FILE_SENDREQUEST/CONTROL/DATA = 80/81/82).
|
|
3175
3196
|
if (this.#fileTransfer.handlePacket(friendId, kind, inner))
|
|
3176
3197
|
return;
|
|
@@ -3353,7 +3374,7 @@ export class Peer {
|
|
|
3353
3374
|
return;
|
|
3354
3375
|
}
|
|
3355
3376
|
if (carrier?.type === PACKET_TYPE_BULKMSG) {
|
|
3356
|
-
const complete = this.#assembleBulkMsg(friendId, carrier);
|
|
3377
|
+
const complete = this.#assembleBulkMsg(friendId, carrier, packetNumber);
|
|
3357
3378
|
if (!complete)
|
|
3358
3379
|
return; // more fragments pending
|
|
3359
3380
|
text = decodeUtf8Best(complete);
|
|
@@ -3483,11 +3504,19 @@ export class Peer {
|
|
|
3483
3504
|
incrementNonce(state.ourBaseNonce);
|
|
3484
3505
|
await this.#sendToFriend(friendId, encrypted, state, false, false);
|
|
3485
3506
|
}
|
|
3486
|
-
/** Append a Carrier BULKMSG fragment
|
|
3487
|
-
*
|
|
3488
|
-
*
|
|
3489
|
-
*
|
|
3490
|
-
|
|
3507
|
+
/** Append a Carrier BULKMSG fragment and return the full reassembled message
|
|
3508
|
+
* once every fragment has landed (undefined while pending).
|
|
3509
|
+
*
|
|
3510
|
+
* The C SDK's send_bulk_message puts totalsz on the FIRST fragment (index 1)
|
|
3511
|
+
* and sends the rest with totalsz 0, back-to-back with ascending reliable
|
|
3512
|
+
* packet numbers. Over a dual/UDP path those fragments can arrive REORDERED —
|
|
3513
|
+
* including the first (totalsz-bearing) one landing after later fragments. So
|
|
3514
|
+
* we key fragments by their PACKET NUMBER (dedup + order), record totalsz
|
|
3515
|
+
* whenever the first fragment shows up (in any order), and complete once we
|
|
3516
|
+
* hold every byte — then concatenate in packet-number order. This tolerates
|
|
3517
|
+
* reordering + duplicates WITHOUT stalling the reliable stream (a genuinely
|
|
3518
|
+
* lost fragment just expires this one message after 60s, nothing else). */
|
|
3519
|
+
#assembleBulkMsg(friendId, frag, packetNumber) {
|
|
3491
3520
|
const now = Date.now();
|
|
3492
3521
|
// Lazy expiry sweep (C uses a 60s assembly timeout).
|
|
3493
3522
|
for (const [key, entry] of this.#bulkAssembly) {
|
|
@@ -3497,24 +3526,35 @@ export class Peer {
|
|
|
3497
3526
|
const key = `${friendId}:${frag.tid.toString()}`;
|
|
3498
3527
|
let entry = this.#bulkAssembly.get(key);
|
|
3499
3528
|
if (!entry) {
|
|
3500
|
-
|
|
3501
|
-
|
|
3529
|
+
entry = { total: 0, frags: new Map(), got: 0, expireAtMs: now + 60_000 };
|
|
3530
|
+
this.#bulkAssembly.set(key, entry);
|
|
3531
|
+
}
|
|
3532
|
+
// The first fragment carries totalsz; it may arrive out of order.
|
|
3533
|
+
if (frag.totalsz > 0) {
|
|
3534
|
+
if (frag.totalsz > CARRIER_MAX_APP_BULKMSG_LEN) {
|
|
3535
|
+
this.#debugLog(`bulkmsg from ${friendId} totalsz ${frag.totalsz} exceeds cap — dropped`);
|
|
3536
|
+
this.#bulkAssembly.delete(key);
|
|
3502
3537
|
return undefined;
|
|
3503
3538
|
}
|
|
3504
|
-
entry =
|
|
3505
|
-
this.#bulkAssembly.set(key, entry);
|
|
3539
|
+
entry.total = frag.totalsz;
|
|
3506
3540
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3541
|
+
// Store by packet number; a duplicate (dual-send) fragment is ignored.
|
|
3542
|
+
if (frag.data.length > 0 && !entry.frags.has(packetNumber)) {
|
|
3543
|
+
entry.frags.set(packetNumber, frag.data);
|
|
3544
|
+
entry.got += frag.data.length;
|
|
3511
3545
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
if (entry.got < entry.total)
|
|
3546
|
+
// Complete only once the totalsz-bearing fragment has arrived AND we hold
|
|
3547
|
+
// every byte. Until then, keep buffering.
|
|
3548
|
+
if (entry.total === 0 || entry.got < entry.total)
|
|
3515
3549
|
return undefined;
|
|
3516
3550
|
this.#bulkAssembly.delete(key);
|
|
3517
|
-
|
|
3551
|
+
if (entry.got > entry.total) {
|
|
3552
|
+
this.#debugLog(`bulkmsg from ${friendId} got ${entry.got} > total ${entry.total} — dropped`);
|
|
3553
|
+
return undefined;
|
|
3554
|
+
}
|
|
3555
|
+
// Concatenate in ascending packet-number order (the fragments' send order).
|
|
3556
|
+
const nums = [...entry.frags.keys()].sort((a, b) => a - b);
|
|
3557
|
+
return concatBytes(nums.map((n) => entry.frags.get(n)));
|
|
3518
3558
|
}
|
|
3519
3559
|
/** Append a Carrier invite fragment (INVITE_REQUEST/RESPONSE); returns the
|
|
3520
3560
|
* full reassembled payload when the last fragment lands, undefined while
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
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",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"test:compat": "pnpm run build && node scripts/compat-selftest.mjs",
|
|
70
70
|
"test:invite": "pnpm run build && node scripts/invite-selftest.mjs",
|
|
71
71
|
"test:reorder": "pnpm run build && node scripts/reorder-selftest.mjs",
|
|
72
|
+
"test:bulkmsg": "pnpm run build && node scripts/bulkmsg-selftest.mjs",
|
|
72
73
|
"test:userinfo": "pnpm run build && node scripts/userinfo-selftest.mjs",
|
|
73
74
|
"test:tcp-onion": "pnpm run build && node scripts/tcp-onion-selftest.mjs",
|
|
74
75
|
"smoke:join": "node scripts/smoke-join.mjs",
|