@decentnetwork/lan 0.1.267 → 0.1.269

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.
@@ -100,6 +100,11 @@ export interface IpcHandlers {
100
100
  * without adding an op per feature. Inbound ones arrive as
101
101
  * `{type:"app", userid, packetId, data}` events on `subscribe`. */
102
102
  appSend: (userid: string, packetId: number, data: Uint8Array) => Promise<void>;
103
+ /** Per-registry dora health: asks each configured dora for its roster
104
+ * INDIVIDUALLY, so one healthy registry cannot mask three dead ones the way
105
+ * the merged resolve path does. Backs external monitoring, which would
106
+ * otherwise need its own Carrier peer and a cold session per check. */
107
+ doraStatus: () => Promise<Record<string, unknown>>;
103
108
  /** Long-poll for inbound call-signaling payloads. Resolves with any queued
104
109
  * signals immediately, otherwise holds the connection until one arrives or a
105
110
  * ~20s timeout elapses (then resolves empty). The UI re-polls to get
@@ -127,7 +132,7 @@ export interface IpcHandlers {
127
132
  selfRestart: () => Promise<Record<string, unknown>>;
128
133
  }
129
134
  export interface IpcRequest {
130
- op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-log-local" | "file-log-local" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "chat-mark-read" | "subscribe" | "sign" | "call-signal" | "call-poll" | "proxy-reload" | "proxy-access" | "app-send" | "self-restart";
135
+ op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-log-local" | "file-log-local" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "chat-mark-read" | "subscribe" | "sign" | "call-signal" | "call-poll" | "proxy-reload" | "proxy-access" | "app-send" | "dora-status" | "self-restart";
131
136
  address?: string;
132
137
  hello?: string;
133
138
  userid?: string;
@@ -294,6 +294,8 @@ export class IpcServer {
294
294
  await this.handlers.appSend(req.userid, req.packetId, new Uint8Array(bytes));
295
295
  return { sent: bytes.length };
296
296
  }
297
+ case "dora-status":
298
+ return await this.handlers.doraStatus();
297
299
  case "call-poll":
298
300
  return await this.handlers.callPoll();
299
301
  case "proxy-reload":
@@ -703,6 +703,11 @@ export class DaemonServer {
703
703
  // applies to every caller, not just this socket.
704
704
  await this.peerManager.sendAppPacket(userid, packetId, data);
705
705
  },
706
+ doraStatus: async () => {
707
+ if (!this.doraIntegration)
708
+ return { checkedAt: new Date().toISOString(), registries: [], note: "dora not enabled on this node" };
709
+ return await this.doraIntegration.doraStatus();
710
+ },
706
711
  callPoll: async () => {
707
712
  // Drain immediately if signals are queued; otherwise hold up to ~20s
708
713
  // for one to arrive (near-instant delivery without a WebSocket). The
@@ -114,6 +114,26 @@ export declare class DoraIntegration {
114
114
  */
115
115
  private tryRegister;
116
116
  stop(): void;
117
+ /**
118
+ * Per-dora health, by asking each one for its roster.
119
+ *
120
+ * This lives in the daemon rather than in a monitoring tool with its own peer
121
+ * for one reason: reaching a dora needs a warm net_crypto session, and the
122
+ * daemon already holds one to every configured registry. A separate process
123
+ * has to build that from scratch on every check, which is slow, adds a second
124
+ * identity for the same operator, and fails in ways that look exactly like
125
+ * the outage it is trying to detect.
126
+ *
127
+ * Each registry is asked INDIVIDUALLY. The normal list() merges every
128
+ * registry's answer, which is right for resolving names and useless for
129
+ * health: one dora answering makes the merged call succeed while three sit
130
+ * dead — which is precisely how a dora stayed broken for ten days unnoticed.
131
+ *
132
+ * Results are cached briefly so a polling dashboard cannot turn into load on
133
+ * the registries it is watching.
134
+ */
135
+ private doraStatusCache?;
136
+ doraStatus(maxAgeMs?: number): Promise<Record<string, unknown>>;
117
137
  getAllocatedIp(): string | undefined;
118
138
  private refreshRoster;
119
139
  /**
@@ -384,6 +384,58 @@ export class DoraIntegration {
384
384
  this.retryBootstrapTimer = undefined;
385
385
  }
386
386
  }
387
+ /**
388
+ * Per-dora health, by asking each one for its roster.
389
+ *
390
+ * This lives in the daemon rather than in a monitoring tool with its own peer
391
+ * for one reason: reaching a dora needs a warm net_crypto session, and the
392
+ * daemon already holds one to every configured registry. A separate process
393
+ * has to build that from scratch on every check, which is slow, adds a second
394
+ * identity for the same operator, and fails in ways that look exactly like
395
+ * the outage it is trying to detect.
396
+ *
397
+ * Each registry is asked INDIVIDUALLY. The normal list() merges every
398
+ * registry's answer, which is right for resolving names and useless for
399
+ * health: one dora answering makes the merged call succeed while three sit
400
+ * dead — which is precisely how a dora stayed broken for ten days unnoticed.
401
+ *
402
+ * Results are cached briefly so a polling dashboard cannot turn into load on
403
+ * the registries it is watching.
404
+ */
405
+ doraStatusCache;
406
+ async doraStatus(maxAgeMs = 20_000) {
407
+ const cached = this.doraStatusCache;
408
+ if (cached && Date.now() - cached.at < maxAgeMs)
409
+ return cached.value;
410
+ const userids = this.opts.config.userids ?? [];
411
+ const checks = await Promise.all(userids.map(async (id) => {
412
+ const started = Date.now();
413
+ try {
414
+ // One client per registry: this is the whole point — a merged call
415
+ // would hide exactly the failure we are looking for.
416
+ const one = new DoraClient({
417
+ registryUserids: [id],
418
+ sendText: (to, text) => this.opts.peerManager.sendText(to, text),
419
+ onText: (h) => this.opts.peerManager.on("message", (pubkey, text) => h(pubkey, text)),
420
+ timeoutMs: 8000,
421
+ });
422
+ const roster = await one.list();
423
+ return { userid: id, ok: Array.isArray(roster) && roster.length > 0,
424
+ entries: Array.isArray(roster) ? roster.length : 0,
425
+ ms: Date.now() - started,
426
+ ...(Array.isArray(roster) && roster.length === 0
427
+ ? { error: "roster empty — answering but holding no records" }
428
+ : {}) };
429
+ }
430
+ catch (err) {
431
+ return { userid: id, ok: false, entries: 0, ms: Date.now() - started,
432
+ error: err instanceof Error ? err.message : String(err) };
433
+ }
434
+ }));
435
+ const value = { checkedAt: new Date().toISOString(), registries: checks };
436
+ this.doraStatusCache = { at: Date.now(), value };
437
+ return value;
438
+ }
387
439
  getAllocatedIp() {
388
440
  return this.allocatedIp;
389
441
  }
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.267";
1
+ window.__DK_UI_VERSION="0.1.269";
2
2
  const ICON_PATHS = {
3
3
  // ---- tab bar (the four must feel like one set) ----
4
4
  users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.267",
3
+ "version": "0.1.269",
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",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "dependencies": {
86
86
  "@decentnetwork/dora": "^0.1.14",
87
- "@decentnetwork/peer": "^0.1.134",
87
+ "@decentnetwork/peer": "^0.1.135",
88
88
  "@decentnetwork/peer-webrtc": "^0.2.10",
89
89
  "ink": "^5.2.1",
90
90
  "js-yaml": "^4.1.0",