@decentnetwork/beagle 0.1.82 → 0.1.83
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 +31 -2
- package/dist/server.js +81 -1
- package/package.json +2 -2
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.82";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -4315,6 +4315,11 @@ ${peer.address}`
|
|
|
4315
4315
|
return [values, setTweak];
|
|
4316
4316
|
}
|
|
4317
4317
|
Object.assign(window, { useTweaks });
|
|
4318
|
+
function ownershipFix(info) {
|
|
4319
|
+
const r = String(info && info.selfUpdateBlocked || "");
|
|
4320
|
+
const at = r.indexOf("sudo chown");
|
|
4321
|
+
return at >= 0 ? r.slice(at) : 'sudo chown -R $(id -u):$(id -g) "$(npm prefix -g)/lib/node_modules/@decentnetwork"';
|
|
4322
|
+
}
|
|
4318
4323
|
function dkParseDeepLinkAddress() {
|
|
4319
4324
|
try {
|
|
4320
4325
|
const h = window.location.hash || "";
|
|
@@ -4843,7 +4848,31 @@ ${peer.address}`
|
|
|
4843
4848
|
background: tone ? "var(--accent)" : "transparent",
|
|
4844
4849
|
color: tone ? "#fff" : "var(--text)"
|
|
4845
4850
|
} }, label);
|
|
4846
|
-
return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" &&
|
|
4851
|
+
return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 95, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 430, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 700, color: "var(--text)" } }, T.updTitle || "Update available"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, behind.map((u) => /* @__PURE__ */ React.createElement("div", { key: u.pkg, style: { display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12.5 } }, /* @__PURE__ */ React.createElement("span", { style: { width: 70, color: "var(--faint)" } }, short(u.pkg)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--dim)" } }, u.current), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--faint)" } }, "\u2192"), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--accent)", fontWeight: 700 } }, u.latest)))), phase === "idle" && info.selfUpdatable === false && // Say it before the button is pressed. The card used to offer an
|
|
4852
|
+
// update, fail on permissions, and hand back `sudo npm i -g` — which
|
|
4853
|
+
// works once and leaves the directory root-owned, so the next update
|
|
4854
|
+
// fails identically. That advice was the loop. This is the command
|
|
4855
|
+
// that ends it, and it is needed once.
|
|
4856
|
+
/* @__PURE__ */ React.createElement("div", { style: {
|
|
4857
|
+
fontFamily: "var(--ui)",
|
|
4858
|
+
fontSize: 12,
|
|
4859
|
+
color: "var(--dim)",
|
|
4860
|
+
lineHeight: 1.6,
|
|
4861
|
+
borderLeft: "2px solid var(--danger)",
|
|
4862
|
+
paddingLeft: 10
|
|
4863
|
+
} }, T.updNoPerm || "Automatic updates cannot write the global npm directory \u2014 this is what a first install with sudo leaves behind. Run this once and it never comes back:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6, alignItems: "center", marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: {
|
|
4864
|
+
flex: 1,
|
|
4865
|
+
minWidth: 0,
|
|
4866
|
+
fontFamily: "var(--mono)",
|
|
4867
|
+
fontSize: 11.5,
|
|
4868
|
+
color: "var(--text)",
|
|
4869
|
+
background: "var(--panel-2)",
|
|
4870
|
+
border: "1px solid var(--line)",
|
|
4871
|
+
borderRadius: 8,
|
|
4872
|
+
padding: "8px 10px",
|
|
4873
|
+
userSelect: "all",
|
|
4874
|
+
wordBreak: "break-all"
|
|
4875
|
+
} }, ownershipFix(info)), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy(ownershipFix(info)) }))), phase === "idle" && appBehind && info.mode === "dev" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updDev || "Running from a source checkout \u2014 update with:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)" } }, "git pull && npm run build")), phase === "idle" && appBehind && info.mode === "unknown" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updUnknown || "Update now runs the global install. If that is not how this was installed, run:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/beagle@latest"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/beagle@latest") }))), phase === "idle" && appBehind && info.mode === "npx" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updNpx || "Running via npx \u2014 restarting picks up the latest version automatically."), daemonPeerBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updPeerDaemon || "The peer version belongs to the running agentnet daemon, not to this app. Update it with:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/lan@latest && agentnet restart") }))), lanBehind && phase === "idle" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan): one click reinstalls it and asks the running daemon to restart itself. Manual equivalent:", /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, "npm i -g @decentnetwork/lan@latest && agentnet restart"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy("npm i -g @decentnetwork/lan@latest && agentnet restart") }))), phase === "lan-done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updLanDone || "lan updated \u2014 daemon restarted with the new version \u2713"), phase === "lan-norestart" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--warn, #f59e0b)" } }, T.updLanNoRestart || "lan updated, but the daemon was not running (or refused) \u2014 the new version applies on its next start."), phase === "running" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updBusy || "Updating\u2026 (about a minute)"), phase === "done" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--online)" } }, T.updDone || "Updated \u2014 restart to apply."), phase === "restarting" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)" } }, T.updRestarting || "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)"), phase === "error" && (() => {
|
|
4847
4876
|
const m = /`([^`]+)`/.exec(err || "");
|
|
4848
4877
|
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--danger)", wordBreak: "break-word" } }, m ? err.replace("`" + m[1] + "`", "") : err), m && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("code", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "8px 10px", userSelect: "all", wordBreak: "break-all" } }, m[1]), /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "copy", title: T.copy || "Copy", onClick: () => dkCopy(m[1]) })), m && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)" } }, T.updManualHint || "Run it in a terminal, then click Restart to apply."));
|
|
4849
4878
|
})(), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error" || phase === "lan-done" || phase === "lan-norestart") && btn(phase === "idle" ? T.updLater || "Later" : T.receivedClose || "Close", onClose), phase === "idle" && lanBehind && btn(T.updLanNow || "Update lan", doLanUpdate, !appBehind), phase === "idle" && appBehind && (info.mode === "global" || info.mode === "unknown") && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "error" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
|
package/dist/server.js
CHANGED
|
@@ -336,13 +336,84 @@ async function npmGlobalInstall(pkg) {
|
|
|
336
336
|
const tail = r.out.slice(-2000);
|
|
337
337
|
if (r.code === 0)
|
|
338
338
|
return { ok: true, log: tail };
|
|
339
|
+
// Do NOT tell people to re-run it with sudo. `sudo npm i -g` succeeds and
|
|
340
|
+
// leaves the package directory owned by root, so the NEXT unprivileged
|
|
341
|
+
// update fails exactly the same way — the advice was the thing keeping
|
|
342
|
+
// them in the loop, and it did, twice in one evening. POSIX needs write
|
|
343
|
+
// permission on a directory to rename it (the `..` entry is updated), so
|
|
344
|
+
// it is ownership of the package dir itself that has to change, once.
|
|
339
345
|
const hint = /EACCES|permission denied/i.test(r.out)
|
|
340
|
-
? ` —
|
|
346
|
+
? ` — ${await globalNpmOwnershipHint(pkg)}`
|
|
341
347
|
: "";
|
|
342
348
|
return { ok: false, error: `npm exited ${r.code}${hint}`, log: tail };
|
|
343
349
|
}
|
|
344
350
|
return { ok: false, error: `npm not found — update manually: npm i -g ${pkg}@latest` };
|
|
345
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Whether this process could write the global npm scope directory.
|
|
354
|
+
*
|
|
355
|
+
* Checked up front so the update card can say "you need to run this once"
|
|
356
|
+
* rather than offering a button that fails. The check is a real access() on
|
|
357
|
+
* the directory npm would have to rename, not a guess from the uid.
|
|
358
|
+
*/
|
|
359
|
+
async function globalNpmWritable() {
|
|
360
|
+
try {
|
|
361
|
+
const prefix = await npmGlobalPrefix();
|
|
362
|
+
if (!prefix)
|
|
363
|
+
return { ok: true }; // cannot tell — do not cry wolf
|
|
364
|
+
const scope = join(prefix, "lib", "node_modules", "@decentnetwork");
|
|
365
|
+
if (!existsSync(scope))
|
|
366
|
+
return { ok: true }; // nothing installed there yet
|
|
367
|
+
const { access } = await import("node:fs/promises");
|
|
368
|
+
const { constants } = await import("node:fs");
|
|
369
|
+
await access(scope, constants.W_OK);
|
|
370
|
+
return { ok: true };
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return { ok: false, reason: await globalNpmOwnershipHint(BEAGLE_PKG) };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The one-time fix for a global npm dir this user cannot write.
|
|
378
|
+
*
|
|
379
|
+
* Almost always the result of a first install done with `sudo npm i -g`: the
|
|
380
|
+
* scope directory ends up root-owned, and every later update — including this
|
|
381
|
+
* app's own — dies renaming it. Telling people to sudo again works once and
|
|
382
|
+
* rebuilds the trap. Hand them the command that ends it instead, with the real
|
|
383
|
+
* paths for THIS machine.
|
|
384
|
+
*/
|
|
385
|
+
async function globalNpmOwnershipHint(pkg) {
|
|
386
|
+
const prefix = await npmGlobalPrefix();
|
|
387
|
+
if (!prefix) {
|
|
388
|
+
return `no write access to the global npm dir. \`sudo npm i -g ${pkg}@latest\` would work but leaves it root-owned, so the next update fails the same way — fix the ownership once instead.`;
|
|
389
|
+
}
|
|
390
|
+
const scope = join(prefix, "lib", "node_modules", "@decentnetwork");
|
|
391
|
+
const bins = [join(prefix, "bin", "agentnet"), join(prefix, "bin", "beagle")].join(" ");
|
|
392
|
+
return (`no write access to ${scope}. This is what a first install with sudo leaves behind, and ` +
|
|
393
|
+
`\`sudo npm i -g\` would rebuild it — run this ONCE and updates never need sudo again: ` +
|
|
394
|
+
`sudo chown -R $(id -u):$(id -g) ${scope} ${bins}`);
|
|
395
|
+
}
|
|
396
|
+
/** `npm prefix -g`, or undefined if npm cannot be reached. */
|
|
397
|
+
async function npmGlobalPrefix() {
|
|
398
|
+
const { spawn } = await import("node:child_process");
|
|
399
|
+
return new Promise((res) => {
|
|
400
|
+
try {
|
|
401
|
+
const ch = spawn("npm", ["prefix", "-g"], { shell: process.platform === "win32" });
|
|
402
|
+
let out = "";
|
|
403
|
+
const timer = setTimeout(() => ch.kill("SIGKILL"), 10_000);
|
|
404
|
+
ch.stdout?.on("data", (c) => (out += c));
|
|
405
|
+
ch.on("error", () => { clearTimeout(timer); res(undefined); });
|
|
406
|
+
ch.on("close", (code) => {
|
|
407
|
+
clearTimeout(timer);
|
|
408
|
+
const v = out.trim().split("\n").pop()?.trim();
|
|
409
|
+
res(code === 0 && v ? v : undefined);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
res(undefined);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
346
417
|
/** Ask the running agentnet daemon to re-exec itself so it picks up the
|
|
347
418
|
* just-installed lan version. Tries the PATH binary, then the global
|
|
348
419
|
* install's CLI entry directly (pm2 PATH often lacks the nvm shims). */
|
|
@@ -1612,10 +1683,19 @@ export function startBeagleServer(opts) {
|
|
|
1612
1683
|
latest,
|
|
1613
1684
|
behind: !!(current && latest && verLt(current, latest)),
|
|
1614
1685
|
});
|
|
1686
|
+
// Can this process actually perform an update, or will it hit EACCES?
|
|
1687
|
+
// Worth knowing BEFORE the person clicks: the card used to offer an
|
|
1688
|
+
// update, fail on permissions, and hand back a sudo command that fixes
|
|
1689
|
+
// it once and rebuilds the problem for next time.
|
|
1690
|
+
const writable = await globalNpmWritable();
|
|
1615
1691
|
const body = {
|
|
1616
1692
|
ok: true,
|
|
1617
1693
|
checkedAt: Date.now(),
|
|
1618
1694
|
mode: installMode(),
|
|
1695
|
+
// false => the button cannot work; the UI shows the one-time fix
|
|
1696
|
+
// instead of an action that is going to fail.
|
|
1697
|
+
selfUpdatable: writable.ok,
|
|
1698
|
+
selfUpdateBlocked: writable.ok ? undefined : writable.reason,
|
|
1619
1699
|
// WHO owns the peer version. With the daemon backend it is the
|
|
1620
1700
|
// agentnet/lan process, not this app — so "update beagle" cannot
|
|
1621
1701
|
// move that row, and telling someone to rebuild beagle (or pull the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
4
4
|
"description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"yargs": "^17.7.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
37
|
+
"@decentnetwork/beagle-ui": "^0.2.5",
|
|
38
38
|
"@types/js-yaml": "^4.0.9",
|
|
39
39
|
"@types/node": "^20.11.0",
|
|
40
40
|
"@types/yargs": "^17.0.32",
|