@decentnetwork/peer 0.1.25 → 0.1.27
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 +65 -13
- package/package.json +1 -1
package/dist/peer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile, rename } from "node:fs/promises";
|
|
3
3
|
import { networkInterfaces } from "node:os";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import nacl from "tweetnacl";
|
|
@@ -168,6 +168,7 @@ export class Peer {
|
|
|
168
168
|
#pendingFriendRequests = new Map();
|
|
169
169
|
#friends = new Map();
|
|
170
170
|
#friendStoreFile;
|
|
171
|
+
#persistSeq = 0; // makes atomic friend-store temp filenames unique per write
|
|
171
172
|
#cookieSymmetricKey;
|
|
172
173
|
#friendSessions = new Map();
|
|
173
174
|
#express;
|
|
@@ -610,20 +611,33 @@ export class Peer {
|
|
|
610
611
|
errors.push(`${route.node.host}:${route.node.port} ${error.message}`);
|
|
611
612
|
}
|
|
612
613
|
}
|
|
613
|
-
|
|
614
|
+
// Record locally so the connection loop keeps retrying, then ALWAYS
|
|
615
|
+
// queue via express — even when every onion send failed. Onion delivery
|
|
616
|
+
// is unreliable (broken DHT onion-announce), and the old code threw on
|
|
617
|
+
// `sent === 0` BEFORE this express fallback ran, so a friend-request to
|
|
618
|
+
// a peer with stale-but-dead onion routes was silently dropped: the node
|
|
619
|
+
// "sent" it, nothing reached the relay, the recipient never friended
|
|
620
|
+
// back, and the session could never bridge. That stranded 2nd/3rd exits.
|
|
621
|
+
this.#recordOutgoingFriendRequest(friendId, pubkey, friendAddress.nospam, hello);
|
|
622
|
+
let expressOk = false;
|
|
623
|
+
if (this.#express?.hasNodes()) {
|
|
624
|
+
try {
|
|
625
|
+
await this.#express.sendOfflineFriendRequest(pubkey, appPayload);
|
|
626
|
+
expressOk = true;
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
this.#debugLog(`offline friend request dispatch failed: ${error.message}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Only a true failure if BOTH transports got nothing out.
|
|
633
|
+
if (sent === 0 && !expressOk) {
|
|
614
634
|
throw new Error(`friend request dispatch failed: ${errors.join("; ")}`);
|
|
615
635
|
}
|
|
616
636
|
this.#lastFriendRequestDispatch = {
|
|
617
|
-
transport: "onion",
|
|
637
|
+
transport: sent > 0 ? "onion" : "express",
|
|
618
638
|
routes: routes.length,
|
|
619
639
|
targets: sent
|
|
620
640
|
};
|
|
621
|
-
this.#recordOutgoingFriendRequest(friendId, pubkey, friendAddress.nospam, hello);
|
|
622
|
-
if (this.#express?.hasNodes()) {
|
|
623
|
-
void this.#express.sendOfflineFriendRequest(pubkey, appPayload).catch((error) => {
|
|
624
|
-
this.#debugLog(`offline friend request dispatch failed: ${error.message}`);
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
641
|
}
|
|
628
642
|
finally {
|
|
629
643
|
resumeSelfAnnounce();
|
|
@@ -3892,17 +3906,55 @@ export class Peer {
|
|
|
3892
3906
|
}
|
|
3893
3907
|
this.#debugLog(`loaded ${this.#friends.size} persisted friends`);
|
|
3894
3908
|
}
|
|
3895
|
-
catch {
|
|
3896
|
-
//
|
|
3909
|
+
catch (error) {
|
|
3910
|
+
// A missing file is normal (fresh node). A PARSE error is not — it
|
|
3911
|
+
// means the store is corrupt (historically: concatenated JSON from a
|
|
3912
|
+
// write race, now prevented by atomic persistFriends). Swallowing it
|
|
3913
|
+
// silently left the node with zero friends and unable to register, with
|
|
3914
|
+
// no clue why — so surface it loudly. Try to recover the first valid
|
|
3915
|
+
// JSON array so a corrupt store self-heals instead of stranding the node.
|
|
3916
|
+
const isParse = error instanceof SyntaxError;
|
|
3917
|
+
if (isParse) {
|
|
3918
|
+
try {
|
|
3919
|
+
const raw = await readFile(this.#friendStoreFile, "utf8");
|
|
3920
|
+
const end = raw.indexOf("]");
|
|
3921
|
+
if (end !== -1) {
|
|
3922
|
+
const recovered = JSON.parse(raw.slice(0, end + 1));
|
|
3923
|
+
if (Array.isArray(recovered)) {
|
|
3924
|
+
for (const record of recovered) {
|
|
3925
|
+
if (record && typeof record.pubkey === "string" && record.pubkey) {
|
|
3926
|
+
this.#friends.set(record.pubkey, record);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
// Rewrite cleanly (atomic) so the corruption is gone for good.
|
|
3930
|
+
this.#persistFriends();
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
catch { /* unrecoverable — fall through with whatever we have */ }
|
|
3935
|
+
// eslint-disable-next-line no-console
|
|
3936
|
+
console.error(`[peer] friend store ${this.#friendStoreFile} was corrupt (${error.message}); ` +
|
|
3937
|
+
`recovered ${this.#friends.size} friend(s).`);
|
|
3938
|
+
}
|
|
3897
3939
|
}
|
|
3898
3940
|
}
|
|
3899
3941
|
#persistFriends() {
|
|
3900
3942
|
if (!this.#friendStoreFile) {
|
|
3901
3943
|
return;
|
|
3902
3944
|
}
|
|
3945
|
+
const file = this.#friendStoreFile;
|
|
3903
3946
|
const payload = JSON.stringify([...this.#friends.values()], null, 2);
|
|
3904
|
-
|
|
3905
|
-
|
|
3947
|
+
// Write to a unique temp file then atomically rename over the target.
|
|
3948
|
+
// A plain writeFile can interleave when two writers race — observed: the
|
|
3949
|
+
// `agentnet init` friend-request flow (a short-lived standalone peer) and
|
|
3950
|
+
// the daemon both persisting produced a file with two CONCATENATED JSON
|
|
3951
|
+
// arrays, so JSON.parse threw, the loader's catch swallowed it, the node
|
|
3952
|
+
// loaded ZERO friends, and could never sendText/register again. rename()
|
|
3953
|
+
// is atomic on POSIX, so a reader always sees a complete old-or-new file.
|
|
3954
|
+
const tmp = `${file}.tmp.${process.pid}.${this.#persistSeq++}`;
|
|
3955
|
+
void mkdir(dirname(file), { recursive: true })
|
|
3956
|
+
.then(() => writeFile(tmp, payload, "utf8"))
|
|
3957
|
+
.then(() => rename(tmp, file))
|
|
3906
3958
|
.catch((error) => {
|
|
3907
3959
|
this.#debugLog(`persist friends failed: ${error.message}`);
|
|
3908
3960
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
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",
|