@decentnetwork/peer 0.1.140 → 0.1.141
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/compat/dnft1.d.ts +62 -0
- package/dist/compat/dnft1.js +146 -0
- package/dist/peer.js +68 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** "\u{1E}DNFT1" — the leading bytes of every frame. */
|
|
2
|
+
export declare const DNFT1_MAGIC: Uint8Array<ArrayBuffer>;
|
|
3
|
+
/** magic(6) + packetId(1) + payloadLen(4). */
|
|
4
|
+
export declare const DNFT1_HEADER_LENGTH = 11;
|
|
5
|
+
/** Wrap one file-transfer packet payload in a DNFT1 frame. */
|
|
6
|
+
export declare function encodeDnft1Frame(packetId: number, payload: Uint8Array): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a DNFT1 frame out of a received message body.
|
|
9
|
+
*
|
|
10
|
+
* Returns null for anything that is not a file-transfer frame (ordinary chat
|
|
11
|
+
* data — the common case, so this must stay cheap), and for a frame truncated
|
|
12
|
+
* below its declared payload length: dropped rather than half-parsed, since a
|
|
13
|
+
* short read would hand the engine a chunk with missing bytes. Bytes past
|
|
14
|
+
* payloadLen are transport junk and are ignored.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseDnft1Frame(raw: Uint8Array): {
|
|
17
|
+
packetId: number;
|
|
18
|
+
payload: Uint8Array;
|
|
19
|
+
} | null;
|
|
20
|
+
/**
|
|
21
|
+
* Picks the transport for each friend's file packets: raw messenger packets
|
|
22
|
+
* (a JS peer, which is faster — no envelope, no message-channel framing) or
|
|
23
|
+
* DNFT1 frames (a native peer, the only thing that reaches its app layer).
|
|
24
|
+
*
|
|
25
|
+
* There is no reliable capability handshake to key this off — appVersion is
|
|
26
|
+
* absent for older builds and lies across forks — so the mode is LEARNED from
|
|
27
|
+
* what actually works, which needs no negotiation and self-corrects:
|
|
28
|
+
*
|
|
29
|
+
* - The engine already retransmits an unanswered OFFER until the receiver
|
|
30
|
+
* responds. Every UNANSWERED_OFFERS_BEFORE_SWITCH of those with no inbound
|
|
31
|
+
* file packet flips the transport. Alternating (rather than a one-way
|
|
32
|
+
* downgrade) matters: an old JS peer that cannot parse DNFT1 must not be
|
|
33
|
+
* stranded there by one slow response, and a native peer must not be
|
|
34
|
+
* stranded on raw. Each transport keeps getting tried until one answers.
|
|
35
|
+
* - The first inbound file packet PINS the mode to the transport it arrived
|
|
36
|
+
* on and stops the alternation. That is ground truth about what the peer
|
|
37
|
+
* speaks, so later transfers to that friend start on the right one.
|
|
38
|
+
*
|
|
39
|
+
* At the engine's offer cadence (~450ms) a switch costs about 1.4s, well
|
|
40
|
+
* inside the 60s give-up window, so the wrong initial guess is invisible.
|
|
41
|
+
*/
|
|
42
|
+
export declare class Dnft1TransportPolicy {
|
|
43
|
+
#private;
|
|
44
|
+
/** Consecutive unanswered offers before trying the other transport. */
|
|
45
|
+
static readonly UNANSWERED_OFFERS_BEFORE_SWITCH = 3;
|
|
46
|
+
/** True when this friend's file packets should be sent as DNFT1 frames. */
|
|
47
|
+
useDnft1(friendId: string): boolean;
|
|
48
|
+
/** True once a real inbound file packet has settled the transport. */
|
|
49
|
+
isPinned(friendId: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Called for every outbound OFFER (packet 80). Returns true when this offer
|
|
52
|
+
* flipped the transport — the caller should send THIS offer on the new one.
|
|
53
|
+
*/
|
|
54
|
+
noteOfferSent(friendId: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Called for every inbound file packet, with the transport it arrived on.
|
|
57
|
+
* Pins the mode: the peer just proved which one it speaks.
|
|
58
|
+
*/
|
|
59
|
+
noteInbound(friendId: string, viaDnft1: boolean): void;
|
|
60
|
+
/** Drop all state for a friend (removed, or session torn down). */
|
|
61
|
+
forget(friendId: string): void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// DNFT1 — carrying file-transfer packets 80–83 inside ordinary friend messages.
|
|
2
|
+
//
|
|
3
|
+
// Why this exists: the JS peer speaks toxcore messenger packets 80–83 natively,
|
|
4
|
+
// but a native app never sees them. The precompiled Carrier C SDK consumes
|
|
5
|
+
// 80/81/82 internally (they feed its own file state machine, which registers no
|
|
6
|
+
// app callbacks) and discards 83 (FEC — it does not know the id). A P0 probe on
|
|
7
|
+
// iOS confirmed it: raw packets never reach the mobile app layer, so JS↔iOS
|
|
8
|
+
// file transfer could not work at all.
|
|
9
|
+
//
|
|
10
|
+
// The fix keeps the SDK untouched: both ends carry the SAME packet payloads
|
|
11
|
+
// inside ordinary friend messages, wrapped in a frame no chat text can produce.
|
|
12
|
+
// iOS implemented it first (FileTransferWire.swift in the Beagle app); this is
|
|
13
|
+
// the byte-exact JS counterpart — read that file before changing anything here.
|
|
14
|
+
//
|
|
15
|
+
// [0x1E]["DNFT1"][packetId u8][payloadLen u32 BE][payload]
|
|
16
|
+
//
|
|
17
|
+
// - 0x1E (record separator) leads, mirroring the "\u{1E}DNPACK1:" text-ack
|
|
18
|
+
// envelope convention — a one-byte reject for ordinary chat text.
|
|
19
|
+
// - payloadLen is explicit because the carrier/express path can append junk
|
|
20
|
+
// past the real payload. Trailing junk after a binary chunk would corrupt the
|
|
21
|
+
// file, so the frame carries its own length and anything past it is ignored.
|
|
22
|
+
//
|
|
23
|
+
// The payloads inside are UNCHANGED — the same bytes filetransfer.ts already
|
|
24
|
+
// builds. This module only adds and removes the envelope, so the engine, the
|
|
25
|
+
// FEC layer and the conformance fixtures stay transport-agnostic.
|
|
26
|
+
import { PACKET_ID_FILE_SENDREQUEST, PACKET_ID_FILE_FEC, } from "./filetransfer.js";
|
|
27
|
+
/** "\u{1E}DNFT1" — the leading bytes of every frame. */
|
|
28
|
+
export const DNFT1_MAGIC = Uint8Array.from([0x1e, 0x44, 0x4e, 0x46, 0x54, 0x31]);
|
|
29
|
+
/** magic(6) + packetId(1) + payloadLen(4). */
|
|
30
|
+
export const DNFT1_HEADER_LENGTH = 11;
|
|
31
|
+
/** Wrap one file-transfer packet payload in a DNFT1 frame. */
|
|
32
|
+
export function encodeDnft1Frame(packetId, payload) {
|
|
33
|
+
const out = new Uint8Array(DNFT1_HEADER_LENGTH + payload.length);
|
|
34
|
+
out.set(DNFT1_MAGIC, 0);
|
|
35
|
+
out[6] = packetId & 0xff;
|
|
36
|
+
const n = payload.length;
|
|
37
|
+
out[7] = (n >>> 24) & 0xff;
|
|
38
|
+
out[8] = (n >>> 16) & 0xff;
|
|
39
|
+
out[9] = (n >>> 8) & 0xff;
|
|
40
|
+
out[10] = n & 0xff;
|
|
41
|
+
out.set(payload, DNFT1_HEADER_LENGTH);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse a DNFT1 frame out of a received message body.
|
|
46
|
+
*
|
|
47
|
+
* Returns null for anything that is not a file-transfer frame (ordinary chat
|
|
48
|
+
* data — the common case, so this must stay cheap), and for a frame truncated
|
|
49
|
+
* below its declared payload length: dropped rather than half-parsed, since a
|
|
50
|
+
* short read would hand the engine a chunk with missing bytes. Bytes past
|
|
51
|
+
* payloadLen are transport junk and are ignored.
|
|
52
|
+
*/
|
|
53
|
+
export function parseDnft1Frame(raw) {
|
|
54
|
+
if (raw.length < DNFT1_HEADER_LENGTH)
|
|
55
|
+
return null;
|
|
56
|
+
for (let i = 0; i < DNFT1_MAGIC.length; i++) {
|
|
57
|
+
if (raw[i] !== DNFT1_MAGIC[i])
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const packetId = raw[6];
|
|
61
|
+
if (packetId < PACKET_ID_FILE_SENDREQUEST || packetId > PACKET_ID_FILE_FEC)
|
|
62
|
+
return null;
|
|
63
|
+
// >>> 0 keeps the length unsigned: a hostile 0xFFFFFFFF must compare as
|
|
64
|
+
// 4294967295 (and fail the bounds check), not as -1 (which would pass it).
|
|
65
|
+
const length = ((raw[7] << 24) | (raw[8] << 16) | (raw[9] << 8) | raw[10]) >>> 0;
|
|
66
|
+
if (raw.length - DNFT1_HEADER_LENGTH < length)
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
packetId,
|
|
70
|
+
payload: raw.subarray(DNFT1_HEADER_LENGTH, DNFT1_HEADER_LENGTH + length),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Picks the transport for each friend's file packets: raw messenger packets
|
|
75
|
+
* (a JS peer, which is faster — no envelope, no message-channel framing) or
|
|
76
|
+
* DNFT1 frames (a native peer, the only thing that reaches its app layer).
|
|
77
|
+
*
|
|
78
|
+
* There is no reliable capability handshake to key this off — appVersion is
|
|
79
|
+
* absent for older builds and lies across forks — so the mode is LEARNED from
|
|
80
|
+
* what actually works, which needs no negotiation and self-corrects:
|
|
81
|
+
*
|
|
82
|
+
* - The engine already retransmits an unanswered OFFER until the receiver
|
|
83
|
+
* responds. Every UNANSWERED_OFFERS_BEFORE_SWITCH of those with no inbound
|
|
84
|
+
* file packet flips the transport. Alternating (rather than a one-way
|
|
85
|
+
* downgrade) matters: an old JS peer that cannot parse DNFT1 must not be
|
|
86
|
+
* stranded there by one slow response, and a native peer must not be
|
|
87
|
+
* stranded on raw. Each transport keeps getting tried until one answers.
|
|
88
|
+
* - The first inbound file packet PINS the mode to the transport it arrived
|
|
89
|
+
* on and stops the alternation. That is ground truth about what the peer
|
|
90
|
+
* speaks, so later transfers to that friend start on the right one.
|
|
91
|
+
*
|
|
92
|
+
* At the engine's offer cadence (~450ms) a switch costs about 1.4s, well
|
|
93
|
+
* inside the 60s give-up window, so the wrong initial guess is invisible.
|
|
94
|
+
*/
|
|
95
|
+
export class Dnft1TransportPolicy {
|
|
96
|
+
/** Consecutive unanswered offers before trying the other transport. */
|
|
97
|
+
static UNANSWERED_OFFERS_BEFORE_SWITCH = 3;
|
|
98
|
+
#dnft1 = new Set();
|
|
99
|
+
#pinned = new Set();
|
|
100
|
+
#unanswered = new Map();
|
|
101
|
+
/** True when this friend's file packets should be sent as DNFT1 frames. */
|
|
102
|
+
useDnft1(friendId) {
|
|
103
|
+
return this.#dnft1.has(friendId);
|
|
104
|
+
}
|
|
105
|
+
/** True once a real inbound file packet has settled the transport. */
|
|
106
|
+
isPinned(friendId) {
|
|
107
|
+
return this.#pinned.has(friendId);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Called for every outbound OFFER (packet 80). Returns true when this offer
|
|
111
|
+
* flipped the transport — the caller should send THIS offer on the new one.
|
|
112
|
+
*/
|
|
113
|
+
noteOfferSent(friendId) {
|
|
114
|
+
if (this.#pinned.has(friendId))
|
|
115
|
+
return false;
|
|
116
|
+
const n = (this.#unanswered.get(friendId) ?? 0) + 1;
|
|
117
|
+
if (n < Dnft1TransportPolicy.UNANSWERED_OFFERS_BEFORE_SWITCH) {
|
|
118
|
+
this.#unanswered.set(friendId, n);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
this.#unanswered.set(friendId, 0);
|
|
122
|
+
if (this.#dnft1.has(friendId))
|
|
123
|
+
this.#dnft1.delete(friendId);
|
|
124
|
+
else
|
|
125
|
+
this.#dnft1.add(friendId);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Called for every inbound file packet, with the transport it arrived on.
|
|
130
|
+
* Pins the mode: the peer just proved which one it speaks.
|
|
131
|
+
*/
|
|
132
|
+
noteInbound(friendId, viaDnft1) {
|
|
133
|
+
this.#unanswered.delete(friendId);
|
|
134
|
+
this.#pinned.add(friendId);
|
|
135
|
+
if (viaDnft1)
|
|
136
|
+
this.#dnft1.add(friendId);
|
|
137
|
+
else
|
|
138
|
+
this.#dnft1.delete(friendId);
|
|
139
|
+
}
|
|
140
|
+
/** Drop all state for a friend (removed, or session torn down). */
|
|
141
|
+
forget(friendId) {
|
|
142
|
+
this.#dnft1.delete(friendId);
|
|
143
|
+
this.#pinned.delete(friendId);
|
|
144
|
+
this.#unanswered.delete(friendId);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/peer.js
CHANGED
|
@@ -13,6 +13,7 @@ import { NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_RESPONSE, ONI
|
|
|
13
13
|
import { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
14
14
|
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";
|
|
15
15
|
import { FileTransferManager, PACKET_ID_FILE_SENDREQUEST, PACKET_ID_FILE_CONTROL, PACKET_ID_FILE_DATA, PACKET_ID_FILE_FEC } from "./compat/filetransfer.js";
|
|
16
|
+
import { Dnft1TransportPolicy, encodeDnft1Frame, parseDnft1Frame } from "./compat/dnft1.js";
|
|
16
17
|
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";
|
|
17
18
|
import { LegacyExpressClient } from "./compat/express.js";
|
|
18
19
|
import { TcpRelayPool } from "./compat/tcp-relay-pool.js";
|
|
@@ -295,12 +296,22 @@ export class Peer {
|
|
|
295
296
|
* old peer returns ACCEPT/ACK over its reliable TCP relay. */
|
|
296
297
|
#fileRelayNegotiationUntil = new Map();
|
|
297
298
|
// Toxcore-standard file transfer (wire-compatible with native Carrier/toxcore).
|
|
299
|
+
/** Which transport carries each friend's file packets — raw messenger
|
|
300
|
+
* packets (JS peers) or DNFT1 frames inside friend messages (native peers,
|
|
301
|
+
* whose Carrier SDK swallows 80-82 and drops 83). See compat/dnft1.ts. */
|
|
302
|
+
#dnft1 = new Dnft1TransportPolicy();
|
|
298
303
|
#fileTransfer = new FileTransferManager((friendId, packetId, payload) => {
|
|
299
304
|
if (packetId === PACKET_ID_FILE_SENDREQUEST) {
|
|
300
305
|
const session = this.#friendSessions.get(friendId);
|
|
301
306
|
const lan = !!session?.lanRemoteHost && session.remote?.host === session.lanRemoteHost;
|
|
302
307
|
if (!lan)
|
|
303
308
|
this.#fileRelayNegotiationUntil.set(friendId, Date.now() + 6_000);
|
|
309
|
+
// Every unanswered offer is evidence the peer cannot hear this
|
|
310
|
+
// transport; the policy alternates until one of them answers.
|
|
311
|
+
this.#dnft1.noteOfferSent(friendId);
|
|
312
|
+
}
|
|
313
|
+
if (this.#dnft1.useDnft1(friendId)) {
|
|
314
|
+
return this.#sendDnft1Frame(friendId, packetId, payload);
|
|
304
315
|
}
|
|
305
316
|
return this.#sendMessengerPacket(friendId, packetId, payload);
|
|
306
317
|
}, (event, payload) => { this.#events.emit(event, payload); }, (friendId) => {
|
|
@@ -1180,6 +1191,7 @@ export class Peer {
|
|
|
1180
1191
|
const existed = this.#friends.delete(friendId);
|
|
1181
1192
|
this.#friendSessions.delete(friendId);
|
|
1182
1193
|
this.#fileRelayNegotiationUntil.delete(friendId);
|
|
1194
|
+
this.#dnft1.forget(friendId);
|
|
1183
1195
|
this.#pendingFriendRequests.delete(friendId);
|
|
1184
1196
|
this.#cookieRetryCount.delete(friendId);
|
|
1185
1197
|
this.#lastCookieSentKey.delete(friendId);
|
|
@@ -1751,6 +1763,37 @@ export class Peer {
|
|
|
1751
1763
|
onCustomPacket(cb) {
|
|
1752
1764
|
this.#events.on("customPacket", cb);
|
|
1753
1765
|
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Send one file-transfer packet as a DNFT1 frame inside a friend message —
|
|
1768
|
+
* the only route that reaches a native app's layer (its Carrier SDK
|
|
1769
|
+
* swallows packets 80-82 and drops 83). Payload bytes are unchanged; see
|
|
1770
|
+
* compat/dnft1.ts for the envelope and the iOS counterpart.
|
|
1771
|
+
*
|
|
1772
|
+
* Deliberately live-session only, and never express: express is
|
|
1773
|
+
* store-and-forward for chat, and 100 MB of file chunks has no business in
|
|
1774
|
+
* a mailbox. Control loss is already covered by the engine's offer/ack
|
|
1775
|
+
* retransmits, so a failed send here is a dropped packet the engine will
|
|
1776
|
+
* redo — not a lost transfer.
|
|
1777
|
+
*/
|
|
1778
|
+
async #sendDnft1Frame(friendId, packetId, payload) {
|
|
1779
|
+
const frame = encodeDnft1Frame(packetId, payload);
|
|
1780
|
+
// A full DATA frame (11 + 5 + 1367 = 1383 B) exceeds the 1024-byte
|
|
1781
|
+
// friendmsg unit, so it rides BULKMSG fragments — exactly what the C SDK
|
|
1782
|
+
// does when iOS hands the same frame to sendFriendMessage.
|
|
1783
|
+
if (frame.length <= CARRIER_MAX_APP_MESSAGE_LEN) {
|
|
1784
|
+
await this.#sendMessengerPacket(friendId, PACKET_ID_MESSAGE, encodeFriendMessagePacket(frame));
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const tid = (BigInt(Date.now()) << 20n) ^ BigInt(Math.floor(Math.random() * 0xfffff));
|
|
1788
|
+
for (let off = 0; off < frame.length; off += CARRIER_MAX_APP_MESSAGE_LEN) {
|
|
1789
|
+
const end = Math.min(off + CARRIER_MAX_APP_MESSAGE_LEN, frame.length);
|
|
1790
|
+
await this.#sendMessengerPacket(friendId, PACKET_ID_MESSAGE, encodeBulkMsgPacket({
|
|
1791
|
+
totalsz: off === 0 ? frame.length : 0,
|
|
1792
|
+
tid,
|
|
1793
|
+
data: frame.subarray(off, end)
|
|
1794
|
+
}));
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1754
1797
|
// ───────────────────── file transfer (toxcore-standard, native-ready) ─────
|
|
1755
1798
|
/**
|
|
1756
1799
|
* Offer a file to a friend (by userid). Returns the fileId (hex) or null if
|
|
@@ -3093,6 +3136,14 @@ export class Peer {
|
|
|
3093
3136
|
// the very same packFilePayload as the online path).
|
|
3094
3137
|
if (this.#tryEmitBinaryInlineFile(fromUserId, decoded.data, "offline"))
|
|
3095
3138
|
return;
|
|
3139
|
+
// Neither end routes DNFT1 through express on purpose — a 100 MB stream
|
|
3140
|
+
// has no business in a store-and-forward mailbox, and the engine's
|
|
3141
|
+
// retransmits already cover control loss. But if a frame does surface
|
|
3142
|
+
// here, swallow it rather than rendering the binary as chat mojibake.
|
|
3143
|
+
if (parseDnft1Frame(decoded.data)) {
|
|
3144
|
+
this.#debugLog(`ignoring DNFT1 frame from ${fromUserId} on the express path`);
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3096
3147
|
if (this.#tryEmitInlineFile(fromUserId, text, "offline"))
|
|
3097
3148
|
return;
|
|
3098
3149
|
void this.#dispatchTextMessage({ pubkey: fromUserId, text, via: "offline" });
|
|
@@ -4121,6 +4172,11 @@ export class Peer {
|
|
|
4121
4172
|
if (kind === PACKET_ID_FILE_CONTROL)
|
|
4122
4173
|
this.#fileRelayNegotiationUntil.delete(friendId);
|
|
4123
4174
|
// Toxcore file transfer (FILE_SENDREQUEST/CONTROL/DATA = 80/81/82).
|
|
4175
|
+
if (kind >= PACKET_ID_FILE_SENDREQUEST && kind <= PACKET_ID_FILE_FEC) {
|
|
4176
|
+
// A raw file packet arrived, so this peer speaks raw — pin it and stop
|
|
4177
|
+
// alternating toward DNFT1.
|
|
4178
|
+
this.#dnft1.noteInbound(friendId, false);
|
|
4179
|
+
}
|
|
4124
4180
|
if (this.#fileTransfer.handlePacket(friendId, kind, inner))
|
|
4125
4181
|
return;
|
|
4126
4182
|
if (kind === PACKET_ID_ONLINE) {
|
|
@@ -4319,6 +4375,18 @@ export class Peer {
|
|
|
4319
4375
|
else {
|
|
4320
4376
|
raw = inner;
|
|
4321
4377
|
}
|
|
4378
|
+
// DNFT1: a file-transfer packet carried inside a friend message (the
|
|
4379
|
+
// native-interop path — see compat/dnft1.ts). Like the Android binary
|
|
4380
|
+
// envelope below, this MUST run on the undecoded bytes: raw chunk bytes
|
|
4381
|
+
// do not survive a UTF-8 decode.
|
|
4382
|
+
const ft = parseDnft1Frame(raw);
|
|
4383
|
+
if (ft) {
|
|
4384
|
+
this.#dnft1.noteInbound(friendId, true);
|
|
4385
|
+
if (ft.packetId === PACKET_ID_FILE_CONTROL)
|
|
4386
|
+
this.#fileRelayNegotiationUntil.delete(friendId);
|
|
4387
|
+
this.#fileTransfer.handlePacket(friendId, ft.packetId, ft.payload);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4322
4390
|
if (this.#tryEmitBinaryInlineFile(friendId, raw, "online"))
|
|
4323
4391
|
return;
|
|
4324
4392
|
text = decodeUtf8Best(raw);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.141",
|
|
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",
|