@decentnetwork/lan 0.1.102 → 0.1.104
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/console/console.js +73 -1
- package/dist/daemon/ipc.d.ts +7 -1
- package/dist/daemon/ipc.js +38 -0
- package/dist/daemon/server.d.ts +4 -0
- package/dist/daemon/server.js +18 -0
- package/dist/ui/desktop/app.js +2 -2
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/console/console.js
CHANGED
|
@@ -435,10 +435,24 @@ function App({ client }) {
|
|
|
435
435
|
}
|
|
436
436
|
};
|
|
437
437
|
void tick();
|
|
438
|
-
|
|
438
|
+
let iv = setInterval(() => {
|
|
439
|
+
if (alive) void tick();
|
|
440
|
+
}, 6e3);
|
|
441
|
+
const stop = client.subscribe(
|
|
442
|
+
() => {
|
|
443
|
+
if (alive) void tick();
|
|
444
|
+
},
|
|
445
|
+
() => {
|
|
446
|
+
clearInterval(iv);
|
|
447
|
+
iv = setInterval(() => {
|
|
448
|
+
if (alive) void tick();
|
|
449
|
+
}, 2500);
|
|
450
|
+
}
|
|
451
|
+
);
|
|
439
452
|
return () => {
|
|
440
453
|
alive = false;
|
|
441
454
|
clearInterval(iv);
|
|
455
|
+
stop();
|
|
442
456
|
};
|
|
443
457
|
}, [client, selId]);
|
|
444
458
|
React.useEffect(() => {
|
|
@@ -909,6 +923,64 @@ var DaemonClient = class _DaemonClient {
|
|
|
909
923
|
});
|
|
910
924
|
});
|
|
911
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Open a persistent subscription to daemon push events (chat / presence /
|
|
928
|
+
* friend-request). Calls `onEvent` for each event and auto-reconnects with
|
|
929
|
+
* backoff if the socket drops. Returns a stop() to tear it down.
|
|
930
|
+
*
|
|
931
|
+
* Degrades gracefully: an older daemon that doesn't know the `subscribe`
|
|
932
|
+
* op replies `{ok:false}` and closes — we surface that via `onUnsupported`
|
|
933
|
+
* so the caller can keep its polling fallback instead of reconnecting in a
|
|
934
|
+
* tight loop. A daemon that supports it acks with `{subscribed:true}` first.
|
|
935
|
+
*/
|
|
936
|
+
subscribe(onEvent, onUnsupported) {
|
|
937
|
+
let stopped = false;
|
|
938
|
+
let sock;
|
|
939
|
+
let retry;
|
|
940
|
+
const connect = () => {
|
|
941
|
+
if (stopped) return;
|
|
942
|
+
let buf = "";
|
|
943
|
+
let acked = false;
|
|
944
|
+
sock = createConnection(this.sockPath);
|
|
945
|
+
sock.on("connect", () => sock.write(JSON.stringify({ op: "subscribe" }) + "\n"));
|
|
946
|
+
sock.on("data", (c) => {
|
|
947
|
+
buf += c.toString("utf-8");
|
|
948
|
+
let nl;
|
|
949
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
950
|
+
const line = buf.slice(0, nl);
|
|
951
|
+
buf = buf.slice(nl + 1);
|
|
952
|
+
if (!line) continue;
|
|
953
|
+
try {
|
|
954
|
+
const msg = JSON.parse(line);
|
|
955
|
+
if (!acked) {
|
|
956
|
+
acked = true;
|
|
957
|
+
if (msg.ok === false) {
|
|
958
|
+
stopped = true;
|
|
959
|
+
sock?.end();
|
|
960
|
+
onUnsupported?.();
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (msg.event) onEvent(msg.event);
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
const reconnect = () => {
|
|
971
|
+
if (stopped) return;
|
|
972
|
+
retry = setTimeout(connect, 1500);
|
|
973
|
+
};
|
|
974
|
+
sock.on("error", reconnect);
|
|
975
|
+
sock.on("close", reconnect);
|
|
976
|
+
};
|
|
977
|
+
connect();
|
|
978
|
+
return () => {
|
|
979
|
+
stopped = true;
|
|
980
|
+
if (retry) clearTimeout(retry);
|
|
981
|
+
sock?.destroy();
|
|
982
|
+
};
|
|
983
|
+
}
|
|
912
984
|
static data(r) {
|
|
913
985
|
return r.ok && r.data ? r.data : {};
|
|
914
986
|
}
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -56,6 +56,12 @@ export interface IpcHandlers {
|
|
|
56
56
|
name?: string;
|
|
57
57
|
description?: string;
|
|
58
58
|
}) => Promise<void>;
|
|
59
|
+
/** Subscribe to daemon push events (chat / presence / friend-request). The
|
|
60
|
+
* IPC server keeps the connection open and calls `emit` for each event;
|
|
61
|
+
* the returned function unsubscribes (called on socket close). Optional —
|
|
62
|
+
* when absent, clients fall back to polling. Lets the TUI/UI react to new
|
|
63
|
+
* messages instantly instead of polling on a tight interval. */
|
|
64
|
+
subscribe?: (emit: (event: Record<string, unknown>) => void) => () => void;
|
|
59
65
|
/** Offer a local file (by path) to a friend over toxcore file transfer. */
|
|
60
66
|
fileSend: (userid: string, path: string) => Promise<Record<string, unknown>>;
|
|
61
67
|
/** Mark a conversation read up to `ts` (defaults to now) — clears unread. */
|
|
@@ -76,7 +82,7 @@ export interface IpcHandlers {
|
|
|
76
82
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
77
83
|
}
|
|
78
84
|
export interface IpcRequest {
|
|
79
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "set-profile" | "file-send" | "chat-mark-read" | "proxy-reload" | "self-restart";
|
|
85
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "set-profile" | "file-send" | "chat-mark-read" | "subscribe" | "proxy-reload" | "self-restart";
|
|
80
86
|
address?: string;
|
|
81
87
|
hello?: string;
|
|
82
88
|
userid?: string;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -109,6 +109,44 @@ export class IpcServer {
|
|
|
109
109
|
reply({ ok: false, error: "malformed request (not JSON)" });
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
|
+
// Streaming subscription: keep the socket OPEN and push newline-JSON
|
|
113
|
+
// events until the client disconnects. This is the one op that breaks
|
|
114
|
+
// the one-shot model — it lets the TUI/UI react to new messages and
|
|
115
|
+
// presence changes instantly instead of polling on a tight interval.
|
|
116
|
+
if (req.op === "subscribe") {
|
|
117
|
+
if (!this.handlers.subscribe) {
|
|
118
|
+
reply({ ok: false, error: "subscribe not supported" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// First line acks the subscription so the client knows push is live
|
|
122
|
+
// (and can distinguish an old daemon that rejects the op).
|
|
123
|
+
try {
|
|
124
|
+
sock.write(JSON.stringify({ ok: true, data: { subscribed: true } }) + "\n");
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
sock.end();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const unsubscribe = this.handlers.subscribe((event) => {
|
|
131
|
+
try {
|
|
132
|
+
sock.write(JSON.stringify({ event }) + "\n");
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// socket gone; cleanup happens via the close handler
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const cleanup = () => {
|
|
139
|
+
try {
|
|
140
|
+
unsubscribe();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// best-effort
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
sock.on("close", cleanup);
|
|
147
|
+
sock.on("end", cleanup);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
112
150
|
void this.dispatch(req)
|
|
113
151
|
.then((data) => reply({ ok: true, ...(data ? { data } : {}) }))
|
|
114
152
|
.catch((err) => reply({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
package/dist/daemon/server.d.ts
CHANGED
|
@@ -37,6 +37,10 @@ export declare class DaemonServer {
|
|
|
37
37
|
* the `agentnet ui` chat window; not persisted across restarts. */
|
|
38
38
|
private messageStore?;
|
|
39
39
|
private friendMeta?;
|
|
40
|
+
/** Push-event fan-out for IPC subscribers (TUI/UI). Fires on inbound/outbound
|
|
41
|
+
* chat, presence changes, and friend-requests so clients can refresh on
|
|
42
|
+
* demand instead of polling on a tight interval. */
|
|
43
|
+
private readonly ipcEvents;
|
|
40
44
|
private startedAt;
|
|
41
45
|
private isRunning;
|
|
42
46
|
private statusTimer?;
|
package/dist/daemon/server.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Daemon Server — wires together all components
|
|
3
3
|
*/
|
|
4
4
|
import { resolve } from "path";
|
|
5
|
+
import { EventEmitter } from "events";
|
|
5
6
|
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
6
7
|
import { PeerManager } from "../carrier/peer-manager.js";
|
|
7
8
|
import { TunDevice } from "../tun/tun-device.js";
|
|
@@ -56,6 +57,10 @@ export class DaemonServer {
|
|
|
56
57
|
* the `agentnet ui` chat window; not persisted across restarts. */
|
|
57
58
|
messageStore;
|
|
58
59
|
friendMeta;
|
|
60
|
+
/** Push-event fan-out for IPC subscribers (TUI/UI). Fires on inbound/outbound
|
|
61
|
+
* chat, presence changes, and friend-requests so clients can refresh on
|
|
62
|
+
* demand instead of polling on a tight interval. */
|
|
63
|
+
ipcEvents = new EventEmitter();
|
|
59
64
|
startedAt = 0;
|
|
60
65
|
isRunning = false;
|
|
61
66
|
statusTimer;
|
|
@@ -204,6 +209,7 @@ export class DaemonServer {
|
|
|
204
209
|
// Start IPC as soon as the peer is up. The CLI uses it to drive
|
|
205
210
|
// operations that need the daemon's Carrier identity (e.g.
|
|
206
211
|
// friend-request) without spawning a competing Peer instance.
|
|
212
|
+
this.ipcEvents.setMaxListeners(50);
|
|
207
213
|
this.ipcServer = new IpcServer(ipcSocketPath(this.config.carrier.dataDir), {
|
|
208
214
|
friendRequest: async (address, hello) => {
|
|
209
215
|
await this.peerManager.sendFriendRequest(address, hello);
|
|
@@ -309,6 +315,11 @@ export class DaemonServer {
|
|
|
309
315
|
chatMarkRead: async (userid, ts) => {
|
|
310
316
|
this.friendMeta?.markRead(userid, ts);
|
|
311
317
|
},
|
|
318
|
+
subscribe: (emit) => {
|
|
319
|
+
const onEvent = (event) => emit(event);
|
|
320
|
+
this.ipcEvents.on("event", onEvent);
|
|
321
|
+
return () => this.ipcEvents.off("event", onEvent);
|
|
322
|
+
},
|
|
312
323
|
proxyReload: async () => {
|
|
313
324
|
// Re-read the proxy allowlist from disk and push it into the
|
|
314
325
|
// running proxy without a daemon restart (which would drop
|
|
@@ -555,6 +566,11 @@ export class DaemonServer {
|
|
|
555
566
|
this.peerManager.on("message", (pubkey, text) => {
|
|
556
567
|
this.logChat(pubkey, "in", text);
|
|
557
568
|
});
|
|
569
|
+
// Presence: push to IPC subscribers so the TUI/UI reflects online/offline
|
|
570
|
+
// transitions immediately instead of on the next poll tick.
|
|
571
|
+
this.peerManager.on("friend-connection", (evt) => {
|
|
572
|
+
this.ipcEvents.emit("event", { type: "presence", userid: evt?.pubkey, status: evt?.status });
|
|
573
|
+
});
|
|
558
574
|
// File transfer (toxcore-standard): auto-accept offers from friends and
|
|
559
575
|
// save completed files under <configDir>/downloads/.
|
|
560
576
|
this.peerManager.on("file-offer", (o) => {
|
|
@@ -580,6 +596,7 @@ export class DaemonServer {
|
|
|
580
596
|
void pubkeyHexToUserid(req.pubkey).then((userid) => {
|
|
581
597
|
const who = `${req.name || "(unnamed)"} ${userid}`;
|
|
582
598
|
const hello = req.hello ? ` hello="${req.hello.slice(0, 60)}"` : "";
|
|
599
|
+
this.ipcEvents.emit("event", { type: "request", userid });
|
|
583
600
|
if (autoAcceptFriends) {
|
|
584
601
|
this.logger.info(`Friend request from ${who}${hello} — auto-accepting`);
|
|
585
602
|
this.peerManager?.acceptFriendRequest(req.pubkey).catch((err) => {
|
|
@@ -802,6 +819,7 @@ export class DaemonServer {
|
|
|
802
819
|
logChat(userid, dir, text) {
|
|
803
820
|
this.messageStore?.append(userid, dir, text);
|
|
804
821
|
this.friendMeta?.ensure(userid);
|
|
822
|
+
this.ipcEvents.emit("event", { type: "chat", userid, dir });
|
|
805
823
|
}
|
|
806
824
|
/** Per-peer timestamp of the last self-heal friend-request re-send, so
|
|
807
825
|
* the watchdog escalates at most once per SELF_HEAL_REFRIEND_MS. */
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1220,7 +1220,7 @@ const STR = {
|
|
|
1220
1220
|
add: "Add",
|
|
1221
1221
|
search: "search peers / ip\u2026",
|
|
1222
1222
|
addSending: "sending friend-request\u2026",
|
|
1223
|
-
addSent: "friend-request sent \u2014 they
|
|
1223
|
+
addSent: "friend-request sent \u2014 delivered when they\u2019re online; appears here once they accept",
|
|
1224
1224
|
addFailed: "could not send",
|
|
1225
1225
|
requests: "Friend requests",
|
|
1226
1226
|
accept: "accept",
|
|
@@ -1295,7 +1295,7 @@ const STR = {
|
|
|
1295
1295
|
add: "\u6DFB\u52A0",
|
|
1296
1296
|
search: "\u641C\u7D22\u597D\u53CB / IP\u2026",
|
|
1297
1297
|
addSending: "\u6B63\u5728\u53D1\u9001\u597D\u53CB\u8BF7\u6C42\u2026",
|
|
1298
|
-
addSent: "\u597D\u53CB\u8BF7\u6C42\u5DF2\u53D1\u9001 \u2014 \u5BF9\u65B9\u63A5\u53D7\u540E\
|
|
1298
|
+
addSent: "\u597D\u53CB\u8BF7\u6C42\u5DF2\u53D1\u9001 \u2014 \u5BF9\u65B9\u4E0A\u7EBF\u540E\u9001\u8FBE\uFF0C\u63A5\u53D7\u540E\u51FA\u73B0\u5728\u5217\u8868",
|
|
1299
1299
|
addFailed: "\u53D1\u9001\u5931\u8D25",
|
|
1300
1300
|
requests: "\u597D\u53CB\u8BF7\u6C42",
|
|
1301
1301
|
accept: "\u63A5\u53D7",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
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",
|