@decentnetwork/lan 0.1.45 → 0.1.47

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.
@@ -81,6 +81,34 @@ export declare function cmdFriendRequest(args: {
81
81
  configDir?: string;
82
82
  waitMs?: number;
83
83
  }): Promise<void>;
84
+ /**
85
+ * Send friend requests to MANY addresses in a single Carrier session.
86
+ *
87
+ * The expensive parts of a standalone friend-request — start, joinNetwork,
88
+ * announceSelf, and the relay-delivery wait — are PER-SESSION, not
89
+ * per-recipient. Sending N requests by calling cmdFriendRequest N times
90
+ * pays them N times (~25s each). This opens the peer once, joins/announces
91
+ * once, fires all requests, waits once, and stops once — so friending 4
92
+ * doras costs about the same as friending 1 (~25s instead of ~100s).
93
+ *
94
+ * Used by `agentnet init` to friend all federated default doras. Returns
95
+ * per-address ok/error so the caller can report which landed. If the
96
+ * daemon is already running, each request is routed via IPC (no peer).
97
+ */
98
+ export declare function cmdFriendRequestMany(args: {
99
+ targets: {
100
+ address: string;
101
+ label: string;
102
+ }[];
103
+ hello?: string;
104
+ configDir?: string;
105
+ waitMs?: number;
106
+ }): Promise<{
107
+ label: string;
108
+ address: string;
109
+ ok: boolean;
110
+ error?: string;
111
+ }[]>;
84
112
  /**
85
113
  * Accept a pending friend request.
86
114
  * Run while daemon is DOWN — opens a temporary peer, accepts, exits.
@@ -139,21 +139,27 @@ export async function cmdInit(args) {
139
139
  // them gives redundancy: any one staying up is enough to get an IP.
140
140
  const configuredUserids = new Set(config.dora?.userids ?? []);
141
141
  const dorasToFriend = DEFAULT_DORAS.filter((d) => configuredUserids.has(d.userid));
142
- for (const dora of dorasToFriend) {
143
- console.log(`\nFriending ${dora.name} (${dora.userid.slice(0, 16)}...) so the daemon can join the shared network on first start.`);
142
+ if (dorasToFriend.length > 0) {
143
+ console.log(`\nFriending ${dorasToFriend.length} default dora${dorasToFriend.length > 1 ? "s" : ""} (${dorasToFriend
144
+ .map((d) => d.name)
145
+ .join(", ")}) in one Carrier session so the daemon can join the shared network on first start.`);
144
146
  try {
145
- await cmdFriendRequest({
146
- address: dora.address,
147
+ const results = await cmdFriendRequestMany({
148
+ targets: dorasToFriend.map((d) => ({ address: d.address, label: d.name })),
147
149
  hello: `decentlan init (${nodeName})`,
148
150
  waitMs: 8000,
149
151
  configDir: dir,
150
152
  });
151
- console.log(` Friend-request dispatched. Auto-accept on the dora side will take it from here.`);
153
+ const failed = results.filter((r) => !r.ok);
154
+ for (const f of failed) {
155
+ console.warn(` ${f.label} failed: ${f.error} — re-run later: agentnet dora enable --address ${f.address}`);
156
+ }
157
+ console.log(` Friended ${results.length - failed.length}/${results.length} doras. Auto-accept on their side takes it from here (you only need one to get an IP).`);
152
158
  }
153
159
  catch (err) {
154
160
  const msg = err instanceof Error ? err.message : String(err);
155
- console.warn(` Friend-request to ${dora.name} failed: ${msg}`);
156
- console.warn(` Re-run later: agentnet dora enable --address ${dora.address}`);
161
+ console.warn(` Friend-requests failed: ${msg}`);
162
+ console.warn(` Re-run later with: agentnet up --real-tun (the daemon retries joining a dora on start).`);
157
163
  }
158
164
  }
159
165
  console.log(`\nNext: sudo agentnet service install # or 'agentnet up --real-tun' to run in foreground`);
@@ -476,6 +482,78 @@ export async function cmdFriendRequest(args) {
476
482
  console.log(`pick it up on next start (and still auto-accept). Only run 'agentnet friend-accept --pubkey ${myPubkey}'`);
477
483
  console.log(`manually if their daemon is down AND they've disabled autoAccept.`);
478
484
  }
485
+ /**
486
+ * Send friend requests to MANY addresses in a single Carrier session.
487
+ *
488
+ * The expensive parts of a standalone friend-request — start, joinNetwork,
489
+ * announceSelf, and the relay-delivery wait — are PER-SESSION, not
490
+ * per-recipient. Sending N requests by calling cmdFriendRequest N times
491
+ * pays them N times (~25s each). This opens the peer once, joins/announces
492
+ * once, fires all requests, waits once, and stops once — so friending 4
493
+ * doras costs about the same as friending 1 (~25s instead of ~100s).
494
+ *
495
+ * Used by `agentnet init` to friend all federated default doras. Returns
496
+ * per-address ok/error so the caller can report which landed. If the
497
+ * daemon is already running, each request is routed via IPC (no peer).
498
+ */
499
+ export async function cmdFriendRequestMany(args) {
500
+ const dir = args.configDir || ConfigLoader.defaultConfigDir();
501
+ const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
502
+ const results = [];
503
+ // Daemon up → route each via IPC (no second peer, no announce cost).
504
+ const livePid = daemonPid(config);
505
+ if (livePid !== null) {
506
+ console.log(`Daemon is running (pid ${livePid}); routing friend-requests via IPC...`);
507
+ for (const t of args.targets) {
508
+ const res = await ipcCall(config, { op: "friend-request", address: t.address, hello: args.hello });
509
+ results.push({ label: t.label, address: t.address, ok: res.ok, error: res.ok ? undefined : res.error });
510
+ console.log(res.ok ? ` ✓ ${t.label}` : ` ✗ ${t.label}: ${res.error}`);
511
+ }
512
+ return results;
513
+ }
514
+ // Daemon down → one standalone peer session for ALL requests.
515
+ const { Peer } = await import("@decentnetwork/peer");
516
+ const keyFile = resolve(config.carrier.dataDir, "keypair.json");
517
+ console.log(`Opening peer with identity at ${keyFile}...`);
518
+ const peer = await Peer.create({
519
+ keyFile,
520
+ compatibilityMode: "legacy",
521
+ bootstrapNodes: config.carrier.bootstrapNodes,
522
+ expressNodes: config.carrier.expressNodes,
523
+ });
524
+ await peer.start();
525
+ console.log(`My address: ${peer.address()}`);
526
+ console.log(`My pubkey: ${peer.pubkey()}`);
527
+ console.log(`Joining Carrier network...`);
528
+ const joinResult = await peer.joinNetwork();
529
+ console.log(`Joined via ${joinResult.respondingNode.host}:${joinResult.respondingNode.port}`);
530
+ console.log(`Announcing self (15s)...`);
531
+ await peer.announceSelf(15000).catch((err) => {
532
+ console.warn(`Self-announce failed: ${err.message}`);
533
+ });
534
+ // Fire all requests on the one announced session.
535
+ for (const t of args.targets) {
536
+ try {
537
+ console.log(`Sending friend request to ${t.label} (${t.address.slice(0, 16)}...)...`);
538
+ await peer.sendFriendRequest(t.address, args.hello || "Decent AgentNet friend request");
539
+ results.push({ label: t.label, address: t.address, ok: true });
540
+ }
541
+ catch (err) {
542
+ const msg = err instanceof Error ? err.message : String(err);
543
+ console.warn(` Failed to send to ${t.label}: ${msg}`);
544
+ results.push({ label: t.label, address: t.address, ok: false, error: msg });
545
+ }
546
+ }
547
+ // Single shared wait for relay delivery of all requests above.
548
+ const waitMs = args.waitMs ?? 8000;
549
+ console.log(`Waiting ${waitMs}ms for relay delivery...`);
550
+ await new Promise((r) => setTimeout(r, waitMs));
551
+ const myUserid = peer.userid();
552
+ await peer.stop();
553
+ console.log(`\nFriend requests sent. Recipients running with autoAccept (the default) have already`);
554
+ console.log(`accepted — they can confirm with 'agentnet diag' and look for userid ${myUserid}.`);
555
+ return results;
556
+ }
479
557
  /**
480
558
  * Accept a pending friend request.
481
559
  * Run while daemon is DOWN — opens a temporary peer, accepts, exits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
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",
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "@decentnetwork/dora": "^0.1.0",
78
- "@decentnetwork/peer": "^0.1.19",
78
+ "@decentnetwork/peer": "^0.1.20",
79
79
  "js-yaml": "^4.1.0",
80
80
  "yargs": "^17.7.2"
81
81
  },