@decentnetwork/peer 0.1.3 → 0.1.5
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 +22 -0
- package/dist/peer.js +77 -5
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -71,6 +71,28 @@ export declare class Peer {
|
|
|
71
71
|
hasTcpRoute: boolean;
|
|
72
72
|
transport: "udp" | "tcp-relay" | "both" | "none";
|
|
73
73
|
lastPingRecvMs: number | null;
|
|
74
|
+
/** OS-assigned UDP port THIS node is listening on. Useful to confirm
|
|
75
|
+
* we even have a UDP socket bound. */
|
|
76
|
+
ourLocalUdpPort: number | null;
|
|
77
|
+
/** Cached UDP endpoint for the peer from a previous online state
|
|
78
|
+
* (persisted across restarts). Set != null means UDP holepunch
|
|
79
|
+
* has succeeded at some point — but null when we've only ever
|
|
80
|
+
* reached them via TCP-relay. Tells us whether discovery has
|
|
81
|
+
* ever found a usable UDP endpoint. */
|
|
82
|
+
friendUdpEndpoint: {
|
|
83
|
+
host: string;
|
|
84
|
+
port: number;
|
|
85
|
+
} | null;
|
|
86
|
+
/** UDP endpoints picked up by recent DHT discovery for this peer,
|
|
87
|
+
* whether or not the cookie exchange has succeeded yet. If this
|
|
88
|
+
* is empty AND friendUdpEndpoint is null AND we keep getting
|
|
89
|
+
* tcp-relay, discovery itself is broken — we don't even know
|
|
90
|
+
* where to send a cookie request. */
|
|
91
|
+
endpointCandidatesCount: number;
|
|
92
|
+
/** Last time we sent an outbound cookie request via UDP for this
|
|
93
|
+
* peer (null = never). Used to confirm Phase 1.2 retries are
|
|
94
|
+
* actually firing. */
|
|
95
|
+
cookieRequestSentMs: number | null;
|
|
74
96
|
} | null;
|
|
75
97
|
waitForFriendRequest(timeoutMs?: number): Promise<FriendRequest>;
|
|
76
98
|
}
|
package/dist/peer.js
CHANGED
|
@@ -772,6 +772,7 @@ export class Peer {
|
|
|
772
772
|
const s = this.#friendSessions.get(pubkey);
|
|
773
773
|
if (!s)
|
|
774
774
|
return null;
|
|
775
|
+
const friend = this.#friends.get(pubkey);
|
|
775
776
|
// session.remote can be a synthetic `tcp:<dhtpk>:0` placeholder
|
|
776
777
|
// when the TCP relay path reports an endpoint with no real UDP
|
|
777
778
|
// address; treat those as no-UDP for reporting.
|
|
@@ -785,12 +786,22 @@ export class Peer {
|
|
|
785
786
|
: s.hasTcpRoute
|
|
786
787
|
? "tcp-relay"
|
|
787
788
|
: "none";
|
|
789
|
+
const friendUdp = friend?.remoteHost &&
|
|
790
|
+
friend.remotePort &&
|
|
791
|
+
!friend.remoteHost.startsWith("tcp:") &&
|
|
792
|
+
friend.remotePort !== 0
|
|
793
|
+
? { host: friend.remoteHost, port: friend.remotePort }
|
|
794
|
+
: null;
|
|
788
795
|
return {
|
|
789
796
|
established: s.established === true,
|
|
790
797
|
udpRemote: realUdp,
|
|
791
798
|
hasTcpRoute: s.hasTcpRoute === true,
|
|
792
799
|
transport,
|
|
793
800
|
lastPingRecvMs: s.lastPingRecvMs ?? null,
|
|
801
|
+
ourLocalUdpPort: this.#udp?.localPort() ?? null,
|
|
802
|
+
friendUdpEndpoint: friendUdp,
|
|
803
|
+
endpointCandidatesCount: s.endpointCandidates?.length ?? 0,
|
|
804
|
+
cookieRequestSentMs: s.cookieRequestSentMs ?? null,
|
|
794
805
|
};
|
|
795
806
|
}
|
|
796
807
|
waitForFriendRequest(timeoutMs = 30000) {
|
|
@@ -2777,10 +2788,73 @@ export class Peer {
|
|
|
2777
2788
|
// residential ISPs where self-announce doesn't propagate).
|
|
2778
2789
|
// Mirrors toxcore's send_dht_pk_packet which calls
|
|
2779
2790
|
// tcp_copy_connected_relays to fill extras.
|
|
2780
|
-
|
|
2791
|
+
// Also pack OUR OWN UDP endpoint(s) as extras (family 0x02 =
|
|
2792
|
+
// UDP_FAMILY_IPV4). Without this, peers receiving our DHT-PK push
|
|
2793
|
+
// learn our TCP relays but have NO IDEA where to send UDP cookie
|
|
2794
|
+
// requests — so they never even try UDP holepunch and every
|
|
2795
|
+
// session stays on tcp-relay (~500ms+ RTT) instead of the
|
|
2796
|
+
// ~80ms UDP path. Observed in the wild: even same-LAN peers
|
|
2797
|
+
// (snoopy and mac-dev on the same /24) stuck on tcp-relay
|
|
2798
|
+
// forever because neither side ever announces a UDP endpoint
|
|
2799
|
+
// to the other.
|
|
2800
|
+
//
|
|
2801
|
+
// For peers behind NAT, the LAN address we advertise is
|
|
2802
|
+
// reachable by same-LAN peers and harmless for WAN peers
|
|
2803
|
+
// (they fall through to tcp-relay as before). For peers with
|
|
2804
|
+
// a public IP (e.g. a Vultr VPS), the LAN address IS the
|
|
2805
|
+
// reachable one; same-format works. STUN-style reflexive
|
|
2806
|
+
// discovery is the next layer but isn't required to fix the
|
|
2807
|
+
// LAN case immediately.
|
|
2808
|
+
const packed = [];
|
|
2809
|
+
const ourUdpPort = this.#udp?.localPort();
|
|
2810
|
+
if (ourUdpPort && ourUdpPort > 0) {
|
|
2811
|
+
const ourDhtPk = this.#keyPair?.publicKey;
|
|
2812
|
+
if (ourDhtPk && ourDhtPk.length === 32) {
|
|
2813
|
+
// Enumerate non-loopback IPv4 addresses on this host.
|
|
2814
|
+
// We use os.networkInterfaces() rather than guessing — this
|
|
2815
|
+
// covers a multi-homed peer with several reachable IPs.
|
|
2816
|
+
try {
|
|
2817
|
+
const ifaces = (await import("os")).networkInterfaces();
|
|
2818
|
+
const seenIps = new Set();
|
|
2819
|
+
for (const iface of Object.values(ifaces)) {
|
|
2820
|
+
if (!iface)
|
|
2821
|
+
continue;
|
|
2822
|
+
for (const addr of iface) {
|
|
2823
|
+
if (addr.family !== "IPv4")
|
|
2824
|
+
continue;
|
|
2825
|
+
if (addr.internal)
|
|
2826
|
+
continue;
|
|
2827
|
+
if (addr.address === "0.0.0.0")
|
|
2828
|
+
continue;
|
|
2829
|
+
if (seenIps.has(addr.address))
|
|
2830
|
+
continue;
|
|
2831
|
+
seenIps.add(addr.address);
|
|
2832
|
+
const parts = addr.address
|
|
2833
|
+
.split(".")
|
|
2834
|
+
.map((p) => Number.parseInt(p, 10));
|
|
2835
|
+
if (parts.length !== 4 || parts.some((n) => !(n >= 0 && n <= 255)))
|
|
2836
|
+
continue;
|
|
2837
|
+
const entry = new Uint8Array(1 + 4 + 2 + 32);
|
|
2838
|
+
entry[0] = 0x02; // UDP_FAMILY_IPV4
|
|
2839
|
+
entry[1] = parts[0];
|
|
2840
|
+
entry[2] = parts[1];
|
|
2841
|
+
entry[3] = parts[2];
|
|
2842
|
+
entry[4] = parts[3];
|
|
2843
|
+
entry[5] = (ourUdpPort >> 8) & 0xff;
|
|
2844
|
+
entry[6] = ourUdpPort & 0xff;
|
|
2845
|
+
entry.set(ourDhtPk, 7);
|
|
2846
|
+
packed.push(entry);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
catch {
|
|
2851
|
+
// os import or interface enumeration failed; skip UDP extras
|
|
2852
|
+
// and let TCP relay carry the session as before.
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2781
2856
|
if (this.#tcpRelays) {
|
|
2782
2857
|
const relays = this.#tcpRelays.connectedRelays(3); // MAX_SHARED_RELAYS
|
|
2783
|
-
const packed = [];
|
|
2784
2858
|
for (const r of relays) {
|
|
2785
2859
|
// Only IPv4 TCP entries supported here; matches what we parse.
|
|
2786
2860
|
// Format per packed-nodes spec: family(1) + ipv4(4) + port(2 BE) + pk(32)
|
|
@@ -2798,10 +2872,8 @@ export class Peer {
|
|
|
2798
2872
|
entry.set(r.serverPublicKey, 7);
|
|
2799
2873
|
packed.push(entry);
|
|
2800
2874
|
}
|
|
2801
|
-
if (packed.length > 0) {
|
|
2802
|
-
extras = concatBytes(packed);
|
|
2803
|
-
}
|
|
2804
2875
|
}
|
|
2876
|
+
const extras = packed.length > 0 ? concatBytes(packed) : new Uint8Array(0);
|
|
2805
2877
|
const innerPayload = concatBytes([
|
|
2806
2878
|
noReplayBytes,
|
|
2807
2879
|
this.#keyPair.publicKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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",
|