@decentnetwork/peer 0.1.33 → 0.1.35
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.js +64 -4
- package/dist/types/peer.d.ts +12 -0
- package/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -2671,7 +2671,12 @@ export class Peer {
|
|
|
2671
2671
|
// re-punch survive a UDP outage. Brief UDP gaps just drop a few data
|
|
2672
2672
|
// packets (the proxied TCP stream retransmits) and the re-punch loop
|
|
2673
2673
|
// restores the direct path within a few seconds.
|
|
2674
|
-
|
|
2674
|
+
// Native chat (64) is always bulk; an app can register one extra custom
|
|
2675
|
+
// id (decentlan's IP channel = 163) via bulkDataPacketId so its
|
|
2676
|
+
// high-volume traffic gets the same single-transport routing instead of
|
|
2677
|
+
// fanning out over UDP + relay + TCP relay (which delivers 3-4 duplicates).
|
|
2678
|
+
const isBulkData = kind === PACKET_ID_MESSAGE ||
|
|
2679
|
+
(this.#opts.bulkDataPacketId !== undefined && kind === this.#opts.bulkDataPacketId);
|
|
2675
2680
|
await this.#sendToFriend(friendId, encrypted, session, isBulkData);
|
|
2676
2681
|
}
|
|
2677
2682
|
/**
|
|
@@ -3020,19 +3025,37 @@ export class Peer {
|
|
|
3020
3025
|
const relay = await this.#ensureTurnRelay().catch(() => undefined);
|
|
3021
3026
|
const relayOctets = relay ? relay.host.split(".").map((s) => parseInt(s, 10)) : undefined;
|
|
3022
3027
|
const relayValid = relayOctets && relayOctets.length === 4 && !relayOctets.some((o) => Number.isNaN(o));
|
|
3023
|
-
|
|
3028
|
+
// Third candidate (bytes 12-17): our primary private LAN address + local
|
|
3029
|
+
// UDP port. A same-LAN peer can punch this directly; without it the only
|
|
3030
|
+
// "direct" option we advertise is our srflx (public) address, which the
|
|
3031
|
+
// NAT must hairpin back onto the LAN — most don't, so two machines in one
|
|
3032
|
+
// office silently fall back to the TURN relay (~260ms for a <1ms hop).
|
|
3033
|
+
const lanIp = getLocalIpv4Addresses().find((ip) => isPrivateAddress(ip));
|
|
3034
|
+
const lanOctets = lanIp ? lanIp.split(".").map((s) => parseInt(s, 10)) : undefined;
|
|
3035
|
+
const lanPort = this.#udp.localPort() ?? 0;
|
|
3036
|
+
const lanValid = !!lanOctets && lanOctets.length === 4 && !lanOctets.some((o) => Number.isNaN(o)) && lanPort > 0;
|
|
3037
|
+
const size = lanValid ? 18 : relayValid ? 12 : 6;
|
|
3038
|
+
const payload = new Uint8Array(size);
|
|
3024
3039
|
payload.set(octets, 0);
|
|
3025
3040
|
payload[4] = (srflx.port >> 8) & 0xff;
|
|
3026
3041
|
payload[5] = srflx.port & 0xff;
|
|
3027
|
-
|
|
3042
|
+
// relay slot (bytes 6-11): filled when we have a relay, else left zero
|
|
3043
|
+
// (receiver skips port 0) so the host candidate can still occupy 12-17.
|
|
3044
|
+
if (relayValid && relay && size >= 12) {
|
|
3028
3045
|
payload.set(relayOctets, 6);
|
|
3029
3046
|
payload[10] = (relay.port >> 8) & 0xff;
|
|
3030
3047
|
payload[11] = relay.port & 0xff;
|
|
3031
3048
|
}
|
|
3049
|
+
if (lanValid) {
|
|
3050
|
+
payload.set(lanOctets, 12);
|
|
3051
|
+
payload[16] = (lanPort >> 8) & 0xff;
|
|
3052
|
+
payload[17] = lanPort & 0xff;
|
|
3053
|
+
}
|
|
3032
3054
|
try {
|
|
3033
3055
|
await this.#sendMessengerPacket(friendId, PACKET_ID_UDP_ENDPOINT, payload);
|
|
3034
3056
|
this.#debugLog(`udp-endpoint offer sent to ${friendId}: direct=${srflx.host}:${srflx.port}` +
|
|
3035
|
-
(relayValid && relay ? ` relay=${relay.host}:${relay.port}` : "")
|
|
3057
|
+
(relayValid && relay ? ` relay=${relay.host}:${relay.port}` : "") +
|
|
3058
|
+
(lanValid ? ` lan=${lanIp}:${lanPort}` : ""));
|
|
3036
3059
|
}
|
|
3037
3060
|
catch {
|
|
3038
3061
|
// best-effort — the retry loop will try again
|
|
@@ -3083,6 +3106,43 @@ export class Peer {
|
|
|
3083
3106
|
}
|
|
3084
3107
|
}
|
|
3085
3108
|
}
|
|
3109
|
+
// LAN host candidate (bytes 12-17): the peer's private LAN address. Only
|
|
3110
|
+
// safe to try when it falls in OUR subnet — same physical LAN — otherwise
|
|
3111
|
+
// a peer's 192.168.1.x could collide with an unrelated host on our own
|
|
3112
|
+
// network. When it matches it's the fastest possible path (direct, no NAT,
|
|
3113
|
+
// no relay), so punch it and add it as a same-LAN endpoint candidate, which
|
|
3114
|
+
// #collectSessionEndpointCandidates prioritises for the session path. We
|
|
3115
|
+
// prefer it as session.remote (when we have no confirmed real UDP yet) so
|
|
3116
|
+
// the very next keepalive upgrades the path off the relay.
|
|
3117
|
+
if (payload.length >= 18) {
|
|
3118
|
+
const lanHost = `${payload[12]}.${payload[13]}.${payload[14]}.${payload[15]}`;
|
|
3119
|
+
const lanPort = ((payload[16] << 8) | payload[17]) >>> 0;
|
|
3120
|
+
const sameLan = lanPort !== 0 &&
|
|
3121
|
+
isPrivateAddress(lanHost) &&
|
|
3122
|
+
getLocalIpv4Subnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
|
|
3123
|
+
!(getLocalIpv4Addresses().includes(lanHost) && this.#udp.localPort() === lanPort);
|
|
3124
|
+
if (sameLan) {
|
|
3125
|
+
this.#rememberEndpointCandidate(session, lanHost, lanPort);
|
|
3126
|
+
this.#debugLog(`udp-endpoint LAN candidate from ${friendId}: ${lanHost}:${lanPort} (same subnet) — punching`);
|
|
3127
|
+
const lanPunch = Uint8Array.of(0xf2);
|
|
3128
|
+
let ln = 0;
|
|
3129
|
+
const lanTimer = setInterval(() => {
|
|
3130
|
+
this.#udp.sendDirectSync(Buffer.from(lanPunch), lanHost, lanPort);
|
|
3131
|
+
if (++ln >= 6)
|
|
3132
|
+
clearInterval(lanTimer);
|
|
3133
|
+
}, 120);
|
|
3134
|
+
const haveRealUdp = session.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
|
|
3135
|
+
if (!haveRealUdp) {
|
|
3136
|
+
session.remote = { host: lanHost, port: lanPort };
|
|
3137
|
+
}
|
|
3138
|
+
if (session.established) {
|
|
3139
|
+
void this.#sendMessengerPacket(friendId, PACKET_ID_ALIVE, new Uint8Array()).catch(() => undefined);
|
|
3140
|
+
}
|
|
3141
|
+
else {
|
|
3142
|
+
void this.#initiateSession(friendId).catch(() => undefined);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3086
3146
|
// Don't punch toward ourselves.
|
|
3087
3147
|
if (getLocalIpv4Addresses().includes(host) && this.#udp.localPort() === port)
|
|
3088
3148
|
return;
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -28,6 +28,18 @@ export type PeerOptions = {
|
|
|
28
28
|
* apps that genuinely want offline message delivery leave it false.
|
|
29
29
|
*/
|
|
30
30
|
expressControlPlaneOnly?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Custom packet id carrying the app's high-volume "bulk data" stream
|
|
33
|
+
* (e.g. decentlan's IP-forwarding channel). Packets sent on this id get
|
|
34
|
+
* the same single-transport routing as native chat (PACKET_ID_MESSAGE):
|
|
35
|
+
* they ride the fresh direct UDP path exclusively, or bridge over the
|
|
36
|
+
* relay when it's stale — instead of fanning out over UDP + relay + TCP
|
|
37
|
+
* relay at once (which delivers 3-4 duplicates of every packet and backs
|
|
38
|
+
* up the relay). Apps that put bulk traffic on a custom id (decentlan
|
|
39
|
+
* uses 163) MUST set this so the SDK recognises it; otherwise the bulk
|
|
40
|
+
* optimisation only applies to packet 64 and custom-id data is duplicated.
|
|
41
|
+
*/
|
|
42
|
+
bulkDataPacketId?: number;
|
|
31
43
|
compatibilityMode?: CompatibilityMode;
|
|
32
44
|
debugLabel?: string;
|
|
33
45
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
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",
|