@decentnetwork/peer 0.1.0 → 0.1.2
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 +31 -0
- package/dist/peer.js +119 -0
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -41,5 +41,36 @@ export declare class Peer {
|
|
|
41
41
|
onFriendConnection(cb: (ev: FriendConnectionEvent) => void): void;
|
|
42
42
|
onFriendInfo(cb: (ev: FriendInfoEvent) => void): void;
|
|
43
43
|
friends(): FriendRecord[];
|
|
44
|
+
/**
|
|
45
|
+
* Read-only snapshot of the live net_crypto session state for a
|
|
46
|
+
* friend, or null if no session has been established yet. Lets
|
|
47
|
+
* callers see whether a peer is reachable via direct UDP, only via
|
|
48
|
+
* TCP relay, or not at all — the answer is the difference between
|
|
49
|
+
* ~80ms RTT (UDP) and ~500ms+ RTT (relay) in practice, so the
|
|
50
|
+
* caller (e.g. `agentnet diag`) can show an operator where their
|
|
51
|
+
* latency is coming from. Returns:
|
|
52
|
+
*
|
|
53
|
+
* established — handshake complete on this side
|
|
54
|
+
* udpRemote — direct UDP endpoint we've seen the peer at,
|
|
55
|
+
* if any; null means UDP holepunch hasn't
|
|
56
|
+
* succeeded (yet) so traffic falls back to TCP
|
|
57
|
+
* relay
|
|
58
|
+
* hasTcpRoute — true when a TCP relay has reported a route to
|
|
59
|
+
* the peer
|
|
60
|
+
* transport — convenience derived field: which path the next
|
|
61
|
+
* outbound packet will actually use
|
|
62
|
+
* lastPingRecvMs — for staleness; if older than ~32s the session
|
|
63
|
+
* is on its way to timeout
|
|
64
|
+
*/
|
|
65
|
+
sessionStatus(pubkey: string): {
|
|
66
|
+
established: boolean;
|
|
67
|
+
udpRemote: {
|
|
68
|
+
host: string;
|
|
69
|
+
port: number;
|
|
70
|
+
} | null;
|
|
71
|
+
hasTcpRoute: boolean;
|
|
72
|
+
transport: "udp" | "tcp-relay" | "both" | "none";
|
|
73
|
+
lastPingRecvMs: number | null;
|
|
74
|
+
} | null;
|
|
44
75
|
waitForFriendRequest(timeoutMs?: number): Promise<FriendRequest>;
|
|
45
76
|
}
|
package/dist/peer.js
CHANGED
|
@@ -747,6 +747,52 @@ export class Peer {
|
|
|
747
747
|
friends() {
|
|
748
748
|
return [...this.#friends.values()];
|
|
749
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Read-only snapshot of the live net_crypto session state for a
|
|
752
|
+
* friend, or null if no session has been established yet. Lets
|
|
753
|
+
* callers see whether a peer is reachable via direct UDP, only via
|
|
754
|
+
* TCP relay, or not at all — the answer is the difference between
|
|
755
|
+
* ~80ms RTT (UDP) and ~500ms+ RTT (relay) in practice, so the
|
|
756
|
+
* caller (e.g. `agentnet diag`) can show an operator where their
|
|
757
|
+
* latency is coming from. Returns:
|
|
758
|
+
*
|
|
759
|
+
* established — handshake complete on this side
|
|
760
|
+
* udpRemote — direct UDP endpoint we've seen the peer at,
|
|
761
|
+
* if any; null means UDP holepunch hasn't
|
|
762
|
+
* succeeded (yet) so traffic falls back to TCP
|
|
763
|
+
* relay
|
|
764
|
+
* hasTcpRoute — true when a TCP relay has reported a route to
|
|
765
|
+
* the peer
|
|
766
|
+
* transport — convenience derived field: which path the next
|
|
767
|
+
* outbound packet will actually use
|
|
768
|
+
* lastPingRecvMs — for staleness; if older than ~32s the session
|
|
769
|
+
* is on its way to timeout
|
|
770
|
+
*/
|
|
771
|
+
sessionStatus(pubkey) {
|
|
772
|
+
const s = this.#friendSessions.get(pubkey);
|
|
773
|
+
if (!s)
|
|
774
|
+
return null;
|
|
775
|
+
// session.remote can be a synthetic `tcp:<dhtpk>:0` placeholder
|
|
776
|
+
// when the TCP relay path reports an endpoint with no real UDP
|
|
777
|
+
// address; treat those as no-UDP for reporting.
|
|
778
|
+
const realUdp = s.remote && !s.remote.host?.startsWith("tcp:") && s.remote.port !== 0
|
|
779
|
+
? { host: s.remote.host, port: s.remote.port }
|
|
780
|
+
: null;
|
|
781
|
+
const transport = realUdp
|
|
782
|
+
? s.hasTcpRoute
|
|
783
|
+
? "both"
|
|
784
|
+
: "udp"
|
|
785
|
+
: s.hasTcpRoute
|
|
786
|
+
? "tcp-relay"
|
|
787
|
+
: "none";
|
|
788
|
+
return {
|
|
789
|
+
established: s.established === true,
|
|
790
|
+
udpRemote: realUdp,
|
|
791
|
+
hasTcpRoute: s.hasTcpRoute === true,
|
|
792
|
+
transport,
|
|
793
|
+
lastPingRecvMs: s.lastPingRecvMs ?? null,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
750
796
|
waitForFriendRequest(timeoutMs = 30000) {
|
|
751
797
|
return new Promise((resolve, reject) => {
|
|
752
798
|
const timer = setTimeout(() => {
|
|
@@ -1984,6 +2030,79 @@ export class Peer {
|
|
|
1984
2030
|
if (session.remote && friend.status !== "online") {
|
|
1985
2031
|
this.#setFriendOnline(friendId, session.remote.host, session.remote.port);
|
|
1986
2032
|
}
|
|
2033
|
+
// Phase 1.2: keep retrying UDP holepunch even after the session
|
|
2034
|
+
// is already "established" via TCP relay. The toxcore-derived
|
|
2035
|
+
// logic above stops trying once anybody is reachable, which is
|
|
2036
|
+
// correct for messaging (text gets through) but disastrous for
|
|
2037
|
+
// IP-packet forwarding (latency stays at ~500-1500ms via the
|
|
2038
|
+
// relay detour instead of dropping to ~80ms once UDP works).
|
|
2039
|
+
//
|
|
2040
|
+
// We detect "on TCP-relay only" as: session.hasTcpRoute is set
|
|
2041
|
+
// AND session.remote is either null or the synthetic
|
|
2042
|
+
// {host: "tcp:<dhtpk>", port: 0} placeholder. In that state we
|
|
2043
|
+
// re-send our DHT-PK so the peer (re-)learns our UDP endpoint,
|
|
2044
|
+
// re-discover their endpoint via DHT, and trigger a fresh
|
|
2045
|
+
// #initiateSession that will send a UDP cookie request. If UDP
|
|
2046
|
+
// succeeds, #handleCryptoHandshake overwrites session.remote
|
|
2047
|
+
// with the real endpoint and #sendMessengerPacket picks UDP
|
|
2048
|
+
// over TCP from then on. Throttled to UDP_RETRY_INTERVAL_MS so
|
|
2049
|
+
// the loop's 250ms tick doesn't flood DHT lookups.
|
|
2050
|
+
const realUdpRemote = session.remote &&
|
|
2051
|
+
!session.remote.host?.startsWith("tcp:") &&
|
|
2052
|
+
session.remote.port !== 0;
|
|
2053
|
+
if (!realUdpRemote && session.hasTcpRoute) {
|
|
2054
|
+
const UDP_RETRY_INTERVAL_MS = 15_000;
|
|
2055
|
+
const lastTry = session.lastUdpRetryMs ?? 0;
|
|
2056
|
+
if (now - lastTry > UDP_RETRY_INTERVAL_MS) {
|
|
2057
|
+
session.lastUdpRetryMs = now;
|
|
2058
|
+
// Recompute friend's real public key for the DHT-PK push.
|
|
2059
|
+
// Same fallback chain as the cooldown branch below.
|
|
2060
|
+
let friendRealPk = session.friendRealPublicKey;
|
|
2061
|
+
if (!friendRealPk && friend.address) {
|
|
2062
|
+
try {
|
|
2063
|
+
friendRealPk = parseCarrierAddress(friend.address).publicKey;
|
|
2064
|
+
}
|
|
2065
|
+
catch {
|
|
2066
|
+
// Fall through to the userid form.
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (!friendRealPk && friend.pubkey) {
|
|
2070
|
+
try {
|
|
2071
|
+
friendRealPk = base58ToBytes(friend.pubkey);
|
|
2072
|
+
}
|
|
2073
|
+
catch {
|
|
2074
|
+
// Ignore malformed.
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
if (friendRealPk && friendRealPk.length === 32) {
|
|
2078
|
+
// 1. Re-announce our DHT-PK so peer learns *our* UDP endpoint
|
|
2079
|
+
// even if their own DHT lookup against us was stale.
|
|
2080
|
+
void this.#sendOnionDhtPk(friendRealPk).catch((error) => {
|
|
2081
|
+
this.#debugLog(`UDP retry: dhtpk_send for ${friendId} failed: ${error.message}`);
|
|
2082
|
+
});
|
|
2083
|
+
// 2. Try to discover the peer's UDP endpoint and kick a fresh
|
|
2084
|
+
// cookie request via UDP. If it lands, session.remote
|
|
2085
|
+
// becomes a real UDP endpoint and the next outbound
|
|
2086
|
+
// packet goes direct.
|
|
2087
|
+
const dhtPk = session.friendDhtPublicKey;
|
|
2088
|
+
if (dhtPk) {
|
|
2089
|
+
void this.#discoverAndCacheFriendEndpoint(friendId, dhtPk)
|
|
2090
|
+
.then((found) => {
|
|
2091
|
+
if (found) {
|
|
2092
|
+
return this.#initiateSession(friendId).catch(() => undefined);
|
|
2093
|
+
}
|
|
2094
|
+
})
|
|
2095
|
+
.catch(() => undefined);
|
|
2096
|
+
}
|
|
2097
|
+
else if (friend.remoteHost && friend.remotePort) {
|
|
2098
|
+
// We already have a real UDP endpoint cached — just
|
|
2099
|
+
// retry the cookie request to it.
|
|
2100
|
+
void this.#initiateSession(friendId).catch(() => undefined);
|
|
2101
|
+
}
|
|
2102
|
+
this.#debugLog(`UDP retry attempt for ${friendId} (currently on TCP relay)`);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1987
2106
|
continue;
|
|
1988
2107
|
}
|
|
1989
2108
|
// No active session: tell the friend our DHT public key so they can
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|