@decentnetwork/peer 0.1.93 → 0.1.95
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/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -358,6 +358,8 @@ export class Peer {
|
|
|
358
358
|
// Per-friend tracking sets so we send our nickname/status/greeting once
|
|
359
359
|
// per session rather than on every PACKET_ID_ONLINE arrival.
|
|
360
360
|
#profileSentTo = new Set();
|
|
361
|
+
#profileRetryAttempts = new Map();
|
|
362
|
+
#profileRetryTimers = new Map();
|
|
361
363
|
#greetingSentTo = new Set();
|
|
362
364
|
#selfAnnouncePromise;
|
|
363
365
|
#selfAnnouncePauseDepth = 0;
|
|
@@ -595,6 +597,10 @@ export class Peer {
|
|
|
595
597
|
async stop() {
|
|
596
598
|
// Stop background producers first so no keepalive/profile packet can race
|
|
597
599
|
// behind the shutdown notice and make the remote session look live again.
|
|
600
|
+
for (const timer of this.#profileRetryTimers.values())
|
|
601
|
+
clearTimeout(timer);
|
|
602
|
+
this.#profileRetryTimers.clear();
|
|
603
|
+
this.#profileRetryAttempts.clear();
|
|
598
604
|
if (this.#expressPollTimer) {
|
|
599
605
|
clearInterval(this.#expressPollTimer);
|
|
600
606
|
this.#expressPollTimer = undefined;
|
|
@@ -981,6 +987,11 @@ export class Peer {
|
|
|
981
987
|
this.#tcpOnlyWarningShown.delete(friendId);
|
|
982
988
|
this.#noEndpointWarned.delete(friendId);
|
|
983
989
|
this.#profileSentTo.delete(friendId);
|
|
990
|
+
const profileRetry = this.#profileRetryTimers.get(friendId);
|
|
991
|
+
if (profileRetry)
|
|
992
|
+
clearTimeout(profileRetry);
|
|
993
|
+
this.#profileRetryTimers.delete(friendId);
|
|
994
|
+
this.#profileRetryAttempts.delete(friendId);
|
|
984
995
|
this.#greetingSentTo.delete(friendId);
|
|
985
996
|
if (existed) {
|
|
986
997
|
this.#persistFriends();
|
|
@@ -1760,10 +1771,19 @@ export class Peer {
|
|
|
1760
1771
|
const now = Date.now();
|
|
1761
1772
|
const peerSendingUndecryptable = state.undecryptableRecvMs !== undefined &&
|
|
1762
1773
|
now - state.undecryptableRecvMs < FRIEND_TIMEOUT_MS;
|
|
1774
|
+
// A duplicate handshake can also be the first packet from a freshly
|
|
1775
|
+
// restarted peer whose KILL notice was lost during shutdown. The
|
|
1776
|
+
// holder still has the peer's old accepted session shell, while the
|
|
1777
|
+
// restarted process needs our handshake reply before it can derive the
|
|
1778
|
+
// shared key. Re-send our CURRENT handshake (same key, so no local
|
|
1779
|
+
// reset) even if we also initiated a cookie chain: after a blackout
|
|
1780
|
+
// both sides commonly initiate at once, so `weInitiated` cannot tell a
|
|
1781
|
+
// request from a reply here. The 2s rate limit bounds duplicate traffic;
|
|
1782
|
+
// the immediate reply cannot bounce because it arrives inside the
|
|
1783
|
+
// limit. This is also the normal lost-reply recovery path.
|
|
1763
1784
|
const resyncDue = now - (state.lastResyncHandshakeMs ?? 0) > 2000;
|
|
1764
|
-
if (
|
|
1785
|
+
if (resyncDue) {
|
|
1765
1786
|
state.lastResyncHandshakeMs = now;
|
|
1766
|
-
state.lastPingRecvMs = now;
|
|
1767
1787
|
const reply = createCryptoHandshake({
|
|
1768
1788
|
recipientCookie: hs.embeddedCookie,
|
|
1769
1789
|
baseNonce: state.ourBaseNonce,
|
|
@@ -1779,11 +1799,16 @@ export class Peer {
|
|
|
1779
1799
|
? Promise.resolve(this.#tcpRelays?.sendOobToFriend(hs.senderDhtPublicKey, reply) ?? 0)
|
|
1780
1800
|
: this.#sendPacket(reply, { host: remote.address, port: remote.port });
|
|
1781
1801
|
void sendResync()
|
|
1782
|
-
.then(() => this.#debugLog(`hs_recv duplicate
|
|
1802
|
+
.then(() => this.#debugLog(`hs_recv duplicate — re-sent our handshake to resync friend=${friendId} ` +
|
|
1803
|
+
`reason=${peerSendingUndecryptable ? "undecryptable-data" : "duplicate-handshake"}`))
|
|
1783
1804
|
.catch((error) => this.#debugLog(`resync handshake failed for ${friendId}: ${error.message}`));
|
|
1784
1805
|
return;
|
|
1785
1806
|
}
|
|
1786
|
-
|
|
1807
|
+
// A handshake proves reachability, not that this net_crypto session can
|
|
1808
|
+
// decrypt data. Refreshing lastPingRecvMs here kept a stale session
|
|
1809
|
+
// immortal: the restarted peer retransmitted handshakes every second,
|
|
1810
|
+
// and each retransmit postponed the 32s dead-session breaker forever.
|
|
1811
|
+
// Only successfully decrypted CRYPTO_DATA may refresh liveness.
|
|
1787
1812
|
this.#debugVerboseLog(`hs_recv duplicate friend=${friendId} remote=${remote.address}:${remote.port} (session preserved)`);
|
|
1788
1813
|
return;
|
|
1789
1814
|
}
|
|
@@ -3978,6 +4003,10 @@ export class Peer {
|
|
|
3978
4003
|
if (info.description !== undefined)
|
|
3979
4004
|
opts.statusMessage = info.description;
|
|
3980
4005
|
this.#profileSentTo.clear();
|
|
4006
|
+
for (const timer of this.#profileRetryTimers.values())
|
|
4007
|
+
clearTimeout(timer);
|
|
4008
|
+
this.#profileRetryTimers.clear();
|
|
4009
|
+
this.#profileRetryAttempts.clear();
|
|
3981
4010
|
for (const [friendId, s] of this.#friendSessions.entries()) {
|
|
3982
4011
|
if (s.established)
|
|
3983
4012
|
this.#sendProfileAndGreeting(friendId);
|
|
@@ -3991,6 +4020,25 @@ export class Peer {
|
|
|
3991
4020
|
description: opts.statusMessage ?? PEER_STATUS_MESSAGE,
|
|
3992
4021
|
};
|
|
3993
4022
|
}
|
|
4023
|
+
#scheduleProfileRetry(friendId) {
|
|
4024
|
+
if (this.#profileRetryTimers.has(friendId))
|
|
4025
|
+
return;
|
|
4026
|
+
const session = this.#friendSessions.get(friendId);
|
|
4027
|
+
if (!this.#started || !session?.established)
|
|
4028
|
+
return;
|
|
4029
|
+
const attempt = (this.#profileRetryAttempts.get(friendId) ?? 0) + 1;
|
|
4030
|
+
this.#profileRetryAttempts.set(friendId, attempt);
|
|
4031
|
+
const delayMs = Math.min(30_000, 1_000 * (2 ** Math.min(attempt - 1, 5)));
|
|
4032
|
+
const timer = setTimeout(() => {
|
|
4033
|
+
this.#profileRetryTimers.delete(friendId);
|
|
4034
|
+
if (this.#started && this.#friendSessions.get(friendId)?.established) {
|
|
4035
|
+
this.#sendProfileAndGreeting(friendId);
|
|
4036
|
+
}
|
|
4037
|
+
}, delayMs);
|
|
4038
|
+
timer.unref?.();
|
|
4039
|
+
this.#profileRetryTimers.set(friendId, timer);
|
|
4040
|
+
this.#debugLog(`profile retry ${attempt} for ${friendId} scheduled in ${delayMs}ms`);
|
|
4041
|
+
}
|
|
3994
4042
|
#sendProfileAndGreeting(friendId) {
|
|
3995
4043
|
if (!this.#profileSentTo.has(friendId)) {
|
|
3996
4044
|
this.#profileSentTo.add(friendId);
|
|
@@ -4030,9 +4078,16 @@ export class Peer {
|
|
|
4030
4078
|
});
|
|
4031
4079
|
await this.#sendMessengerPacket(friendId, PACKET_ID_STATUSMESSAGE, userInfo);
|
|
4032
4080
|
this.#debugLog(`userinfo sent to ${friendId} (name="${nick}", descr="${descr}", proto=${AGENTNET_PROTO_VERSION}, platform=${this.#opts.platform ?? process.platform})`);
|
|
4081
|
+
this.#profileRetryAttempts.delete(friendId);
|
|
4033
4082
|
}
|
|
4034
4083
|
catch (error) {
|
|
4084
|
+
// PACKET_ID_ONLINE can arrive a fraction before every transport is
|
|
4085
|
+
// ready to accept messenger packets. Do not leave the optimistic
|
|
4086
|
+
// "sent" marker wedged after that race: re-arm it and retry with
|
|
4087
|
+
// bounded backoff while this same session remains established.
|
|
4088
|
+
this.#profileSentTo.delete(friendId);
|
|
4035
4089
|
this.#debugLog(`send profile failed for ${friendId}: ${error.message}`);
|
|
4090
|
+
this.#scheduleProfileRetry(friendId);
|
|
4036
4091
|
}
|
|
4037
4092
|
})();
|
|
4038
4093
|
}
|
|
@@ -5364,6 +5419,11 @@ export class Peer {
|
|
|
5364
5419
|
// transitions prevents the user from seeing the same greeting message
|
|
5365
5420
|
// repeated each time the session bounces.
|
|
5366
5421
|
this.#profileSentTo.delete(friendId);
|
|
5422
|
+
const profileRetry = this.#profileRetryTimers.get(friendId);
|
|
5423
|
+
if (profileRetry)
|
|
5424
|
+
clearTimeout(profileRetry);
|
|
5425
|
+
this.#profileRetryTimers.delete(friendId);
|
|
5426
|
+
this.#profileRetryAttempts.delete(friendId);
|
|
5367
5427
|
const friend = this.#friends.get(friendId);
|
|
5368
5428
|
if (!friend) {
|
|
5369
5429
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.95",
|
|
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",
|