@decentnetwork/beagle 0.1.38 → 0.1.40
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/desktop/app.js +17 -5
- package/dist/server.js +42 -11
- package/package.json +1 -1
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.39";
|
|
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"/>',
|
|
@@ -1882,7 +1882,7 @@ function DkChatForm({ form, submitted, peer, onSubmit }) {
|
|
|
1882
1882
|
return null;
|
|
1883
1883
|
}), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
|
|
1884
1884
|
}
|
|
1885
|
-
function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onCall, onFormSubmit, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
1885
|
+
function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onOpenFile, onCall, onFormSubmit, answered, selMode, selected, onToggleSel, busy, isGroup }) {
|
|
1886
1886
|
const mine = m.from === "me";
|
|
1887
1887
|
const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
|
|
1888
1888
|
const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
|
|
@@ -2061,7 +2061,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
|
|
|
2061
2061
|
{
|
|
2062
2062
|
style: { ...cardStyle, cursor: "pointer", opacity: btnBusy ? 0.85 : 1 },
|
|
2063
2063
|
title: T.open || "open",
|
|
2064
|
-
onClick: () => window.open(dkFileUrl(m.file.name), "_blank", "noopener")
|
|
2064
|
+
onClick: () => onOpenFile ? onOpenFile(m.file.name) : window.open(dkFileUrl(m.file.name), "_blank", "noopener")
|
|
2065
2065
|
},
|
|
2066
2066
|
icon,
|
|
2067
2067
|
meta,
|
|
@@ -2124,7 +2124,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
|
|
|
2124
2124
|
{
|
|
2125
2125
|
style: cardStyle,
|
|
2126
2126
|
title: "open",
|
|
2127
|
-
onClick: () => window.open(dkFileUrl(m.file.name), "_blank", "noopener")
|
|
2127
|
+
onClick: () => onOpenFile ? onOpenFile(m.file.name) : window.open(dkFileUrl(m.file.name), "_blank", "noopener")
|
|
2128
2128
|
},
|
|
2129
2129
|
icon,
|
|
2130
2130
|
meta,
|
|
@@ -2309,10 +2309,21 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
2309
2309
|
return;
|
|
2310
2310
|
dkApi.revealFile(name).then((r) => {
|
|
2311
2311
|
if (!r || r.ok === false) {
|
|
2312
|
-
|
|
2312
|
+
const missing = /not found/i.test(r && r.error || "");
|
|
2313
|
+
showFlash("err", missing ? lang === "zh" ? "\u672C\u5730\u6CA1\u6709\u8FD9\u4E2A\u6587\u4EF6\u7684\u526F\u672C(\u5B83\u662F\u65E7\u7248\u672C\u53D1\u9001\u7684,\u5F53\u65F6\u53D1\u5B8C\u5373\u5220;\u65B0\u53D1\u9001\u7684\u6587\u4EF6\u90FD\u4F1A\u4FDD\u7559)" : "No local copy of this file \u2014 it was sent by an older version that did not keep sent copies. Files sent from now on are kept." : (lang === "zh" ? "\u65E0\u6CD5\u5728\u6587\u4EF6\u7BA1\u7406\u5668\u4E2D\u663E\u793A: " : "Could not reveal file: ") + (r && r.error || ""));
|
|
2313
2314
|
}
|
|
2314
2315
|
});
|
|
2315
2316
|
};
|
|
2317
|
+
const doOpenFile = (name) => {
|
|
2318
|
+
if (!name)
|
|
2319
|
+
return;
|
|
2320
|
+
fetch(dkFileDownloadUrl(name), { method: "GET", headers: { range: "bytes=0-0" } }).then((r) => {
|
|
2321
|
+
if (r.ok || r.status === 206)
|
|
2322
|
+
window.open(dkFileUrl(name), "_blank", "noopener");
|
|
2323
|
+
else
|
|
2324
|
+
showFlash("err", lang === "zh" ? "\u672C\u5730\u6CA1\u6709\u8FD9\u4E2A\u6587\u4EF6\u7684\u526F\u672C(\u65E7\u7248\u672C\u53D1\u9001\u7684\u6587\u4EF6\u672A\u4FDD\u7559;\u65B0\u53D1\u9001\u7684\u90FD\u4F1A\u4FDD\u7559)" : "No local copy \u2014 sent before local copies were kept.");
|
|
2325
|
+
}).catch(() => showFlash("err", lang === "zh" ? "\u6253\u5F00\u5931\u8D25" : "Could not open the file"));
|
|
2326
|
+
};
|
|
2316
2327
|
const doCancel = (id) => {
|
|
2317
2328
|
if (!id || fileBusy[id])
|
|
2318
2329
|
return;
|
|
@@ -2525,6 +2536,7 @@ ${peer.address}`
|
|
|
2525
2536
|
onCancel: doCancel,
|
|
2526
2537
|
onRetry: doRetry,
|
|
2527
2538
|
onReveal: doReveal,
|
|
2539
|
+
onOpenFile: doOpenFile,
|
|
2528
2540
|
onCall,
|
|
2529
2541
|
onFormSubmit: () => onReloadThread && onReloadThread(),
|
|
2530
2542
|
answered,
|
package/dist/server.js
CHANGED
|
@@ -305,6 +305,39 @@ async function agentnetRestart() {
|
|
|
305
305
|
}
|
|
306
306
|
return { ok: false, error: lastErr };
|
|
307
307
|
}
|
|
308
|
+
// ── saved-files locations ───────────────────────────────────────────────────
|
|
309
|
+
// The daemon (often running as root) owns downloads/ — on such machines this
|
|
310
|
+
// user-level process gets EACCES writing there. Files WE save (sent copies,
|
|
311
|
+
// browser-WebRTC receives) then go to a sibling `sent/` dir we own; readers
|
|
312
|
+
// (open / download / reveal) look in downloads first, then sent.
|
|
313
|
+
function sentDirFor(downloadsDir) {
|
|
314
|
+
return join(dirname(downloadsDir), "sent");
|
|
315
|
+
}
|
|
316
|
+
async function pickWritableSaveDir(downloadsDir) {
|
|
317
|
+
const fsp = await import("node:fs/promises");
|
|
318
|
+
try {
|
|
319
|
+
await fsp.mkdir(downloadsDir, { recursive: true });
|
|
320
|
+
await fsp.access(downloadsDir, (await import("node:fs")).constants.W_OK);
|
|
321
|
+
return downloadsDir;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
const alt = sentDirFor(downloadsDir);
|
|
325
|
+
await fsp.mkdir(alt, { recursive: true });
|
|
326
|
+
return alt;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/** Resolve a saved file by name across downloads/ and sent/, with a
|
|
330
|
+
* path-traversal guard. Returns null when absent from both. */
|
|
331
|
+
function findSavedFile(downloadsDir, name) {
|
|
332
|
+
if (!name)
|
|
333
|
+
return null;
|
|
334
|
+
for (const dir of [downloadsDir, sentDirFor(downloadsDir)]) {
|
|
335
|
+
const f = join(dir, name);
|
|
336
|
+
if (f.startsWith(dir) && existsSync(f))
|
|
337
|
+
return f;
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
308
341
|
/** True only when the request came from the local machine. Used to gate the
|
|
309
342
|
* "Sign in with Decent" routes so binding the UI to a LAN IP can't expose
|
|
310
343
|
* identity signing to other hosts. The popup always runs in the local user's
|
|
@@ -977,8 +1010,7 @@ export function startBeagleServer(opts) {
|
|
|
977
1010
|
const keep = !!opts.downloadsDir;
|
|
978
1011
|
let destDir;
|
|
979
1012
|
if (keep) {
|
|
980
|
-
destDir = opts.downloadsDir;
|
|
981
|
-
await fsp.mkdir(destDir, { recursive: true });
|
|
1013
|
+
destDir = await pickWritableSaveDir(opts.downloadsDir);
|
|
982
1014
|
}
|
|
983
1015
|
else {
|
|
984
1016
|
destDir = await fsp.mkdtemp(join(os.tmpdir(), "agentnet-up-"));
|
|
@@ -1036,16 +1068,15 @@ export function startBeagleServer(opts) {
|
|
|
1036
1068
|
return;
|
|
1037
1069
|
}
|
|
1038
1070
|
const fs = await import("node:fs");
|
|
1039
|
-
const
|
|
1040
|
-
await fsp.mkdir(opts.downloadsDir, { recursive: true });
|
|
1071
|
+
const saveDir = await pickWritableSaveDir(opts.downloadsDir);
|
|
1041
1072
|
let finalName = safe;
|
|
1042
|
-
let finalPath = join(
|
|
1073
|
+
let finalPath = join(saveDir, finalName);
|
|
1043
1074
|
const dot = safe.lastIndexOf(".");
|
|
1044
1075
|
const base = dot > 0 ? safe.slice(0, dot) : safe;
|
|
1045
1076
|
const ext = dot > 0 ? safe.slice(dot) : "";
|
|
1046
1077
|
for (let i = 1; existsSync(finalPath); i++) {
|
|
1047
1078
|
finalName = `${base}-${i}${ext}`;
|
|
1048
|
-
finalPath = join(
|
|
1079
|
+
finalPath = join(saveDir, finalName);
|
|
1049
1080
|
}
|
|
1050
1081
|
try {
|
|
1051
1082
|
await new Promise((resolve2, reject) => {
|
|
@@ -1088,8 +1119,8 @@ export function startBeagleServer(opts) {
|
|
|
1088
1119
|
}
|
|
1089
1120
|
const body = await readBody(req);
|
|
1090
1121
|
const name = (typeof body.name === "string" ? body.name : "").replace(/[/\\]/g, "");
|
|
1091
|
-
const file =
|
|
1092
|
-
if (!
|
|
1122
|
+
const file = findSavedFile(opts.downloadsDir, name);
|
|
1123
|
+
if (!file) {
|
|
1093
1124
|
sendJson(res, 404, { ok: false, error: "not found" });
|
|
1094
1125
|
return;
|
|
1095
1126
|
}
|
|
@@ -1105,9 +1136,9 @@ export function startBeagleServer(opts) {
|
|
|
1105
1136
|
}
|
|
1106
1137
|
const q = new URL(req.url || "", "http://x").searchParams;
|
|
1107
1138
|
const name = (q.get("name") || "").replace(/[/\\]/g, "");
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
if (!
|
|
1139
|
+
// Looks in downloads/ then sent/ (traversal-guarded in the helper).
|
|
1140
|
+
const file = findSavedFile(opts.downloadsDir, name);
|
|
1141
|
+
if (!file) {
|
|
1111
1142
|
res.writeHead(404);
|
|
1112
1143
|
res.end("not found");
|
|
1113
1144
|
return;
|
package/package.json
CHANGED