@decentnetwork/lan 0.1.270 → 0.1.272
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 +17 -4
- package/dist/daemon/server.js +41 -17
- 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,25 @@ 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
|
+
/** Seeded with the epoch so a cursor from a PREVIOUS daemon incarnation is
|
|
56
|
+
* always smaller than any new signal's seq. Seeded at 0, a daemon restart
|
|
57
|
+
* made every open tab deaf: their old cursor exceeded the fresh counter,
|
|
58
|
+
* so `seq > since` never matched and incoming calls stopped ringing until
|
|
59
|
+
* a hard refresh. */
|
|
60
|
+
private callSignalSeq;
|
|
51
61
|
/** Resolvers for in-flight call-poll long-polls, woken when a signal lands. */
|
|
52
62
|
private callSignalWaiters;
|
|
63
|
+
/** Call signals older than this have no business ringing anything. */
|
|
64
|
+
private static readonly CALL_SIGNAL_TTL_MS;
|
|
65
|
+
private pruneCallSignals;
|
|
53
66
|
/** Directory holding queued (offline) outgoing file bytes: <configDir>/outbox.
|
|
54
67
|
* One file per queued message, named by its msgId. Set in start(). */
|
|
55
68
|
private outboxDir;
|
package/dist/daemon/server.js
CHANGED
|
@@ -90,12 +90,29 @@ 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
|
+
/** Seeded with the epoch so a cursor from a PREVIOUS daemon incarnation is
|
|
102
|
+
* always smaller than any new signal's seq. Seeded at 0, a daemon restart
|
|
103
|
+
* made every open tab deaf: their old cursor exceeded the fresh counter,
|
|
104
|
+
* so `seq > since` never matched and incoming calls stopped ringing until
|
|
105
|
+
* a hard refresh. */
|
|
106
|
+
callSignalSeq = Date.now();
|
|
97
107
|
/** Resolvers for in-flight call-poll long-polls, woken when a signal lands. */
|
|
98
108
|
callSignalWaiters = [];
|
|
109
|
+
/** Call signals older than this have no business ringing anything. */
|
|
110
|
+
static CALL_SIGNAL_TTL_MS = 90_000;
|
|
111
|
+
pruneCallSignals() {
|
|
112
|
+
const cutoff = Date.now() - DaemonServer.CALL_SIGNAL_TTL_MS;
|
|
113
|
+
while (this.callSignalLog.length && this.callSignalLog[0].ts < cutoff)
|
|
114
|
+
this.callSignalLog.shift();
|
|
115
|
+
}
|
|
99
116
|
/** Directory holding queued (offline) outgoing file bytes: <configDir>/outbox.
|
|
100
117
|
* One file per queued message, named by its msgId. Set in start(). */
|
|
101
118
|
outboxDir = "";
|
|
@@ -708,11 +725,16 @@ export class DaemonServer {
|
|
|
708
725
|
return { checkedAt: new Date().toISOString(), registries: [], note: "dora not enabled on this node" };
|
|
709
726
|
return await this.doraIntegration.doraStatus();
|
|
710
727
|
},
|
|
711
|
-
callPoll: async () => {
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
|
|
728
|
+
callPoll: async (since) => {
|
|
729
|
+
// Long-poll at a per-subscriber cursor: reading never consumes, so
|
|
730
|
+
// every open tab receives every signal. A caller without a cursor
|
|
731
|
+
// (older UI) starts at "now" and gets only future signals — the old
|
|
732
|
+
// semantics minus the stealing. Hold up to ~20s for a new signal
|
|
733
|
+
// (the IPC client's timeout is 30s, so a 20s hold is safe).
|
|
734
|
+
const from = since ?? this.callSignalSeq;
|
|
735
|
+
this.pruneCallSignals();
|
|
736
|
+
let avail = this.callSignalLog.filter((s) => s.seq > from);
|
|
737
|
+
if (avail.length === 0) {
|
|
716
738
|
await new Promise((resolve) => {
|
|
717
739
|
const timer = setTimeout(() => {
|
|
718
740
|
this.callSignalWaiters = this.callSignalWaiters.filter((w) => w !== wake);
|
|
@@ -724,18 +746,19 @@ export class DaemonServer {
|
|
|
724
746
|
};
|
|
725
747
|
this.callSignalWaiters.push(wake);
|
|
726
748
|
});
|
|
749
|
+
this.pruneCallSignals();
|
|
750
|
+
avail = this.callSignalLog.filter((s) => s.seq > from);
|
|
727
751
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const kinds = signals.map((s) => { try {
|
|
752
|
+
if (avail.length) {
|
|
753
|
+
const kinds = avail.map((s) => { try {
|
|
731
754
|
return JSON.parse(s.data).type;
|
|
732
755
|
}
|
|
733
756
|
catch {
|
|
734
757
|
return "?";
|
|
735
758
|
} }).join(",");
|
|
736
|
-
this.logger.info(`call-poll -> browser: ${
|
|
759
|
+
this.logger.info(`call-poll -> browser: ${avail.length} signal(s) [${kinds}] (cursor ${from} -> ${this.callSignalSeq})`);
|
|
737
760
|
}
|
|
738
|
-
return { signals };
|
|
761
|
+
return { signals: avail.map(({ userid, data }) => ({ userid, data })), cursor: this.callSignalSeq };
|
|
739
762
|
},
|
|
740
763
|
subscribe: (emit) => {
|
|
741
764
|
const onEvent = (event) => emit(event);
|
|
@@ -1198,9 +1221,10 @@ export class DaemonServer {
|
|
|
1198
1221
|
if (!text)
|
|
1199
1222
|
return;
|
|
1200
1223
|
// Cap the backlog so a UI that stopped polling can't grow it forever.
|
|
1201
|
-
if (this.
|
|
1202
|
-
this.
|
|
1203
|
-
this.
|
|
1224
|
+
if (this.callSignalLog.length > 256)
|
|
1225
|
+
this.callSignalLog.shift();
|
|
1226
|
+
this.callSignalLog.push({ seq: ++this.callSignalSeq, ts: Date.now(), userid: evt.pubkey, data: text });
|
|
1227
|
+
this.pruneCallSignals();
|
|
1204
1228
|
const waiters = this.callSignalWaiters;
|
|
1205
1229
|
this.callSignalWaiters = [];
|
|
1206
1230
|
for (const wake of waiters)
|
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.272";
|
|
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.272",
|
|
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",
|