@decentnetwork/lan 0.1.146 → 0.1.148
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/carrier/peer-manager.d.ts +2 -0
- package/dist/carrier/peer-manager.js +4 -0
- package/dist/cli/commands.js +10 -1
- package/dist/daemon/ipc.d.ts +3 -1
- package/dist/daemon/ipc.js +5 -0
- package/dist/daemon/server.js +16 -0
- package/dist/ui/desktop/app.js +19 -2
- package/dist/ui/server.js +13 -0
- package/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -64,6 +64,8 @@ export declare class PeerManager extends EventEmitter {
|
|
|
64
64
|
sendFile(userid: string, data: Uint8Array, name: string): string | null;
|
|
65
65
|
/** Accept an incoming file offer. */
|
|
66
66
|
acceptFile(userid: string, fileNumber: number): void;
|
|
67
|
+
/** Cancel an in-flight send by content fileId. Returns true if it was found. */
|
|
68
|
+
cancelSend(userid: string, fileId: string): boolean;
|
|
67
69
|
/**
|
|
68
70
|
* Send an outbound friend request to a Carrier address (NOT a bare
|
|
69
71
|
* userid — sendFriendRequest needs the address form because it
|
|
@@ -125,6 +125,10 @@ export class PeerManager extends EventEmitter {
|
|
|
125
125
|
acceptFile(userid, fileNumber) {
|
|
126
126
|
this.peer?.acceptFile(userid, fileNumber);
|
|
127
127
|
}
|
|
128
|
+
/** Cancel an in-flight send by content fileId. Returns true if it was found. */
|
|
129
|
+
cancelSend(userid, fileId) {
|
|
130
|
+
return this.peer?.cancelFileById(userid, fileId, true) ?? false;
|
|
131
|
+
}
|
|
128
132
|
/**
|
|
129
133
|
* Send an outbound friend request to a Carrier address (NOT a bare
|
|
130
134
|
* userid — sendFriendRequest needs the address form because it
|
package/dist/cli/commands.js
CHANGED
|
@@ -1400,8 +1400,17 @@ export async function cmdProxyRouter(args) {
|
|
|
1400
1400
|
exits.push(ex);
|
|
1401
1401
|
}
|
|
1402
1402
|
}
|
|
1403
|
-
|
|
1403
|
+
// --all forces a FULL tunnel even with a routes.yaml: unmatched hosts go
|
|
1404
|
+
// through an exit region instead of DIRECT, so nothing can leak to the
|
|
1405
|
+
// local (out-of-region) IP. Previously --all was silently ignored on this
|
|
1406
|
+
// file-driven path. Prefer the file's declared default, else the first
|
|
1407
|
+
// region (e.g. china).
|
|
1408
|
+
defaultRegion = args.all
|
|
1409
|
+
? (routesFile.default ?? regions[0]?.name ?? "direct")
|
|
1410
|
+
: (routesFile.default ?? "direct");
|
|
1404
1411
|
console.log(`Loaded ${regions.length} region(s) from ${routesPath}`);
|
|
1412
|
+
if (args.all)
|
|
1413
|
+
console.log(`--all: unmatched hosts → region [${defaultRegion}] (full tunnel, no DIRECT leak)`);
|
|
1405
1414
|
}
|
|
1406
1415
|
else if (args.all) {
|
|
1407
1416
|
// ---- --all: every discovered peer through exits, single region --------
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -78,6 +78,8 @@ export interface IpcHandlers {
|
|
|
78
78
|
fileSend: (userid: string, path: string) => Promise<Record<string, unknown>>;
|
|
79
79
|
/** Delete file/chat messages by id (and their on-disk files). */
|
|
80
80
|
fileDelete: (peer: string, ids: string[]) => Promise<Record<string, unknown>>;
|
|
81
|
+
/** Cancel in-flight sends by chat message id. */
|
|
82
|
+
fileCancel: (peer: string, ids: string[]) => Promise<Record<string, unknown>>;
|
|
81
83
|
/** Mark a conversation read up to `ts` (defaults to now) — clears unread. */
|
|
82
84
|
chatMarkRead: (userid: string, ts?: number) => Promise<void>;
|
|
83
85
|
/** Re-read proxy allowlist from config and apply it to the running
|
|
@@ -102,7 +104,7 @@ export interface IpcHandlers {
|
|
|
102
104
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
103
105
|
}
|
|
104
106
|
export interface IpcRequest {
|
|
105
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "proxy-access" | "self-restart";
|
|
107
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "file-cancel" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "proxy-access" | "self-restart";
|
|
106
108
|
address?: string;
|
|
107
109
|
hello?: string;
|
|
108
110
|
userid?: string;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -225,6 +225,11 @@ export class IpcServer {
|
|
|
225
225
|
throw new Error("userid is required");
|
|
226
226
|
return await this.handlers.fileDelete(req.userid, req.ids ?? []);
|
|
227
227
|
}
|
|
228
|
+
case "file-cancel": {
|
|
229
|
+
if (!req.userid)
|
|
230
|
+
throw new Error("userid is required");
|
|
231
|
+
return await this.handlers.fileCancel(req.userid, req.ids ?? []);
|
|
232
|
+
}
|
|
228
233
|
case "chat-mark-read": {
|
|
229
234
|
if (!req.userid)
|
|
230
235
|
throw new Error("userid is required");
|
package/dist/daemon/server.js
CHANGED
|
@@ -493,6 +493,22 @@ export class DaemonServer {
|
|
|
493
493
|
this.logger.info(`Deleted ${removed.length} message(s), ${files} file(s) for ${userid.slice(0, 8)}`);
|
|
494
494
|
return { deleted: removed.length, files };
|
|
495
495
|
},
|
|
496
|
+
fileCancel: async (userid, ids) => {
|
|
497
|
+
let n = 0;
|
|
498
|
+
for (const id of ids) {
|
|
499
|
+
for (const [fid, v] of this.activeSends) {
|
|
500
|
+
if (v.peer !== userid || v.msgId !== id)
|
|
501
|
+
continue;
|
|
502
|
+
if (this.peerManager?.cancelSend(userid, fid))
|
|
503
|
+
n++;
|
|
504
|
+
this.activeSends.delete(fid);
|
|
505
|
+
this.messageStore?.patchFile(userid, id, { status: "failed", sent: 0, kbps: undefined });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
this.ipcEvents.emit("event", { type: "chat", userid, dir: "out" });
|
|
509
|
+
this.logger.info(`Cancelled ${n} send(s) for ${userid.slice(0, 8)}`);
|
|
510
|
+
return { cancelled: n };
|
|
511
|
+
},
|
|
496
512
|
chatMarkRead: async (userid, ts) => {
|
|
497
513
|
this.friendMeta?.markRead(userid, ts);
|
|
498
514
|
},
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -216,6 +216,7 @@ const dkApi = {
|
|
|
216
216
|
alias: (userid, alias) => dkPost("/api/friend-alias", { userid, alias }),
|
|
217
217
|
markRead: (userid) => dkPost("/api/chat-mark-read", { userid }),
|
|
218
218
|
delFiles: (userid, ids) => dkPost("/api/file-delete", { userid, ids }),
|
|
219
|
+
cancelSend: (userid, ids) => dkPost("/api/file-cancel", { userid, ids }),
|
|
219
220
|
setProfile: (name, description) => dkPost("/api/set-profile", { name, description }),
|
|
220
221
|
sendFile: async (userid, file) => {
|
|
221
222
|
try {
|
|
@@ -1054,7 +1055,7 @@ const mediaBtnStyle = {
|
|
|
1054
1055
|
justifyContent: "center",
|
|
1055
1056
|
lineHeight: 0
|
|
1056
1057
|
};
|
|
1057
|
-
function Msg({ m, peer, T, onTheater, onDelete, selMode, selected, onToggleSel }) {
|
|
1058
|
+
function Msg({ m, peer, T, onTheater, onDelete, onCancel, selMode, selected, onToggleSel }) {
|
|
1058
1059
|
const mine = m.from === "me";
|
|
1059
1060
|
return /* @__PURE__ */ React.createElement(
|
|
1060
1061
|
"div",
|
|
@@ -1153,7 +1154,18 @@ function Msg({ m, peer, T, onTheater, onDelete, selMode, selected, onToggleSel }
|
|
|
1153
1154
|
const icon = /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 7, flexShrink: 0, background: mine ? "rgba(255,255,255,0.16)" : "var(--chip)", display: "flex", alignItems: "center", justifyContent: "center", color: mine ? "#fff" : "var(--accent)" } }, /* @__PURE__ */ React.createElement(Icon, { name: m.file.media === "image" ? "image" : m.file.media === "video" ? "video" : m.file.media === "audio" ? "play" : "file", size: 18, stroke: 1.9 }));
|
|
1154
1155
|
const meta = /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: mine ? "#fff" : "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.file.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: mine ? "rgba(255,255,255,0.7)" : "var(--faint)", marginTop: 1 } }, m.file.status === "queued" ? `${m.file.size} \xB7 ${T.queued || "queued"}` : m.file.status === "sending" ? `${m.file.size} \xB7 ${dkPct(m.file.pct)}${m.file.kbps ? " \xB7 " + dkSpeed(m.file.kbps) : ""}` : m.file.status === "failed" ? `${m.file.size} \xB7 failed` : mine ? `${m.file.size} \xB7 sent` : `${m.file.size} \xB7 ${T.open || "open"}`));
|
|
1155
1156
|
if (mine) {
|
|
1156
|
-
return /* @__PURE__ */ React.createElement("div", { style: cardStyle }, icon, meta, m.file.status === "queued" ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 16, stroke: 2, color: "rgba(255,255,255,0.85)" }) : m.file.status === "sending" ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.9)", whiteSpace: "nowrap" } }, dkPct(m.file.pct), m.file.kbps ? " \xB7 " + dkSpeed(m.file.kbps) : "") : m.file.status === "failed" ? /* @__PURE__ */ React.createElement(Icon, { name: "x", size: 16, stroke: 2.4, color: "rgba(255,200,190,0.95)" }) : /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 16, stroke: 2, color: "rgba(255,255,255,0.85)" }))
|
|
1157
|
+
return /* @__PURE__ */ React.createElement("div", { style: cardStyle }, icon, meta, m.file.status === "queued" ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 16, stroke: 2, color: "rgba(255,255,255,0.85)" }) : m.file.status === "sending" ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.9)", whiteSpace: "nowrap" } }, dkPct(m.file.pct), m.file.kbps ? " \xB7 " + dkSpeed(m.file.kbps) : "") : m.file.status === "failed" ? /* @__PURE__ */ React.createElement(Icon, { name: "x", size: 16, stroke: 2.4, color: "rgba(255,200,190,0.95)" }) : /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 16, stroke: 2, color: "rgba(255,255,255,0.85)" }), (m.file.status === "sending" || m.file.status === "queued") && m.id && /* @__PURE__ */ React.createElement(
|
|
1158
|
+
"button",
|
|
1159
|
+
{
|
|
1160
|
+
title: T.cancel || "cancel",
|
|
1161
|
+
onClick: (e) => {
|
|
1162
|
+
e.stopPropagation();
|
|
1163
|
+
onCancel(m.id);
|
|
1164
|
+
},
|
|
1165
|
+
style: { background: "rgba(255,255,255,0.16)", border: "none", borderRadius: 6, cursor: "pointer", padding: 3, marginLeft: 2, display: "inline-flex", flexShrink: 0 }
|
|
1166
|
+
},
|
|
1167
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "x", size: 13, stroke: 2.4, color: "#fff" })
|
|
1168
|
+
));
|
|
1157
1169
|
}
|
|
1158
1170
|
return /* @__PURE__ */ React.createElement(
|
|
1159
1171
|
"div",
|
|
@@ -1225,6 +1237,10 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1225
1237
|
n.has(id) ? n.delete(id) : n.add(id);
|
|
1226
1238
|
return n;
|
|
1227
1239
|
});
|
|
1240
|
+
const doCancel = (id) => {
|
|
1241
|
+
if (id) dkApi.cancelSend(peer.userId, [id]).catch(() => {
|
|
1242
|
+
});
|
|
1243
|
+
};
|
|
1228
1244
|
const doDelete = (ids) => {
|
|
1229
1245
|
ids = ids.filter(Boolean);
|
|
1230
1246
|
if (!ids.length) return;
|
|
@@ -1309,6 +1325,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1309
1325
|
T,
|
|
1310
1326
|
onTheater: setTheater,
|
|
1311
1327
|
onDelete: (id) => doDelete([id]),
|
|
1328
|
+
onCancel: doCancel,
|
|
1312
1329
|
selMode,
|
|
1313
1330
|
selected: sel.has(m.id),
|
|
1314
1331
|
onToggleSel: toggleSel
|
package/dist/ui/server.js
CHANGED
|
@@ -271,6 +271,19 @@ export function startFriendUi(opts) {
|
|
|
271
271
|
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, ...(r.data ?? {}) } : { ok: false, error: r.error });
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
|
+
// Cancel in-flight sends by chat message id.
|
|
275
|
+
if (req.method === "POST" && url === "/api/file-cancel") {
|
|
276
|
+
const body = await readBody(req);
|
|
277
|
+
const userid = typeof body.userid === "string" ? body.userid : "";
|
|
278
|
+
const ids = Array.isArray(body.ids) ? body.ids.filter((x) => typeof x === "string") : [];
|
|
279
|
+
if (!userid || !ids.length) {
|
|
280
|
+
sendJson(res, 400, { ok: false, error: "userid and ids required" });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const r = await opts.call({ op: "file-cancel", userid, ids });
|
|
284
|
+
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, ...(r.data ?? {}) } : { ok: false, error: r.error });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
274
287
|
// ── File transfer ──────────────────────────────────────────────────
|
|
275
288
|
// Upload: the browser POSTs raw file bytes with ?userid=&name= in the
|
|
276
289
|
// query. We stream them to a temp file (no in-memory buffering of large
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.148",
|
|
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",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@decentnetwork/dora": "^0.1.11",
|
|
82
|
-
"@decentnetwork/peer": "^0.1.
|
|
82
|
+
"@decentnetwork/peer": "^0.1.79",
|
|
83
83
|
"ink": "^5.2.1",
|
|
84
84
|
"js-yaml": "^4.1.0",
|
|
85
85
|
"node-forge": "^1.4.0",
|