@decentnetwork/lan 0.1.16 → 0.1.18
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/carrier/peer-manager.js +23 -6
- package/dist/cli/commands.d.ts +5 -2
- package/dist/cli/commands.js +59 -24
- package/dist/cli/index.js +34 -10
- package/dist/daemon/server.js +2 -0
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
|
@@ -159,12 +159,29 @@ export class PeerManager extends EventEmitter {
|
|
|
159
159
|
if (!this.peer) {
|
|
160
160
|
throw new Error("Peer not created. Call create() first.");
|
|
161
161
|
}
|
|
162
|
-
return this.peer.friends().map((friend) =>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
return this.peer.friends().map((friend) => {
|
|
163
|
+
// Preserve the SDK's three-state status ("requested" matters —
|
|
164
|
+
// collapsing it into "offline" hid the "did my friend-request
|
|
165
|
+
// make it across?" diagnostic from diag output).
|
|
166
|
+
const status = friend.status === "online"
|
|
167
|
+
? "online"
|
|
168
|
+
: friend.status === "requested"
|
|
169
|
+
? "requested"
|
|
170
|
+
: "offline";
|
|
171
|
+
// Both pubkey and userid are populated with the base58 userid in
|
|
172
|
+
// every current SDK code path, but persisted records from
|
|
173
|
+
// pre-1.7.x clients may only have pubkey. Keep both forms.
|
|
174
|
+
const carrierId = friend.userid || friend.pubkey;
|
|
175
|
+
return {
|
|
176
|
+
pubkey: friend.pubkey,
|
|
177
|
+
carrierId,
|
|
178
|
+
name: friend.name,
|
|
179
|
+
status,
|
|
180
|
+
address: friend.address,
|
|
181
|
+
lastSeen: friend.acceptedAt,
|
|
182
|
+
acceptedAt: friend.acceptedAt,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
168
185
|
}
|
|
169
186
|
/**
|
|
170
187
|
* Check if a specific friend is currently online (direct UDP path established).
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -90,13 +90,16 @@ export declare function cmdFriendRequest(args: {
|
|
|
90
90
|
* (this takes ~45s), then wait for the request to arrive.
|
|
91
91
|
*/
|
|
92
92
|
export declare function cmdFriendAccept(args: {
|
|
93
|
-
|
|
93
|
+
userid?: string;
|
|
94
94
|
waitForRequest?: boolean;
|
|
95
95
|
waitMs?: number;
|
|
96
96
|
configDir?: string;
|
|
97
97
|
}): Promise<void>;
|
|
98
98
|
/**
|
|
99
|
-
* List friends in the friend store.
|
|
99
|
+
* List friends in the friend store. Prefers the live daemon over IPC —
|
|
100
|
+
* opening a second Peer with the same keyfile would stomp on the
|
|
101
|
+
* daemon's Carrier session, so we only fall back to that path when the
|
|
102
|
+
* daemon isn't running. Same pattern as `peers list` and `resolve`.
|
|
100
103
|
*/
|
|
101
104
|
export declare function cmdFriendsList(args: {
|
|
102
105
|
configDir?: string;
|
package/dist/cli/commands.js
CHANGED
|
@@ -460,6 +460,21 @@ export async function cmdFriendRequest(args) {
|
|
|
460
460
|
export async function cmdFriendAccept(args) {
|
|
461
461
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
462
462
|
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
463
|
+
// When the daemon is up, route the accept through IPC — no need
|
|
464
|
+
// to bring the daemon down. This is what 'friends accept' does
|
|
465
|
+
// and 'friend-accept' is just an alias for that path now.
|
|
466
|
+
if (daemonPid(config) !== null) {
|
|
467
|
+
if (!args.userid) {
|
|
468
|
+
throw new Error("userid is required when the daemon is up. Use 'agentnet friends pending' to see queued requests, then 'agentnet friend-accept <userid>'.");
|
|
469
|
+
}
|
|
470
|
+
await cmdFriendsAccept({ userid: args.userid, configDir: args.configDir });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
// Daemon-down fallback: open a standalone peer. This path stays
|
|
474
|
+
// because it's the only way to accept a request when the daemon
|
|
475
|
+
// can't run (e.g. the operator hasn't set up the helper binary
|
|
476
|
+
// yet). The --wait interactive mode is also useful for one-shot
|
|
477
|
+
// bootstrapping.
|
|
463
478
|
assertDaemonNotRunning(config, "friend-accept");
|
|
464
479
|
const { Peer } = await import("@decentnetwork/peer");
|
|
465
480
|
const keyFile = resolve(config.carrier.dataDir, "keypair.json");
|
|
@@ -487,7 +502,10 @@ export async function cmdFriendAccept(args) {
|
|
|
487
502
|
if (stored.length === 0) {
|
|
488
503
|
console.warn(`Self-announce got 0 storage nodes — request may still arrive via express relay`);
|
|
489
504
|
}
|
|
490
|
-
|
|
505
|
+
// The SDK uses `pubkey` as the local-var name historically; semantically
|
|
506
|
+
// this is the sender's userid (base58). Keep the variable name to
|
|
507
|
+
// minimize churn but document the mapping.
|
|
508
|
+
let pubkey = args.userid;
|
|
491
509
|
const wait = args.waitForRequest;
|
|
492
510
|
if (!pubkey || wait) {
|
|
493
511
|
const waitMs = args.waitMs ?? 120000;
|
|
@@ -500,7 +518,7 @@ export async function cmdFriendAccept(args) {
|
|
|
500
518
|
catch (err) {
|
|
501
519
|
if (!pubkey)
|
|
502
520
|
throw err;
|
|
503
|
-
console.warn(`No incoming request — will try acceptance with provided
|
|
521
|
+
console.warn(`No incoming request — will try acceptance with provided userid ${pubkey}`);
|
|
504
522
|
}
|
|
505
523
|
}
|
|
506
524
|
console.log(`Accepting friend request from ${pubkey}...`);
|
|
@@ -511,46 +529,63 @@ export async function cmdFriendAccept(args) {
|
|
|
511
529
|
console.log(`Friend accepted: ${pubkey}`);
|
|
512
530
|
}
|
|
513
531
|
/**
|
|
514
|
-
* List friends in the friend store.
|
|
532
|
+
* List friends in the friend store. Prefers the live daemon over IPC —
|
|
533
|
+
* opening a second Peer with the same keyfile would stomp on the
|
|
534
|
+
* daemon's Carrier session, so we only fall back to that path when the
|
|
535
|
+
* daemon isn't running. Same pattern as `peers list` and `resolve`.
|
|
515
536
|
*/
|
|
516
537
|
export async function cmdFriendsList(args) {
|
|
517
538
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
518
539
|
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
540
|
+
let friends;
|
|
541
|
+
let source;
|
|
542
|
+
if (daemonPid(config) !== null) {
|
|
543
|
+
const res = await ipcCall(config, { op: "diag" });
|
|
544
|
+
if (!res.ok)
|
|
545
|
+
throw new Error(`Daemon diag failed: ${res.error}`);
|
|
546
|
+
friends = (res.data.friends ?? []);
|
|
547
|
+
source = "daemon";
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const { Peer } = await import("@decentnetwork/peer");
|
|
551
|
+
const keyFile = resolve(config.carrier.dataDir, "keypair.json");
|
|
552
|
+
if (!existsSync(keyFile)) {
|
|
553
|
+
console.log("No identity yet. Start the daemon at least once to generate one.");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const peer = await Peer.create({
|
|
557
|
+
keyFile,
|
|
558
|
+
compatibilityMode: "legacy",
|
|
559
|
+
bootstrapNodes: config.carrier.bootstrapNodes,
|
|
560
|
+
expressNodes: config.carrier.expressNodes,
|
|
561
|
+
});
|
|
562
|
+
await peer.start();
|
|
563
|
+
friends = peer.friends().map((f) => ({
|
|
564
|
+
pubkey: f.pubkey,
|
|
565
|
+
carrierId: f.userid || f.pubkey,
|
|
566
|
+
name: f.name,
|
|
567
|
+
status: f.status,
|
|
568
|
+
address: f.address,
|
|
569
|
+
acceptedAt: f.acceptedAt,
|
|
570
|
+
}));
|
|
571
|
+
await peer.stop();
|
|
572
|
+
source = "disk";
|
|
525
573
|
}
|
|
526
|
-
// Open peer and start (start() loads friends from disk)
|
|
527
|
-
const peer = await Peer.create({
|
|
528
|
-
keyFile,
|
|
529
|
-
compatibilityMode: "legacy",
|
|
530
|
-
bootstrapNodes: config.carrier.bootstrapNodes,
|
|
531
|
-
expressNodes: config.carrier.expressNodes,
|
|
532
|
-
});
|
|
533
|
-
await peer.start();
|
|
534
|
-
const friends = peer.friends();
|
|
535
574
|
if (friends.length === 0) {
|
|
536
575
|
console.log("No friends. Use 'agentnet friend-request --address <addr>' to add one.");
|
|
537
|
-
await peer.stop();
|
|
538
576
|
return;
|
|
539
577
|
}
|
|
540
|
-
console.log(`Friends (${friends.length}):`);
|
|
578
|
+
console.log(`Friends (${friends.length}) ${source === "daemon" ? "[live]" : "[disk]"}:`);
|
|
541
579
|
for (const friend of friends) {
|
|
542
580
|
console.log(``);
|
|
543
581
|
console.log(` ${friend.name || "(unnamed)"} status=${friend.status}`);
|
|
544
|
-
|
|
545
|
-
// the raw hex pubkey unless someone explicitly needs it.
|
|
546
|
-
const userid = friend.userid && friend.userid !== friend.pubkey ? friend.userid : friend.pubkey;
|
|
582
|
+
const userid = friend.carrierId || friend.pubkey;
|
|
547
583
|
console.log(` userid: ${userid}`);
|
|
548
584
|
if (friend.address)
|
|
549
585
|
console.log(` address: ${friend.address}`);
|
|
550
586
|
if (friend.acceptedAt)
|
|
551
587
|
console.log(` accepted: ${new Date(friend.acceptedAt).toISOString()}`);
|
|
552
588
|
}
|
|
553
|
-
await peer.stop();
|
|
554
589
|
}
|
|
555
590
|
/**
|
|
556
591
|
* List queued friend-requests held by the running daemon (the ones
|
package/dist/cli/index.js
CHANGED
|
@@ -112,8 +112,8 @@ async function main() {
|
|
|
112
112
|
configDir: argv["config-dir"],
|
|
113
113
|
});
|
|
114
114
|
})
|
|
115
|
-
.command("friend-request", "Send a friend request (
|
|
116
|
-
.
|
|
115
|
+
.command("friend-request <address>", "Send a friend request (routes through the running daemon when up; opens a standalone peer when down)", (y) => y
|
|
116
|
+
.positional("address", { type: "string", demandOption: true, describe: "Recipient Carrier address" })
|
|
117
117
|
.option("hello", { type: "string", describe: "Greeting message" })
|
|
118
118
|
.option("wait-ms", { type: "number", default: 8000, describe: "Wait time for relay delivery" })
|
|
119
119
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
@@ -124,13 +124,27 @@ async function main() {
|
|
|
124
124
|
configDir: argv["config-dir"],
|
|
125
125
|
});
|
|
126
126
|
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
// `friend-accept` is the legacy daemon-down command. With autoAccept
|
|
128
|
+
// on by default (the standard config), this is rarely used — but we
|
|
129
|
+
// keep it as an alias for `friends accept` so an operator running
|
|
130
|
+
// an old recipe doesn't dead-end.
|
|
131
|
+
//
|
|
132
|
+
// Positional userid is supported. The flag is named --userid (NOT
|
|
133
|
+
// --pubkey) because Carrier's identifier model is address + userid;
|
|
134
|
+
// "pubkey" only exists as a hex-encoded internal representation
|
|
135
|
+
// operators never need to touch.
|
|
136
|
+
.command("friend-accept [userid]", "Accept a queued friend-request by userid (alias of 'friends accept')", (y) => y
|
|
137
|
+
.positional("userid", { type: "string", describe: "Sender's Carrier userid (base58)" })
|
|
138
|
+
.option("userid", { type: "string", describe: "Sender's Carrier userid (base58)" })
|
|
139
|
+
// --wait is kept for the daemon-down interactive path
|
|
140
|
+
// (rarely used now; only matters when autoAccept is off
|
|
141
|
+
// AND the operator wants to run a one-shot accept without
|
|
142
|
+
// bringing the daemon up).
|
|
143
|
+
.option("wait", { type: "boolean", default: false, describe: "Daemon-down mode: wait for an incoming request" })
|
|
130
144
|
.option("wait-ms", { type: "number", default: 120000, describe: "Time to wait for request (ms)" })
|
|
131
145
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
132
146
|
await cmdFriendAccept({
|
|
133
|
-
|
|
147
|
+
userid: argv.userid,
|
|
134
148
|
waitForRequest: argv.wait,
|
|
135
149
|
waitMs: argv["wait-ms"],
|
|
136
150
|
configDir: argv["config-dir"],
|
|
@@ -149,14 +163,24 @@ async function main() {
|
|
|
149
163
|
.command("pending", "List queued friend-requests (over IPC; daemon must be up)", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
150
164
|
await cmdFriendsPending({ configDir: argv["config-dir"] });
|
|
151
165
|
})
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
// Accept positional userid OR `--userid X`. Operators expect
|
|
167
|
+
// `agentnet friends accept <userid>` to just work — without
|
|
168
|
+
// a positional that fails with "Unknown argument: <userid>"
|
|
169
|
+
// because yargs treats the unflagged value as junk.
|
|
170
|
+
.command("accept [userid]", "Accept a queued friend-request by userid (over IPC; daemon stays up)", (yy) => yy
|
|
171
|
+
.positional("userid", { type: "string", describe: "Sender's Carrier userid (base58)" })
|
|
172
|
+
.option("userid", { type: "string", describe: "Same as positional; either form works" })
|
|
154
173
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
174
|
+
if (!argv.userid)
|
|
175
|
+
throw new Error("userid is required (positional or --userid)");
|
|
155
176
|
await cmdFriendsAccept({ userid: argv.userid, configDir: argv["config-dir"] });
|
|
156
177
|
})
|
|
157
|
-
.command("reject", "Drop a queued friend-request by userid (over IPC; daemon stays up)", (yy) => yy
|
|
158
|
-
.
|
|
178
|
+
.command("reject [userid]", "Drop a queued friend-request by userid (over IPC; daemon stays up)", (yy) => yy
|
|
179
|
+
.positional("userid", { type: "string", describe: "Sender's Carrier userid (base58)" })
|
|
180
|
+
.option("userid", { type: "string", describe: "Same as positional; either form works" })
|
|
159
181
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
182
|
+
if (!argv.userid)
|
|
183
|
+
throw new Error("userid is required (positional or --userid)");
|
|
160
184
|
await cmdFriendsReject({ userid: argv.userid, configDir: argv["config-dir"] });
|
|
161
185
|
})
|
|
162
186
|
.demandCommand(1, "Specify a friends subcommand (run 'agentnet friends --help')"), () => {
|
package/dist/daemon/server.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -8,11 +8,21 @@ export interface PeerIdentity {
|
|
|
8
8
|
}
|
|
9
9
|
export interface RemotePeer {
|
|
10
10
|
pubkey: string;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Mirrors the SDK's FriendRecord.status verbatim:
|
|
13
|
+
* - "online": crypto session established
|
|
14
|
+
* - "offline": was a friend, currently disconnected
|
|
15
|
+
* - "requested": we sent a friend-request; peer hasn't accepted
|
|
16
|
+
* Collapsing "requested" into "offline" loses the operator signal
|
|
17
|
+
* ("did my friend-request go through?") so keep them distinct.
|
|
18
|
+
*/
|
|
19
|
+
status: "online" | "offline" | "requested";
|
|
12
20
|
name?: string;
|
|
13
21
|
carrierId?: string;
|
|
14
22
|
virtualIp?: string;
|
|
23
|
+
address?: string;
|
|
15
24
|
lastSeen?: number;
|
|
25
|
+
acceptedAt?: number;
|
|
16
26
|
}
|
|
17
27
|
export interface FriendConnectionEvent {
|
|
18
28
|
pubkey: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|