@decentnetwork/lan 0.1.269 → 0.1.271
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/daemon/ipc.d.ts +3 -1
- package/dist/daemon/ipc.js +1 -1
- package/dist/daemon/server.d.ts +12 -4
- package/dist/daemon/server.js +36 -17
- package/dist/dora/dora-integration.js +13 -3
- package/dist/ui/desktop/app.js +1 -1
- package/package.json +1 -1
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export interface IpcHandlers {
|
|
|
109
109
|
* signals immediately, otherwise holds the connection until one arrives or a
|
|
110
110
|
* ~20s timeout elapses (then resolves empty). The UI re-polls to get
|
|
111
111
|
* near-instant offer/answer/candidate delivery without a WebSocket. */
|
|
112
|
-
callPoll: () => Promise<Record<string, unknown>>;
|
|
112
|
+
callPoll: (since?: number) => Promise<Record<string, unknown>>;
|
|
113
113
|
/** Re-read proxy allowlist from config and apply it to the running
|
|
114
114
|
* proxy WITHOUT restarting the daemon. Lets `agentnet proxy
|
|
115
115
|
* allow-host` take effect instantly instead of forcing a daemon
|
|
@@ -153,6 +153,8 @@ export interface IpcRequest {
|
|
|
153
153
|
data?: string;
|
|
154
154
|
/** Carrier custom-packet id for the "app-send" op (165-191). */
|
|
155
155
|
packetId?: number;
|
|
156
|
+
/** Broadcast cursor for "call-poll": return signals with seq > since. */
|
|
157
|
+
since?: number;
|
|
156
158
|
}
|
|
157
159
|
export interface IpcResponseOk {
|
|
158
160
|
ok: true;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -297,7 +297,7 @@ export class IpcServer {
|
|
|
297
297
|
case "dora-status":
|
|
298
298
|
return await this.handlers.doraStatus();
|
|
299
299
|
case "call-poll":
|
|
300
|
-
return await this.handlers.callPoll();
|
|
300
|
+
return await this.handlers.callPoll(typeof req.since === "number" ? req.since : undefined);
|
|
301
301
|
case "proxy-reload":
|
|
302
302
|
return await this.handlers.proxyReload();
|
|
303
303
|
case "proxy-access":
|
package/dist/daemon/server.d.ts
CHANGED
|
@@ -44,12 +44,20 @@ export declare class DaemonServer {
|
|
|
44
44
|
/** Outgoing file transfers in flight: fileId → the chat message tracking it,
|
|
45
45
|
* so progress/complete/cancel events can patch its status + sent bytes. */
|
|
46
46
|
private readonly activeSends;
|
|
47
|
-
/** Inbound WebRTC call-signaling payloads
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
47
|
+
/** Inbound WebRTC call-signaling payloads as a broadcast ring: every poller
|
|
48
|
+
* reads at its own cursor, entries expire by AGE (90s), never by being
|
|
49
|
+
* read. The previous shape — splice(0) on first poll — handed the whole
|
|
50
|
+
* batch to whichever poller arrived first, so with two tabs (or the
|
|
51
|
+
* desktop app + a browser) open, an incoming call rang a random one and
|
|
52
|
+
* the surface the user was looking at stayed silent (INBOX 2026-08-05).
|
|
53
|
+
* Bounded so a UI that never polls can't grow it unboundedly. */
|
|
54
|
+
private readonly callSignalLog;
|
|
55
|
+
private callSignalSeq;
|
|
51
56
|
/** Resolvers for in-flight call-poll long-polls, woken when a signal lands. */
|
|
52
57
|
private callSignalWaiters;
|
|
58
|
+
/** Call signals older than this have no business ringing anything. */
|
|
59
|
+
private static readonly CALL_SIGNAL_TTL_MS;
|
|
60
|
+
private pruneCallSignals;
|
|
53
61
|
/** Directory holding queued (offline) outgoing file bytes: <configDir>/outbox.
|
|
54
62
|
* One file per queued message, named by its msgId. Set in start(). */
|
|
55
63
|
private outboxDir;
|
package/dist/daemon/server.js
CHANGED
|
@@ -90,12 +90,24 @@ export class DaemonServer {
|
|
|
90
90
|
/** Outgoing file transfers in flight: fileId → the chat message tracking it,
|
|
91
91
|
* so progress/complete/cancel events can patch its status + sent bytes. */
|
|
92
92
|
activeSends = new Map();
|
|
93
|
-
/** Inbound WebRTC call-signaling payloads
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
|
|
93
|
+
/** Inbound WebRTC call-signaling payloads as a broadcast ring: every poller
|
|
94
|
+
* reads at its own cursor, entries expire by AGE (90s), never by being
|
|
95
|
+
* read. The previous shape — splice(0) on first poll — handed the whole
|
|
96
|
+
* batch to whichever poller arrived first, so with two tabs (or the
|
|
97
|
+
* desktop app + a browser) open, an incoming call rang a random one and
|
|
98
|
+
* the surface the user was looking at stayed silent (INBOX 2026-08-05).
|
|
99
|
+
* Bounded so a UI that never polls can't grow it unboundedly. */
|
|
100
|
+
callSignalLog = [];
|
|
101
|
+
callSignalSeq = 0;
|
|
97
102
|
/** Resolvers for in-flight call-poll long-polls, woken when a signal lands. */
|
|
98
103
|
callSignalWaiters = [];
|
|
104
|
+
/** Call signals older than this have no business ringing anything. */
|
|
105
|
+
static CALL_SIGNAL_TTL_MS = 90_000;
|
|
106
|
+
pruneCallSignals() {
|
|
107
|
+
const cutoff = Date.now() - DaemonServer.CALL_SIGNAL_TTL_MS;
|
|
108
|
+
while (this.callSignalLog.length && this.callSignalLog[0].ts < cutoff)
|
|
109
|
+
this.callSignalLog.shift();
|
|
110
|
+
}
|
|
99
111
|
/** Directory holding queued (offline) outgoing file bytes: <configDir>/outbox.
|
|
100
112
|
* One file per queued message, named by its msgId. Set in start(). */
|
|
101
113
|
outboxDir = "";
|
|
@@ -708,11 +720,16 @@ export class DaemonServer {
|
|
|
708
720
|
return { checkedAt: new Date().toISOString(), registries: [], note: "dora not enabled on this node" };
|
|
709
721
|
return await this.doraIntegration.doraStatus();
|
|
710
722
|
},
|
|
711
|
-
callPoll: async () => {
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
|
|
723
|
+
callPoll: async (since) => {
|
|
724
|
+
// Long-poll at a per-subscriber cursor: reading never consumes, so
|
|
725
|
+
// every open tab receives every signal. A caller without a cursor
|
|
726
|
+
// (older UI) starts at "now" and gets only future signals — the old
|
|
727
|
+
// semantics minus the stealing. Hold up to ~20s for a new signal
|
|
728
|
+
// (the IPC client's timeout is 30s, so a 20s hold is safe).
|
|
729
|
+
const from = since ?? this.callSignalSeq;
|
|
730
|
+
this.pruneCallSignals();
|
|
731
|
+
let avail = this.callSignalLog.filter((s) => s.seq > from);
|
|
732
|
+
if (avail.length === 0) {
|
|
716
733
|
await new Promise((resolve) => {
|
|
717
734
|
const timer = setTimeout(() => {
|
|
718
735
|
this.callSignalWaiters = this.callSignalWaiters.filter((w) => w !== wake);
|
|
@@ -724,18 +741,19 @@ export class DaemonServer {
|
|
|
724
741
|
};
|
|
725
742
|
this.callSignalWaiters.push(wake);
|
|
726
743
|
});
|
|
744
|
+
this.pruneCallSignals();
|
|
745
|
+
avail = this.callSignalLog.filter((s) => s.seq > from);
|
|
727
746
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const kinds = signals.map((s) => { try {
|
|
747
|
+
if (avail.length) {
|
|
748
|
+
const kinds = avail.map((s) => { try {
|
|
731
749
|
return JSON.parse(s.data).type;
|
|
732
750
|
}
|
|
733
751
|
catch {
|
|
734
752
|
return "?";
|
|
735
753
|
} }).join(",");
|
|
736
|
-
this.logger.info(`call-poll -> browser: ${
|
|
754
|
+
this.logger.info(`call-poll -> browser: ${avail.length} signal(s) [${kinds}] (cursor ${from} -> ${this.callSignalSeq})`);
|
|
737
755
|
}
|
|
738
|
-
return { signals };
|
|
756
|
+
return { signals: avail.map(({ userid, data }) => ({ userid, data })), cursor: this.callSignalSeq };
|
|
739
757
|
},
|
|
740
758
|
subscribe: (emit) => {
|
|
741
759
|
const onEvent = (event) => emit(event);
|
|
@@ -1198,9 +1216,10 @@ export class DaemonServer {
|
|
|
1198
1216
|
if (!text)
|
|
1199
1217
|
return;
|
|
1200
1218
|
// Cap the backlog so a UI that stopped polling can't grow it forever.
|
|
1201
|
-
if (this.
|
|
1202
|
-
this.
|
|
1203
|
-
this.
|
|
1219
|
+
if (this.callSignalLog.length > 256)
|
|
1220
|
+
this.callSignalLog.shift();
|
|
1221
|
+
this.callSignalLog.push({ seq: ++this.callSignalSeq, ts: Date.now(), userid: evt.pubkey, data: text });
|
|
1222
|
+
this.pruneCallSignals();
|
|
1204
1223
|
const waiters = this.callSignalWaiters;
|
|
1205
1224
|
this.callSignalWaiters = [];
|
|
1206
1225
|
for (const wake of waiters)
|
|
@@ -413,11 +413,21 @@ export class DoraIntegration {
|
|
|
413
413
|
try {
|
|
414
414
|
// One client per registry: this is the whole point — a merged call
|
|
415
415
|
// would hide exactly the failure we are looking for.
|
|
416
|
+
// Same channel and event as the constructor above, deliberately:
|
|
417
|
+
// dora speaks on packet 162 via sendDora / "dora-message", NOT on
|
|
418
|
+
// chat's packet 64. Reaching for sendText/"message" here sent every
|
|
419
|
+
// request down a channel the registry never reads, and produced four
|
|
420
|
+
// identical timeouts that looked exactly like four dead doras.
|
|
421
|
+
//
|
|
422
|
+
// Timeout matches too. 8s was optimistic: the comment on the
|
|
423
|
+
// constructor records that China -> overseas bootstrap -> relay ->
|
|
424
|
+
// the dora's own relay and back consistently blows past 10s, which
|
|
425
|
+
// is why the working path uses 30.
|
|
416
426
|
const one = new DoraClient({
|
|
417
427
|
registryUserids: [id],
|
|
418
|
-
sendText: (to, text) => this.opts.peerManager.
|
|
419
|
-
onText: (h) => this.opts.peerManager.on("message", (
|
|
420
|
-
timeoutMs:
|
|
428
|
+
sendText: (to, text) => this.opts.peerManager.sendDora(to, text),
|
|
429
|
+
onText: (h) => this.opts.peerManager.on("dora-message", (from, text) => h(from, text)),
|
|
430
|
+
timeoutMs: parseInt(process.env.AGENTNET_DORA_TIMEOUT_MS || "30000", 10),
|
|
421
431
|
});
|
|
422
432
|
const roster = await one.list();
|
|
423
433
|
return { userid: id, ok: Array.isArray(roster) && roster.length > 0,
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.271";
|
|
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.
|
|
3
|
+
"version": "0.1.271",
|
|
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",
|