@decentnetwork/beagle 0.1.13 → 0.1.15
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/cli.js +3 -1
- package/dist/desktop/app.js +97 -5
- package/dist/embedded-host.js +7 -3
- package/dist/node-config.d.ts +4 -0
- package/dist/node-config.js +24 -1
- package/dist/peer-host.d.ts +2 -0
- package/dist/peer-host.js +1 -0
- package/dist/server.js +9 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { startBeagleServer } from "./server.js";
|
|
12
12
|
import { openPeerHost, defaultConfigDir, decentlanCarrierDir, makeDaemonHost } from "./peer-host.js";
|
|
13
13
|
import { SwitchablePeerHost } from "./lan-handoff.js";
|
|
14
|
-
import { loadNodeConfig } from "./node-config.js";
|
|
14
|
+
import { loadNodeConfig, saveAutoAccept } from "./node-config.js";
|
|
15
15
|
function parseArgs(argv) {
|
|
16
16
|
const get = (flag) => {
|
|
17
17
|
const i = argv.indexOf(flag);
|
|
@@ -66,6 +66,7 @@ async function main() {
|
|
|
66
66
|
nickname,
|
|
67
67
|
statusMessage,
|
|
68
68
|
autoAcceptFriends: autoAccept,
|
|
69
|
+
onAutoAcceptChange: (enabled) => saveAutoAccept(args.configDir, enabled),
|
|
69
70
|
force: args.backend,
|
|
70
71
|
}));
|
|
71
72
|
}
|
|
@@ -84,6 +85,7 @@ async function main() {
|
|
|
84
85
|
nickname,
|
|
85
86
|
statusMessage,
|
|
86
87
|
autoAcceptFriends: autoAccept,
|
|
88
|
+
onAutoAcceptChange: (enabled) => saveAutoAccept(args.configDir, enabled),
|
|
87
89
|
force: "embedded",
|
|
88
90
|
})).host;
|
|
89
91
|
const lanHost = new SwitchablePeerHost(peerHost, {
|
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.15";
|
|
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"/>',
|
|
@@ -1553,13 +1553,85 @@ function dkSplitGroupSender(text) {
|
|
|
1553
1553
|
const m = /^([^:\n]{1,48}):\s([\s\S]+)$/.exec(text);
|
|
1554
1554
|
return m ? { sender: m[1], body: m[2] } : null;
|
|
1555
1555
|
}
|
|
1556
|
+
function dkParseCGP1(text) {
|
|
1557
|
+
const t = String(text || "").trim();
|
|
1558
|
+
if (!t.startsWith("CGP1 "))
|
|
1559
|
+
return null;
|
|
1560
|
+
let j = null;
|
|
1561
|
+
try {
|
|
1562
|
+
j = JSON.parse(t.slice(5));
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
if (!j || j.type !== "carrier_group_message" || j.chat_type !== "group")
|
|
1567
|
+
return null;
|
|
1568
|
+
const origin = j.origin || {};
|
|
1569
|
+
const senderId = origin.userid || origin.friendid || null;
|
|
1570
|
+
const sender = origin.nickname || origin.user_info && origin.user_info.name || (senderId ? senderId.slice(0, 10) : "member");
|
|
1571
|
+
const body = j.message && j.message.text || j.render && j.render.plain || "";
|
|
1572
|
+
return { sender, senderId, body: String(body), ts: null, strong: true };
|
|
1573
|
+
}
|
|
1556
1574
|
function dkGroupParse(text) {
|
|
1575
|
+
const cgp = dkParseCGP1(text);
|
|
1576
|
+
if (cgp)
|
|
1577
|
+
return cgp;
|
|
1557
1578
|
const t = String(text || "");
|
|
1558
1579
|
const { body: noTs, ts } = dkStripGroupTs(t);
|
|
1559
1580
|
const split = dkSplitGroupSender(noTs.trim());
|
|
1560
1581
|
if (!split)
|
|
1561
1582
|
return null;
|
|
1562
|
-
return { sender: split.sender, body: split.body.trim(), ts, strong: !!ts };
|
|
1583
|
+
return { sender: split.sender, senderId: null, body: split.body.trim(), ts, strong: !!ts };
|
|
1584
|
+
}
|
|
1585
|
+
function DkGroupSender({ grp, T }) {
|
|
1586
|
+
const [open, setOpen] = React.useState(false);
|
|
1587
|
+
const [busy, setBusy] = React.useState(false);
|
|
1588
|
+
const [done, setDone] = React.useState(null);
|
|
1589
|
+
const seed = grp.senderId || grp.sender;
|
|
1590
|
+
const addFriend = () => {
|
|
1591
|
+
setBusy(true);
|
|
1592
|
+
fetch("/api/add", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ address: grp.senderId }) }).then((r) => r.json()).then((r) => setDone(r.ok ? T && T.addSent || "request sent" : r.error || "failed")).catch((e) => setDone(String(e && e.message || e))).finally(() => setBusy(false));
|
|
1593
|
+
};
|
|
1594
|
+
return /* @__PURE__ */ React.createElement("span", { style: { position: "relative", display: "inline-flex", alignItems: "center", gap: 5, margin: "0 3px 2px" } }, /* @__PURE__ */ React.createElement(
|
|
1595
|
+
"button",
|
|
1596
|
+
{
|
|
1597
|
+
onClick: () => setOpen((v) => !v),
|
|
1598
|
+
style: { display: "inline-flex", alignItems: "center", gap: 5, background: "none", border: "none", padding: 0, cursor: "pointer" }
|
|
1599
|
+
},
|
|
1600
|
+
/* @__PURE__ */ React.createElement(DkIdenticon, { seed, size: 16, radius: 4 }),
|
|
1601
|
+
/* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, grp.sender)
|
|
1602
|
+
), open && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { onClick: () => setOpen(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("span", { style: {
|
|
1603
|
+
position: "absolute",
|
|
1604
|
+
left: 0,
|
|
1605
|
+
top: 20,
|
|
1606
|
+
zIndex: 50,
|
|
1607
|
+
width: 240,
|
|
1608
|
+
padding: 12,
|
|
1609
|
+
borderRadius: 10,
|
|
1610
|
+
background: "var(--panel-2)",
|
|
1611
|
+
border: "1px solid var(--line)",
|
|
1612
|
+
boxShadow: "0 14px 40px rgba(0,0,0,0.4)",
|
|
1613
|
+
display: "flex",
|
|
1614
|
+
flexDirection: "column",
|
|
1615
|
+
gap: 8
|
|
1616
|
+
} }, /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 700, color: "var(--text)", wordBreak: "break-all" } }, grp.sender)), grp.senderId ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Mono, { size: 10.5, dim: true, copy: grp.senderId, title: grp.senderId }, shortKey(grp.senderId, 12, 8)), /* @__PURE__ */ React.createElement(
|
|
1617
|
+
"button",
|
|
1618
|
+
{
|
|
1619
|
+
onClick: addFriend,
|
|
1620
|
+
disabled: busy || !!done,
|
|
1621
|
+
style: {
|
|
1622
|
+
padding: "6px 10px",
|
|
1623
|
+
borderRadius: 8,
|
|
1624
|
+
border: "none",
|
|
1625
|
+
background: "var(--accent)",
|
|
1626
|
+
color: "#fff",
|
|
1627
|
+
fontFamily: "var(--ui)",
|
|
1628
|
+
fontSize: 12,
|
|
1629
|
+
cursor: busy || done ? "default" : "pointer",
|
|
1630
|
+
opacity: busy ? 0.6 : 1
|
|
1631
|
+
}
|
|
1632
|
+
},
|
|
1633
|
+
done || (T && T.add || "Add friend")
|
|
1634
|
+
)) : /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", lineHeight: 1.45 } }, T && T.groupSenderUnknown || "The group relays only this display name. Register Beagle as a group agent (/agent add <beagle address> in the group) to receive full member identities you can add as friends."))));
|
|
1563
1635
|
}
|
|
1564
1636
|
function dkThreadIsGroup(thread) {
|
|
1565
1637
|
return (thread || []).some((m) => !m.day && m.dir !== "out" && !m.file && (dkGroupParse(m.text) || {}).strong);
|
|
@@ -2002,7 +2074,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
|
|
|
2002
2074
|
)
|
|
2003
2075
|
);
|
|
2004
2076
|
})()
|
|
2005
|
-
) : nativeMedia ? /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxWidth: "min(420px, 88%)" } }, nativeMedia.sender && /* @__PURE__ */ React.createElement(
|
|
2077
|
+
) : nativeMedia ? /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxWidth: "min(420px, 88%)" } }, nativeMedia.sender && /* @__PURE__ */ React.createElement(DkGroupSender, { grp: { sender: nativeMedia.sender, senderId: null }, T }), nativeMedia.kind === "image" ? /* @__PURE__ */ React.createElement(
|
|
2006
2078
|
"img",
|
|
2007
2079
|
{
|
|
2008
2080
|
src: nativeMedia.url,
|
|
@@ -2017,7 +2089,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
|
|
|
2017
2089
|
}
|
|
2018
2090
|
}
|
|
2019
2091
|
}
|
|
2020
|
-
) : nativeMedia.kind === "audio" ? /* @__PURE__ */ React.createElement("audio", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%" } }) : /* @__PURE__ */ React.createElement("video", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%", borderRadius: 12, border: "1px solid var(--line)" } })) : /* @__PURE__ */ React.createElement(React.Fragment, null, grp && /* @__PURE__ */ React.createElement(
|
|
2092
|
+
) : nativeMedia.kind === "audio" ? /* @__PURE__ */ React.createElement("audio", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%" } }) : /* @__PURE__ */ React.createElement("video", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%", borderRadius: 12, border: "1px solid var(--line)" } })) : /* @__PURE__ */ React.createElement(React.Fragment, null, grp && /* @__PURE__ */ React.createElement(DkGroupSender, { grp, T }), /* @__PURE__ */ React.createElement("div", { style: {
|
|
2021
2093
|
padding: "8px 12px",
|
|
2022
2094
|
borderRadius: 12,
|
|
2023
2095
|
borderBottomRightRadius: mine ? 4 : 12,
|
|
@@ -2723,7 +2795,19 @@ function DkEditModal({ T, me, onClose, onSave }) {
|
|
|
2723
2795
|
function ProfileTab({ T, me, onEdit }) {
|
|
2724
2796
|
const [qr, setQr] = React.useState(null);
|
|
2725
2797
|
const [editing, setEditing] = React.useState(false);
|
|
2726
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })),
|
|
2798
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), typeof me.autoAccept === "boolean" && /* @__PURE__ */ React.createElement(Card, { label: T && T.friendsSettings || "Friends" }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
|
|
2799
|
+
"input",
|
|
2800
|
+
{
|
|
2801
|
+
type: "checkbox",
|
|
2802
|
+
checked: me.autoAccept,
|
|
2803
|
+
onChange: (e) => {
|
|
2804
|
+
const enabled = e.target.checked;
|
|
2805
|
+
fetch("/api/autoaccept", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ enabled }) }).catch(() => {
|
|
2806
|
+
});
|
|
2807
|
+
},
|
|
2808
|
+
style: { width: 16, height: 16, accentColor: "var(--accent)" }
|
|
2809
|
+
}
|
|
2810
|
+
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}`, last: true })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
|
|
2727
2811
|
if (onEdit)
|
|
2728
2812
|
onEdit(name, description);
|
|
2729
2813
|
setEditing(false);
|
|
@@ -3582,6 +3666,10 @@ const STR = {
|
|
|
3582
3666
|
open: "open",
|
|
3583
3667
|
retry: "Retry",
|
|
3584
3668
|
cancel: "Cancel",
|
|
3669
|
+
friendsSettings: "Friends",
|
|
3670
|
+
autoAcceptLabel: "Auto-accept friend requests",
|
|
3671
|
+
autoAcceptSub: "Off: requests wait in the sidebar for your approval.",
|
|
3672
|
+
groupSenderUnknown: "The group relays only this display name. Register Beagle as a group agent (/agent add <beagle address> in the group) to receive full member identities you can add as friends.",
|
|
3585
3673
|
pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
|
|
3586
3674
|
cancelling: "Cancelling\u2026",
|
|
3587
3675
|
cancelled: "Transfer cancelled",
|
|
@@ -3679,6 +3767,10 @@ const STR = {
|
|
|
3679
3767
|
open: "\u6253\u5F00",
|
|
3680
3768
|
retry: "\u91CD\u8BD5",
|
|
3681
3769
|
cancel: "\u53D6\u6D88",
|
|
3770
|
+
friendsSettings: "\u597D\u53CB",
|
|
3771
|
+
autoAcceptLabel: "\u81EA\u52A8\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42",
|
|
3772
|
+
autoAcceptSub: "\u5173\u95ED\u540E\uFF0C\u597D\u53CB\u8BF7\u6C42\u4F1A\u7B49\u5F85\u4F60\u5728\u4FA7\u8FB9\u680F\u624B\u52A8\u786E\u8BA4\u3002",
|
|
3773
|
+
groupSenderUnknown: "\u7FA4\u670D\u52A1\u53EA\u8F6C\u53D1\u4E86\u663E\u793A\u540D\uFF0C\u62FF\u4E0D\u5230\u8BE5\u6210\u5458\u7684\u5B8C\u6574\u8EAB\u4EFD\u3002\u5728\u7FA4\u91CC\u6267\u884C /agent add <beagle \u5730\u5740> \u628A Beagle \u6CE8\u518C\u4E3A\u7FA4 agent \u540E\uFF0C\u5373\u53EF\u6536\u5230\u5B8C\u6574\u8EAB\u4EFD\u5E76\u4E00\u952E\u52A0\u597D\u53CB\u3002",
|
|
3682
3774
|
pendingFriend: "\u7B49\u5F85\u5BF9\u65B9\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42 \u2014 \u6D88\u606F\u4F1A\u5148\u6392\u961F\uFF0C\u5BF9\u65B9\u63A5\u53D7\u540E\u81EA\u52A8\u9001\u8FBE\u3002",
|
|
3683
3775
|
cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
|
|
3684
3776
|
cancelled: "\u5DF2\u53D6\u6D88\u4F20\u8F93",
|
package/dist/embedded-host.js
CHANGED
|
@@ -318,7 +318,7 @@ export class EmbeddedHost {
|
|
|
318
318
|
const id = this.#node.identity();
|
|
319
319
|
return {
|
|
320
320
|
identity: id,
|
|
321
|
-
node: { name: this.#opts.nickname ?? "", backend: "embedded" },
|
|
321
|
+
node: { name: this.#opts.nickname ?? "", backend: "embedded", autoAccept: this.#autoAccept },
|
|
322
322
|
// No TUN in this backend — say so explicitly rather than omitting
|
|
323
323
|
// the key, so the UI can distinguish "no LAN" from "unknown".
|
|
324
324
|
tun: null,
|
|
@@ -380,8 +380,12 @@ export class EmbeddedHost {
|
|
|
380
380
|
this.#pending.delete(uid);
|
|
381
381
|
return {};
|
|
382
382
|
case "friends-autoaccept": {
|
|
383
|
-
this
|
|
384
|
-
|
|
383
|
+
// Without an explicit boolean this is a READ — a bare call must never
|
|
384
|
+
// flip the switch off (Boolean(undefined) === false).
|
|
385
|
+
if (typeof req.enabled === "boolean") {
|
|
386
|
+
this.#autoAccept = req.enabled;
|
|
387
|
+
this.#opts.onAutoAcceptChange?.(this.#autoAccept);
|
|
388
|
+
}
|
|
385
389
|
return { autoAccept: this.#autoAccept };
|
|
386
390
|
}
|
|
387
391
|
case "friend-remove":
|
package/dist/node-config.d.ts
CHANGED
|
@@ -14,3 +14,7 @@ export interface NodeConfig {
|
|
|
14
14
|
autoAccept: boolean;
|
|
15
15
|
}
|
|
16
16
|
export declare function loadNodeConfig(configDir: string): NodeConfig;
|
|
17
|
+
/** Persist the auto-accept switch into config.yaml, preserving everything
|
|
18
|
+
* else in the file. Without this the runtime toggle lasted until the next
|
|
19
|
+
* restart and then silently reverted — worse than no switch. */
|
|
20
|
+
export declare function saveAutoAccept(configDir: string, enabled: boolean): void;
|
package/dist/node-config.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// The bootstrap list itself is data, not code, so it lives in config/ and can
|
|
9
9
|
// be refreshed without a release (decentlan's `agentnet bootstrap update`
|
|
10
10
|
// pulls the published feed the same way).
|
|
11
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { dirname, resolve } from "node:path";
|
|
14
14
|
import yaml from "js-yaml";
|
|
@@ -51,3 +51,26 @@ export function loadNodeConfig(configDir) {
|
|
|
51
51
|
}
|
|
52
52
|
return { bootstrapNodes: shipped, autoAccept: true };
|
|
53
53
|
}
|
|
54
|
+
/** Persist the auto-accept switch into config.yaml, preserving everything
|
|
55
|
+
* else in the file. Without this the runtime toggle lasted until the next
|
|
56
|
+
* restart and then silently reverted — worse than no switch. */
|
|
57
|
+
export function saveAutoAccept(configDir, enabled) {
|
|
58
|
+
const cfgPath = resolve(configDir, "config.yaml");
|
|
59
|
+
let cfg = {};
|
|
60
|
+
try {
|
|
61
|
+
if (existsSync(cfgPath))
|
|
62
|
+
cfg = (yaml.load(readFileSync(cfgPath, "utf-8")) ?? {});
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Malformed file: rather than destroy it with a rewrite, keep the toggle
|
|
66
|
+
// session-only.
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
cfg.friends = { ...(cfg.friends ?? {}), autoAccept: enabled };
|
|
70
|
+
try {
|
|
71
|
+
writeFileSync(cfgPath, yaml.dump(cfg), "utf-8");
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* read-only config dir: session-only toggle */
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/peer-host.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface OpenPeerHostOptions {
|
|
|
37
37
|
nickname?: string;
|
|
38
38
|
statusMessage?: string;
|
|
39
39
|
autoAcceptFriends?: boolean;
|
|
40
|
+
/** Persist a runtime auto-accept toggle (embedded backend only). */
|
|
41
|
+
onAutoAcceptChange?: (enabled: boolean) => void;
|
|
40
42
|
/** Force a backend instead of auto-detecting. Mainly for testing the
|
|
41
43
|
* embedded path on a machine that also runs a daemon. */
|
|
42
44
|
force?: BackendKind;
|
package/dist/peer-host.js
CHANGED
package/dist/server.js
CHANGED
|
@@ -674,6 +674,9 @@ export function startBeagleServer(opts) {
|
|
|
674
674
|
wire: opts.meExtra?.wire ?? "163",
|
|
675
675
|
isExit: !!meExit,
|
|
676
676
|
exitRegion: meExit?.region ?? null,
|
|
677
|
+
// null = backend didn't report it (daemon); the UI then hides the
|
|
678
|
+
// toggle rather than showing a state it cannot know.
|
|
679
|
+
autoAccept: typeof node.autoAccept === "boolean" ? node.autoAccept : null,
|
|
677
680
|
};
|
|
678
681
|
const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
|
|
679
682
|
const peers = fl.map((f) => {
|
|
@@ -967,6 +970,12 @@ export function startBeagleServer(opts) {
|
|
|
967
970
|
}
|
|
968
971
|
return;
|
|
969
972
|
}
|
|
973
|
+
if (req.method === "POST" && url === "/api/autoaccept") {
|
|
974
|
+
const { enabled } = await readBody(req);
|
|
975
|
+
const r = await opts.call({ op: "friends-autoaccept", enabled: !!enabled });
|
|
976
|
+
sendJson(res, r.ok ? 200 : 400, r);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
970
979
|
if (req.method === "POST" && url === "/api/accept") {
|
|
971
980
|
const { userid } = await readBody(req);
|
|
972
981
|
const r = await opts.call({ op: "friends-accept", userid });
|
package/package.json
CHANGED