@decentnetwork/peer 0.1.139 → 0.1.140
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/index.d.ts +1 -1
- package/dist/peer.d.ts +11 -2
- package/dist/peer.js +80 -4
- package/dist/types/peer.d.ts +17 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,4 +14,4 @@ export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
|
|
14
14
|
export type { CarrierPacket, FriendMessagePacket, FriendRequestPacket, InviteReqPacket, InviteRspPacket } from "./compat/packet.js";
|
|
15
15
|
export type { ToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
16
16
|
export type { CarrierAddressParts } from "./compat/address.js";
|
|
17
|
-
export type { CompatibilityMode, CustomPacketEvent, FriendConnectionEvent, FriendConnectionStatus, FriendInfoEvent, FriendRequest, InlineFileEvent, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, SendTextUntilAckOptions, TextMessage } from "./types/peer.js";
|
|
17
|
+
export type { CompatibilityMode, CustomPacketEvent, FriendConnectionEvent, FriendConnectionStatus, FriendInfoEvent, FriendRequest, InlineFileEvent, InlineSendResult, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, SendTextUntilAckOptions, TextMessage } from "./types/peer.js";
|
package/dist/peer.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type BootstrapResult } from "./compat/bootstrap.js";
|
|
2
2
|
import type { FriendRecord } from "./store/friends.js";
|
|
3
3
|
import { type CarrierTurnServerInfo, type RtcIceServer } from "./ice-servers.js";
|
|
4
|
-
import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, InlineFileEvent, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, SendTextUntilAckOptions, TextMessage } from "./types/peer.js";
|
|
4
|
+
import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, InlineFileEvent, InlineSendResult, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, SendTextUntilAckOptions, TextMessage } from "./types/peer.js";
|
|
5
5
|
export declare class Peer {
|
|
6
6
|
#private;
|
|
7
7
|
private constructor();
|
|
@@ -140,7 +140,16 @@ export declare class Peer {
|
|
|
140
140
|
name: string;
|
|
141
141
|
data: Uint8Array;
|
|
142
142
|
fileType?: "image" | "audio" | "text" | "unknown";
|
|
143
|
-
|
|
143
|
+
deliveryTimeoutMs?: number;
|
|
144
|
+
}): Promise<InlineSendResult>;
|
|
145
|
+
/**
|
|
146
|
+
* True when this friend is a NATIVE Carrier client (iOS/Android/C SDK): its
|
|
147
|
+
* DHT/relay key differs from its identity key, where a JS peer announces its
|
|
148
|
+
* identity key as its DHT key. Natives cannot see the toxcore file-transfer
|
|
149
|
+
* protocol (sendFile) — the inline envelope is their only file path — and
|
|
150
|
+
* they never speak the JS text-ACK scheme.
|
|
151
|
+
*/
|
|
152
|
+
isNativeFriend(pubkey: string): boolean;
|
|
144
153
|
/** Fired when a peer sends a Carrier friend-invite (PACKET_TYPE_INVITE_REQUEST).
|
|
145
154
|
* This is the channel the iOS/Android WebRTC SDK uses for call signaling —
|
|
146
155
|
* each RtcSignal arrives as an "invite" with ext="carrier" and `data` the
|
package/dist/peer.js
CHANGED
|
@@ -1240,6 +1240,9 @@ export class Peer {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
await this.#sendTextPlain(pubkey, text);
|
|
1242
1242
|
}
|
|
1243
|
+
/** What #sendTextPlain actually did with the message — callers that promise
|
|
1244
|
+
* delivery semantics (inline files) need to know the path and, for the live
|
|
1245
|
+
* path, where the send window stood after the last fragment. */
|
|
1243
1246
|
async #sendTextPlain(pubkey, text) {
|
|
1244
1247
|
const friend = this.#friends.get(pubkey);
|
|
1245
1248
|
if (!friend) {
|
|
@@ -1307,7 +1310,10 @@ export class Peer {
|
|
|
1307
1310
|
for (const p of livePackets) {
|
|
1308
1311
|
await this.#sendMessengerPacket(pubkey, PACKET_ID_MESSAGE, p);
|
|
1309
1312
|
}
|
|
1310
|
-
|
|
1313
|
+
// Every fragment got a packet number BELOW the counter's position now —
|
|
1314
|
+
// when the peer's implicit acks advance the window past it, the whole
|
|
1315
|
+
// message is confirmed on their device.
|
|
1316
|
+
return { via: "online", endPacketNumber: this.#friendSessions.get(pubkey)?.sendPacketNumber };
|
|
1311
1317
|
}
|
|
1312
1318
|
catch (error) {
|
|
1313
1319
|
const emsg = error.message;
|
|
@@ -1343,7 +1349,7 @@ export class Peer {
|
|
|
1343
1349
|
// no session is simply a no-op.
|
|
1344
1350
|
if (text.length === 0) {
|
|
1345
1351
|
this.#debugLog(`sendText: empty connection-kick to ${pubkey} with no live session — dropping (not flooding express)`);
|
|
1346
|
-
return;
|
|
1352
|
+
return { via: "online" };
|
|
1347
1353
|
}
|
|
1348
1354
|
// Offline / fallback path via Carrier express HTTP store-and-forward.
|
|
1349
1355
|
// Skipped entirely in control-plane-only mode (decentlan's data plane):
|
|
@@ -1353,7 +1359,7 @@ export class Peer {
|
|
|
1353
1359
|
if (this.#express?.hasNodes() && !this.#opts.expressControlPlaneOnly) {
|
|
1354
1360
|
await this.#express.sendOfflineText(pubkey, packet);
|
|
1355
1361
|
this.#debugLog(`sendText: queued via express for ${pubkey}`);
|
|
1356
|
-
return;
|
|
1362
|
+
return { via: "offline" };
|
|
1357
1363
|
}
|
|
1358
1364
|
throw new Error("friend is offline and no express node is configured");
|
|
1359
1365
|
}
|
|
@@ -1568,7 +1574,77 @@ export class Peer {
|
|
|
1568
1574
|
if (envelope.length > CARRIER_MAX_APP_BULKMSG_LEN) {
|
|
1569
1575
|
throw new Error(`inline file too large for the message channel (${envelope.length} bytes encoded, max ${CARRIER_MAX_APP_BULKMSG_LEN})`);
|
|
1570
1576
|
}
|
|
1571
|
-
|
|
1577
|
+
// The confirmation window scales with the payload: a floor for handshake
|
|
1578
|
+
// latency plus time to move the envelope at a conservative 50 KB/s, capped
|
|
1579
|
+
// so a dead session can't pin the caller for ages.
|
|
1580
|
+
const deliveryTimeoutMs = opts.deliveryTimeoutMs ??
|
|
1581
|
+
Math.min(180_000, 20_000 + Math.ceil(envelope.length / 50));
|
|
1582
|
+
if (this.#shouldRequireTextAck(pubkey)) {
|
|
1583
|
+
// JS peer: the app-level text-ACK is the strongest confirmation there is
|
|
1584
|
+
// — the receiving app parsed the envelope. The retry interval must be
|
|
1585
|
+
// longer than one full send of the payload, or a slow path gets the
|
|
1586
|
+
// whole multi-MB envelope re-blasted mid-flight.
|
|
1587
|
+
await this.sendTextUntilAck(pubkey, envelope, {
|
|
1588
|
+
timeoutMs: deliveryTimeoutMs,
|
|
1589
|
+
retryIntervalMs: Math.max(TEXT_ACK_RETRY_MS, Math.ceil(envelope.length / 100))
|
|
1590
|
+
});
|
|
1591
|
+
return { delivery: "acked" };
|
|
1592
|
+
}
|
|
1593
|
+
// Native peer (iOS/Android/C): it will never speak our text-ACK envelope,
|
|
1594
|
+
// but its toxcore DOES acknowledge every reliable packet — the very signal
|
|
1595
|
+
// its own SDK surfaces to the app as "Delivered". Wait for the send window
|
|
1596
|
+
// to drain past our fragments.
|
|
1597
|
+
const outcome = await this.#sendTextPlain(pubkey, envelope);
|
|
1598
|
+
if (outcome.via === "offline")
|
|
1599
|
+
return { delivery: "offline" };
|
|
1600
|
+
if (outcome.endPacketNumber === undefined)
|
|
1601
|
+
return { delivery: "accepted" };
|
|
1602
|
+
const acked = await this.#awaitTransportAck(pubkey, outcome.endPacketNumber, deliveryTimeoutMs);
|
|
1603
|
+
this.#debugLog(`inline file "${opts.name}" to ${pubkey}: transport ${acked ? "ACKED" : `NOT confirmed within ${deliveryTimeoutMs}ms`}`);
|
|
1604
|
+
return { delivery: acked ? "acked" : "accepted" };
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* True once the friend's implicit acks (PACKET_ID_REQUEST walks) have moved
|
|
1608
|
+
* our reliable send window past `endPacketNumber` — i.e. every packet we had
|
|
1609
|
+
* sent by that point is confirmed received on their device. False when the
|
|
1610
|
+
* window doesn't drain in time or the session dies/rekeys under us (a new
|
|
1611
|
+
* session restarts the numbering, so the old position proves nothing).
|
|
1612
|
+
*/
|
|
1613
|
+
async #awaitTransportAck(pubkey, endPacketNumber, timeoutMs) {
|
|
1614
|
+
const watched = this.#friendSessions.get(pubkey);
|
|
1615
|
+
if (!watched)
|
|
1616
|
+
return false;
|
|
1617
|
+
const deadline = Date.now() + timeoutMs;
|
|
1618
|
+
for (;;) {
|
|
1619
|
+
const session = this.#friendSessions.get(pubkey);
|
|
1620
|
+
if (session !== watched || !session.established)
|
|
1621
|
+
return false;
|
|
1622
|
+
const start = session.sendBufferStartNum;
|
|
1623
|
+
if (start !== undefined) {
|
|
1624
|
+
// Wraparound-safe: the window is ≤8192 packets, so a "distance" over
|
|
1625
|
+
// 2^31 means start has passed endPacketNumber.
|
|
1626
|
+
const pending = (endPacketNumber - start) >>> 0;
|
|
1627
|
+
if (pending === 0 || pending > 0x80000000)
|
|
1628
|
+
return true;
|
|
1629
|
+
}
|
|
1630
|
+
else if (!session.sendArray || session.sendArray.size === 0) {
|
|
1631
|
+
return true; // nothing left unacknowledged at all
|
|
1632
|
+
}
|
|
1633
|
+
if (Date.now() >= deadline)
|
|
1634
|
+
return false;
|
|
1635
|
+
await sleep(250);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* True when this friend is a NATIVE Carrier client (iOS/Android/C SDK): its
|
|
1640
|
+
* DHT/relay key differs from its identity key, where a JS peer announces its
|
|
1641
|
+
* identity key as its DHT key. Natives cannot see the toxcore file-transfer
|
|
1642
|
+
* protocol (sendFile) — the inline envelope is their only file path — and
|
|
1643
|
+
* they never speak the JS text-ACK scheme.
|
|
1644
|
+
*/
|
|
1645
|
+
isNativeFriend(pubkey) {
|
|
1646
|
+
const friend = this.#friends.get(pubkey);
|
|
1647
|
+
return !!(friend?.dhtPubkey && friend.dhtPubkey !== friend.pubkey);
|
|
1572
1648
|
}
|
|
1573
1649
|
/** Fired when a peer sends a Carrier friend-invite (PACKET_TYPE_INVITE_REQUEST).
|
|
1574
1650
|
* This is the channel the iOS/Android WebRTC SDK uses for call signaling —
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -135,6 +135,23 @@ export type TextMessage = {
|
|
|
135
135
|
* can see when online delivery is failing and only offline messages land. */
|
|
136
136
|
via?: "online" | "offline";
|
|
137
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* Outcome of an inline-file send — the caller's ONLY honest basis for a "sent"
|
|
140
|
+
* checkmark. "Bytes left this machine" is not delivery.
|
|
141
|
+
*/
|
|
142
|
+
export type InlineSendResult = {
|
|
143
|
+
/**
|
|
144
|
+
* - "acked": the peer CONFIRMED receipt — an app-level ACK from a JS peer,
|
|
145
|
+
* or the toxcore transport ACK from a native one (the same signal Android
|
|
146
|
+
* surfaces as ReceiptState.Delivered). Safe to mark "sent".
|
|
147
|
+
* - "accepted": the transport took the bytes but no confirmation arrived in
|
|
148
|
+
* the window. NOT delivered as far as anyone can prove — mark it failed or
|
|
149
|
+
* queued, never "sent".
|
|
150
|
+
* - "offline": stored on the express relay for the peer's next pull. Mark
|
|
151
|
+
* it queued.
|
|
152
|
+
*/
|
|
153
|
+
delivery: "acked" | "accepted" | "offline";
|
|
154
|
+
};
|
|
138
155
|
export type SendTextUntilAckOptions = {
|
|
139
156
|
/** Stable id for retries across a caller-managed outbox. Defaults to a random id. */
|
|
140
157
|
deliveryId?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.140",
|
|
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",
|