@decentnetwork/peer 0.1.134 → 0.1.136
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/ice-servers.d.ts +60 -0
- package/dist/ice-servers.js +78 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/peer.d.ts +30 -5
- package/dist/peer.js +88 -11
- package/dist/types/peer.d.ts +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC ICE helpers matching Android Carrier WebrtcClient.getIceServers() /
|
|
3
|
+
* CarrierExtension.getTurnServerInfo() (carrier_get_turn_server).
|
|
4
|
+
*
|
|
5
|
+
* Online Android Beagle gathers STUN+TURN against a Carrier bootstrap node's
|
|
6
|
+
* coturn (UDP/3478) using Carrier long-term credentials. Browser / Node WebRTC
|
|
7
|
+
* stacks need the same list — not the tokyo.fi.chat static relay used only as
|
|
8
|
+
* a fallback for the peer SDK's own messaging UDP relay path.
|
|
9
|
+
*/
|
|
10
|
+
import { CARRIER_TURN_PORT, CARRIER_TURN_REALM } from "./turn-creds.js";
|
|
11
|
+
import type { NetworkNode } from "./types/peer.js";
|
|
12
|
+
/** Same shape as Android CarrierExtension.TurnServerInfo / CarrierTurnServer. */
|
|
13
|
+
export interface CarrierTurnServerInfo {
|
|
14
|
+
server: string;
|
|
15
|
+
port: number;
|
|
16
|
+
username: string;
|
|
17
|
+
password: string;
|
|
18
|
+
realm: string;
|
|
19
|
+
}
|
|
20
|
+
/** Browser / wrtc RTCIceServer-compatible entry. */
|
|
21
|
+
export interface RtcIceServer {
|
|
22
|
+
urls: string | string[];
|
|
23
|
+
username?: string;
|
|
24
|
+
credential?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface BuildIceServersOptions {
|
|
27
|
+
/** Bootstrap / bootnodes that run Carrier TURN on port 3478. */
|
|
28
|
+
bootstrapNodes: NetworkNode[];
|
|
29
|
+
ourUserid: string;
|
|
30
|
+
ourSecretKey: Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* How many bootstrap TURNs to advertise. Android returns one; browsers often
|
|
33
|
+
* benefit from a few (default 3). Cap with this.
|
|
34
|
+
*/
|
|
35
|
+
limit?: number;
|
|
36
|
+
/** Also advertise turn:?transport=tcp (useful for UDP-blocked browsers). */
|
|
37
|
+
includeTcpTurn?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Derive TURN info for one bootnode — peer analogue of
|
|
41
|
+
* carrier_get_turn_server / CarrierExtension.getTurnServerInfo().
|
|
42
|
+
*/
|
|
43
|
+
export declare function getTurnServerInfoForBootnode(opts: {
|
|
44
|
+
bootnode: NetworkNode;
|
|
45
|
+
ourUserid: string;
|
|
46
|
+
ourSecretKey: Uint8Array;
|
|
47
|
+
}): CarrierTurnServerInfo;
|
|
48
|
+
/**
|
|
49
|
+
* Build RTCIceServer entries from TURN info the way Android WebrtcClient does:
|
|
50
|
+
* stun:host:port + turn:host:port with the same username/password.
|
|
51
|
+
*/
|
|
52
|
+
export declare function iceServersFromTurnInfo(info: CarrierTurnServerInfo, opts?: {
|
|
53
|
+
includeTcpTurn?: boolean;
|
|
54
|
+
}): RtcIceServer[];
|
|
55
|
+
/**
|
|
56
|
+
* Build a WebRTC iceServers list from Carrier bootstrap nodes.
|
|
57
|
+
* Fresh nonce/credentials on every call (same as Android).
|
|
58
|
+
*/
|
|
59
|
+
export declare function getIceServers(opts: BuildIceServersOptions): RtcIceServer[];
|
|
60
|
+
export { CARRIER_TURN_PORT, CARRIER_TURN_REALM };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC ICE helpers matching Android Carrier WebrtcClient.getIceServers() /
|
|
3
|
+
* CarrierExtension.getTurnServerInfo() (carrier_get_turn_server).
|
|
4
|
+
*
|
|
5
|
+
* Online Android Beagle gathers STUN+TURN against a Carrier bootstrap node's
|
|
6
|
+
* coturn (UDP/3478) using Carrier long-term credentials. Browser / Node WebRTC
|
|
7
|
+
* stacks need the same list — not the tokyo.fi.chat static relay used only as
|
|
8
|
+
* a fallback for the peer SDK's own messaging UDP relay path.
|
|
9
|
+
*/
|
|
10
|
+
import { base58ToBytes } from "./utils/base58.js";
|
|
11
|
+
import { CARRIER_TURN_PORT, CARRIER_TURN_REALM, deriveCarrierTurnCreds } from "./turn-creds.js";
|
|
12
|
+
function bootnodePublicKey(node) {
|
|
13
|
+
if (node.pkBytes && node.pkBytes.length === 32)
|
|
14
|
+
return node.pkBytes;
|
|
15
|
+
if (!node.pk)
|
|
16
|
+
throw new Error(`bootstrap node ${node.host} has no pk`);
|
|
17
|
+
return base58ToBytes(node.pk);
|
|
18
|
+
}
|
|
19
|
+
function toTurnServerInfo(creds) {
|
|
20
|
+
return {
|
|
21
|
+
server: creds.host,
|
|
22
|
+
port: creds.port,
|
|
23
|
+
username: creds.username,
|
|
24
|
+
password: creds.password,
|
|
25
|
+
realm: creds.realm
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Derive TURN info for one bootnode — peer analogue of
|
|
30
|
+
* carrier_get_turn_server / CarrierExtension.getTurnServerInfo().
|
|
31
|
+
*/
|
|
32
|
+
export function getTurnServerInfoForBootnode(opts) {
|
|
33
|
+
const creds = deriveCarrierTurnCreds({
|
|
34
|
+
bootnodeHost: opts.bootnode.host,
|
|
35
|
+
bootnodePublicKey: bootnodePublicKey(opts.bootnode),
|
|
36
|
+
ourUserid: opts.ourUserid,
|
|
37
|
+
ourSecretKey: opts.ourSecretKey
|
|
38
|
+
});
|
|
39
|
+
return toTurnServerInfo(creds);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build RTCIceServer entries from TURN info the way Android WebrtcClient does:
|
|
43
|
+
* stun:host:port + turn:host:port with the same username/password.
|
|
44
|
+
*/
|
|
45
|
+
export function iceServersFromTurnInfo(info, opts) {
|
|
46
|
+
const base = `${info.server}:${info.port}`;
|
|
47
|
+
const auth = { username: info.username, credential: info.password };
|
|
48
|
+
const out = [
|
|
49
|
+
{ urls: `stun:${base}`, ...auth },
|
|
50
|
+
{ urls: `turn:${base}`, ...auth }
|
|
51
|
+
];
|
|
52
|
+
if (opts?.includeTcpTurn !== false) {
|
|
53
|
+
out.push({ urls: `turn:${base}?transport=tcp`, ...auth });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build a WebRTC iceServers list from Carrier bootstrap nodes.
|
|
59
|
+
* Fresh nonce/credentials on every call (same as Android).
|
|
60
|
+
*/
|
|
61
|
+
export function getIceServers(opts) {
|
|
62
|
+
const limit = opts.limit ?? 3;
|
|
63
|
+
const nodes = opts.bootstrapNodes.filter((n) => n?.host && (n.pk || n.pkBytes)).slice(0, limit);
|
|
64
|
+
if (!nodes.length) {
|
|
65
|
+
throw new Error("getIceServers: no bootstrap nodes with public keys");
|
|
66
|
+
}
|
|
67
|
+
const ice = [];
|
|
68
|
+
for (const bootnode of nodes) {
|
|
69
|
+
const info = getTurnServerInfoForBootnode({
|
|
70
|
+
bootnode,
|
|
71
|
+
ourUserid: opts.ourUserid,
|
|
72
|
+
ourSecretKey: opts.ourSecretKey
|
|
73
|
+
});
|
|
74
|
+
ice.push(...iceServersFromTurnInfo(info, { includeTcpTurn: opts.includeTcpTurn }));
|
|
75
|
+
}
|
|
76
|
+
return ice;
|
|
77
|
+
}
|
|
78
|
+
export { CARRIER_TURN_PORT, CARRIER_TURN_REALM };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE,
|
|
|
6
6
|
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
|
7
7
|
export { createCarrierRendezvousAuthProof, RENDEZVOUS_AUTH_KEY_DOMAIN, RENDEZVOUS_AUTH_PROOF_LENGTH } from "./crypto/rendezvous.js";
|
|
8
8
|
export { base58ToBytes, bytesToBase58 } from "./utils/base58.js";
|
|
9
|
+
export { CARRIER_TURN_PORT, CARRIER_TURN_REALM, CARRIER_TURN_USER_SUFFIX, deriveCarrierTurnCreds } from "./turn-creds.js";
|
|
10
|
+
export type { CarrierTurnCreds } from "./turn-creds.js";
|
|
11
|
+
export { getIceServers, getTurnServerInfoForBootnode, iceServersFromTurnInfo } from "./ice-servers.js";
|
|
12
|
+
export type { BuildIceServersOptions, CarrierTurnServerInfo, RtcIceServer } from "./ice-servers.js";
|
|
9
13
|
export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
|
10
14
|
export type { CarrierPacket, FriendMessagePacket, FriendRequestPacket, InviteReqPacket, InviteRspPacket } from "./compat/packet.js";
|
|
11
15
|
export type { ToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,6 @@ export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE,
|
|
|
6
6
|
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
|
7
7
|
export { createCarrierRendezvousAuthProof, RENDEZVOUS_AUTH_KEY_DOMAIN, RENDEZVOUS_AUTH_PROOF_LENGTH } from "./crypto/rendezvous.js";
|
|
8
8
|
export { base58ToBytes, bytesToBase58 } from "./utils/base58.js";
|
|
9
|
+
export { CARRIER_TURN_PORT, CARRIER_TURN_REALM, CARRIER_TURN_USER_SUFFIX, deriveCarrierTurnCreds } from "./turn-creds.js";
|
|
10
|
+
export { getIceServers, getTurnServerInfoForBootnode, iceServersFromTurnInfo } from "./ice-servers.js";
|
|
9
11
|
export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
package/dist/peer.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BootstrapResult } from "./compat/bootstrap.js";
|
|
2
2
|
import type { FriendRecord } from "./store/friends.js";
|
|
3
|
+
import { type CarrierTurnServerInfo, type RtcIceServer } from "./ice-servers.js";
|
|
3
4
|
import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, InlineFileEvent, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, SendTextUntilAckOptions, TextMessage } from "./types/peer.js";
|
|
4
5
|
export declare class Peer {
|
|
5
6
|
#private;
|
|
@@ -18,6 +19,21 @@ export declare class Peer {
|
|
|
18
19
|
*/
|
|
19
20
|
sign(message: Uint8Array): Uint8Array;
|
|
20
21
|
address(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Carrier bootstrap TURN credentials — peer analogue of Android
|
|
24
|
+
* CarrierExtension.getTurnServerInfo() / carrier_get_turn_server().
|
|
25
|
+
* Uses the first configured bootstrap node with a public key (fresh nonce
|
|
26
|
+
* each call). For WebRTC, prefer {@link getIceServers}.
|
|
27
|
+
*/
|
|
28
|
+
getTurnServerInfo(bootnodeIndex?: number): CarrierTurnServerInfo;
|
|
29
|
+
/**
|
|
30
|
+
* RTCIceServer list for browser / native WebRTC, matching Android
|
|
31
|
+
* WebrtcClient.getIceServers() (Carrier bootstrap STUN+TURN, not tokyo).
|
|
32
|
+
*/
|
|
33
|
+
getIceServers(opts?: {
|
|
34
|
+
limit?: number;
|
|
35
|
+
includeTcpTurn?: boolean;
|
|
36
|
+
}): RtcIceServer[];
|
|
21
37
|
joinNetwork(): Promise<BootstrapResult>;
|
|
22
38
|
lookup(pubkey: string): Promise<LookupResult>;
|
|
23
39
|
announceSelf(timeoutMs?: number): Promise<NetworkNode[]>;
|
|
@@ -87,11 +103,13 @@ export declare class Peer {
|
|
|
87
103
|
/**
|
|
88
104
|
* Send text and keep retransmitting until the peer explicitly ACKs it.
|
|
89
105
|
*
|
|
90
|
-
* The
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
106
|
+
* The ACK means the far side's SDK durably received the message — it is
|
|
107
|
+
* sent BEFORE the peer's onText handlers run, so a peer that is busy in
|
|
108
|
+
* application code (a model call, a disk-bound job) still ACKs instantly.
|
|
109
|
+
* It does NOT mean the application processed the message; if you need that,
|
|
110
|
+
* have the far side send an application-level reply. Pass a stable
|
|
111
|
+
* deliveryId when retrying an item from an application outbox across
|
|
112
|
+
* process restarts.
|
|
95
113
|
*/
|
|
96
114
|
sendTextUntilAck(pubkey: string, text: string, opts?: SendTextUntilAckOptions): Promise<{
|
|
97
115
|
deliveryId: string;
|
|
@@ -99,6 +117,13 @@ export declare class Peer {
|
|
|
99
117
|
waitForFriendConnected(pubkey: string, timeoutMs?: number): Promise<boolean>;
|
|
100
118
|
onFriendRequest(cb: (req: FriendRequest) => void): void;
|
|
101
119
|
onText(cb: (msg: TextMessage) => unknown | Promise<unknown>): void;
|
|
120
|
+
/** @internal Test seam: deliver a text message exactly as if it arrived off
|
|
121
|
+
* the wire. Exists so the ACK-before-handlers ordering is testable without
|
|
122
|
+
* a network — do not use outside tests. */
|
|
123
|
+
_testDispatchTextMessage(msg: {
|
|
124
|
+
pubkey: string;
|
|
125
|
+
text: string;
|
|
126
|
+
}): Promise<void>;
|
|
102
127
|
/** Files received inline over (bulk)messages — the iOS/C Carrier apps'
|
|
103
128
|
* native way of sending images/audio online (FileModel JSON envelope). */
|
|
104
129
|
onInlineFile(cb: (evt: InlineFileEvent) => void): void;
|
package/dist/peer.js
CHANGED
|
@@ -23,6 +23,7 @@ import { UdpTransport } from "./transport/udp.js";
|
|
|
23
23
|
import { bytesToHex, concatBytes, randomBytes } from "./utils/bytes.js";
|
|
24
24
|
import { buildBindingRequest, decodeStun, decodeXorMappedAddress, findAttr, STUN_ATTR_XOR_MAPPED_ADDRESS, STUN_BINDING_SUCCESS } from "./stun.js";
|
|
25
25
|
import { TurnClient } from "./turn.js";
|
|
26
|
+
import { getIceServers as buildRtcIceServers, getTurnServerInfoForBootnode } from "./ice-servers.js";
|
|
26
27
|
import { createBoundUdp4Socket, closeDgramSocket } from "./transport/dgram-lifecycle.js";
|
|
27
28
|
// Dedicated TURN relay servers — fixed public relays used as a stable
|
|
28
29
|
// fallback path when direct UDP hole-punch fails or flaps (symmetric NAT,
|
|
@@ -34,7 +35,21 @@ import { createBoundUdp4Socket, closeDgramSocket } from "./transport/dgram-lifec
|
|
|
34
35
|
const TURN_RELAY_SERVERS = (() => {
|
|
35
36
|
const raw = process.env.DECENT_TURN_SERVERS?.trim();
|
|
36
37
|
if (!raw) {
|
|
37
|
-
|
|
38
|
+
// MORE THAN ONE, ALWAYS. This list is not a preference — it is the only
|
|
39
|
+
// way a peer learns its own public endpoint, so when every entry is down
|
|
40
|
+
// nobody can hole-punch and the WHOLE network silently degrades to TCP
|
|
41
|
+
// relay. That is not hypothetical: coturn on tokyo core-dumped and stayed
|
|
42
|
+
// dead for ten days, and with tokyo as the sole entry every link in the
|
|
43
|
+
// network ran at relay latency (~1200ms instead of ~130ms) the entire
|
|
44
|
+
// time. Nothing alerted, because the port stayed open.
|
|
45
|
+
//
|
|
46
|
+
// Ordered by expected reachability from the biggest population of peers.
|
|
47
|
+
// A dead entry costs one timeout before the next is tried, so listing a
|
|
48
|
+
// spare is close to free; having none is a network-wide outage.
|
|
49
|
+
return [
|
|
50
|
+
{ host: "tokyo.fi.chat", port: 3478, username: "allcom", password: "allcompass" },
|
|
51
|
+
{ host: "gfax.cn", port: 3478, username: "allcom", password: "allcompass" },
|
|
52
|
+
];
|
|
38
53
|
}
|
|
39
54
|
return raw
|
|
40
55
|
.split(",")
|
|
@@ -817,6 +832,43 @@ export class Peer {
|
|
|
817
832
|
}
|
|
818
833
|
return carrierAddressFromPublicKey(this.#keyPair.publicKey);
|
|
819
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* Carrier bootstrap TURN credentials — peer analogue of Android
|
|
837
|
+
* CarrierExtension.getTurnServerInfo() / carrier_get_turn_server().
|
|
838
|
+
* Uses the first configured bootstrap node with a public key (fresh nonce
|
|
839
|
+
* each call). For WebRTC, prefer {@link getIceServers}.
|
|
840
|
+
*/
|
|
841
|
+
getTurnServerInfo(bootnodeIndex = 0) {
|
|
842
|
+
if (!this.#keyPair) {
|
|
843
|
+
throw new Error("Peer is not started");
|
|
844
|
+
}
|
|
845
|
+
const nodes = this.#opts.bootstrapNodes.filter((n) => n?.host && (n.pk || n.pkBytes));
|
|
846
|
+
const bootnode = nodes[bootnodeIndex] ?? nodes[0];
|
|
847
|
+
if (!bootnode) {
|
|
848
|
+
throw new Error("getTurnServerInfo: no bootstrap nodes configured");
|
|
849
|
+
}
|
|
850
|
+
return getTurnServerInfoForBootnode({
|
|
851
|
+
bootnode,
|
|
852
|
+
ourUserid: this.userid(),
|
|
853
|
+
ourSecretKey: this.#keyPair.secretKey
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* RTCIceServer list for browser / native WebRTC, matching Android
|
|
858
|
+
* WebrtcClient.getIceServers() (Carrier bootstrap STUN+TURN, not tokyo).
|
|
859
|
+
*/
|
|
860
|
+
getIceServers(opts) {
|
|
861
|
+
if (!this.#keyPair) {
|
|
862
|
+
throw new Error("Peer is not started");
|
|
863
|
+
}
|
|
864
|
+
return buildRtcIceServers({
|
|
865
|
+
bootstrapNodes: this.#opts.bootstrapNodes,
|
|
866
|
+
ourUserid: this.userid(),
|
|
867
|
+
ourSecretKey: this.#keyPair.secretKey,
|
|
868
|
+
limit: opts?.limit,
|
|
869
|
+
includeTcpTurn: opts?.includeTcpTurn
|
|
870
|
+
});
|
|
871
|
+
}
|
|
820
872
|
async joinNetwork() {
|
|
821
873
|
if (!this.#bootstrap) {
|
|
822
874
|
throw new Error("Peer is not started");
|
|
@@ -1316,11 +1368,13 @@ export class Peer {
|
|
|
1316
1368
|
/**
|
|
1317
1369
|
* Send text and keep retransmitting until the peer explicitly ACKs it.
|
|
1318
1370
|
*
|
|
1319
|
-
* The
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1371
|
+
* The ACK means the far side's SDK durably received the message — it is
|
|
1372
|
+
* sent BEFORE the peer's onText handlers run, so a peer that is busy in
|
|
1373
|
+
* application code (a model call, a disk-bound job) still ACKs instantly.
|
|
1374
|
+
* It does NOT mean the application processed the message; if you need that,
|
|
1375
|
+
* have the far side send an application-level reply. Pass a stable
|
|
1376
|
+
* deliveryId when retrying an item from an application outbox across
|
|
1377
|
+
* process restarts.
|
|
1324
1378
|
*/
|
|
1325
1379
|
async sendTextUntilAck(pubkey, text, opts = {}) {
|
|
1326
1380
|
const deliveryId = opts.deliveryId ?? createTextDeliveryId();
|
|
@@ -1427,6 +1481,28 @@ export class Peer {
|
|
|
1427
1481
|
? async () => { await this.#sendTextAck(msg.pubkey, deliveryId); }
|
|
1428
1482
|
: undefined;
|
|
1429
1483
|
const delivered = { ...msg, text, deliveryId, ack };
|
|
1484
|
+
// ACK BEFORE the handlers, not after. The ACK means "the SDK durably
|
|
1485
|
+
// received this", never "the application finished processing it" — the
|
|
1486
|
+
// two were conflated here and it broke real chat twice over:
|
|
1487
|
+
//
|
|
1488
|
+
// 1. A peer whose handler was slow (beagle-help awaiting a 10s+ model
|
|
1489
|
+
// call) sent its ACK 10s late — inside sendText's 15s auto window —
|
|
1490
|
+
// so senders got "ACK timed out" for messages that were delivered
|
|
1491
|
+
// AND answered. The UI then dropped the sender's own message as
|
|
1492
|
+
// failed: "对方在回答一个看不见的问题".
|
|
1493
|
+
// 2. The delivery id was only registered after the handlers returned,
|
|
1494
|
+
// so the sender's 5s retransmits arriving DURING a slow handler
|
|
1495
|
+
// re-dispatched the same message — the busy peer processed it again
|
|
1496
|
+
// (and hit its model again), making the overload worse.
|
|
1497
|
+
//
|
|
1498
|
+
// Registering + ACKing up front fixes both: retries hit the dedupe path
|
|
1499
|
+
// and re-ACK, and a busy application can no longer turn into a transport
|
|
1500
|
+
// failure. A handler that needs "processed" semantics must build its own
|
|
1501
|
+
// application-level reply — that is what an answer message is.
|
|
1502
|
+
if (deliveryId) {
|
|
1503
|
+
this.#rememberDeliveredTextId(deliveryId);
|
|
1504
|
+
void ack?.();
|
|
1505
|
+
}
|
|
1430
1506
|
try {
|
|
1431
1507
|
for (const handler of this.#textHandlers) {
|
|
1432
1508
|
await handler(delivered);
|
|
@@ -1434,13 +1510,14 @@ export class Peer {
|
|
|
1434
1510
|
}
|
|
1435
1511
|
catch (error) {
|
|
1436
1512
|
this.#debugLog(`text handler failed for ${msg.pubkey}: ${error.message}`);
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
if (deliveryId) {
|
|
1440
|
-
this.#rememberDeliveredTextId(deliveryId);
|
|
1441
|
-
await ack?.();
|
|
1442
1513
|
}
|
|
1443
1514
|
}
|
|
1515
|
+
/** @internal Test seam: deliver a text message exactly as if it arrived off
|
|
1516
|
+
* the wire. Exists so the ACK-before-handlers ordering is testable without
|
|
1517
|
+
* a network — do not use outside tests. */
|
|
1518
|
+
_testDispatchTextMessage(msg) {
|
|
1519
|
+
return this.#dispatchTextMessage(msg);
|
|
1520
|
+
}
|
|
1444
1521
|
async #sendTextAck(pubkey, deliveryId) {
|
|
1445
1522
|
try {
|
|
1446
1523
|
await this.sendText(pubkey, encodeTextAckEnvelope({ t: "ack", id: deliveryId }));
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -125,9 +125,10 @@ export type TextMessage = {
|
|
|
125
125
|
/** Stable id for SDK-level acknowledged delivery. Present only for messages
|
|
126
126
|
* sent with sendTextUntilAck(). Receivers can persist this id for dedupe. */
|
|
127
127
|
deliveryId?: string;
|
|
128
|
-
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
/** Re-send the delivery ACK. The SDK ACKs automatically BEFORE handlers run
|
|
129
|
+
* — the ACK means "SDK received", never "application processed", so a slow
|
|
130
|
+
* handler (model call, disk job) cannot fake a delivery failure at the
|
|
131
|
+
* sender. Kept on the message for idempotent manual re-ACKs only. */
|
|
131
132
|
ack?: () => Promise<void>;
|
|
132
133
|
/** Delivery path: "online" = live net_crypto session (direct/relay), "offline"
|
|
133
134
|
* = express store-and-forward. Lets the UI color the two differently so a user
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.136",
|
|
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",
|