@decentnetwork/beagle 0.1.23 → 0.1.25
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 +184 -8
- package/dist/server.js +219 -3
- 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.24";
|
|
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"/>',
|
|
@@ -627,9 +627,25 @@ function DkBot({ seed, size = 30, radius = 7 }) {
|
|
|
627
627
|
justifyContent: "center"
|
|
628
628
|
} }, /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: size * 0.56, color: "#fff", stroke: 1.9 }));
|
|
629
629
|
}
|
|
630
|
+
function DkImgAvatar({ url, seed, size, radius }) {
|
|
631
|
+
const [broken, setBroken] = React.useState(false);
|
|
632
|
+
if (broken)
|
|
633
|
+
return /* @__PURE__ */ React.createElement(DkIdenticon, { seed, size, radius });
|
|
634
|
+
return /* @__PURE__ */ React.createElement(
|
|
635
|
+
"img",
|
|
636
|
+
{
|
|
637
|
+
src: url,
|
|
638
|
+
alt: "",
|
|
639
|
+
width: size,
|
|
640
|
+
height: size,
|
|
641
|
+
onError: () => setBroken(true),
|
|
642
|
+
style: { width: size, height: size, borderRadius: radius, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" }
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
}
|
|
630
646
|
function DkAvatar({ peer, size = 30, radius = 7, dot = true }) {
|
|
631
647
|
const d = Math.max(8, size * 0.3);
|
|
632
|
-
return /* @__PURE__ */ React.createElement("div", { style: { position: "relative", width: size, height: size, flexShrink: 0 } }, peer.agent ? /* @__PURE__ */ React.createElement(DkBot, { seed: peer.id, size, radius }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId || peer.id, size, radius }), dot && /* @__PURE__ */ React.createElement("span", { style: {
|
|
648
|
+
return /* @__PURE__ */ React.createElement("div", { style: { position: "relative", width: size, height: size, flexShrink: 0 } }, peer.agent ? /* @__PURE__ */ React.createElement(DkBot, { seed: peer.id, size, radius }) : peer.avatarUrl ? /* @__PURE__ */ React.createElement(DkImgAvatar, { url: peer.avatarUrl, seed: peer.userId || peer.id, size, radius }) : peer.punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: peer.punkId, size, radius, fallbackSeed: peer.userId || peer.id }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId || peer.id, size, radius }), dot && /* @__PURE__ */ React.createElement("span", { style: {
|
|
633
649
|
position: "absolute",
|
|
634
650
|
right: -2,
|
|
635
651
|
bottom: -2,
|
|
@@ -764,7 +780,7 @@ function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, styl
|
|
|
764
780
|
function Section({ label, count, trailing, style }) {
|
|
765
781
|
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px", ...style } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), count != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "var(--faint)" } }, count), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, height: 1, background: "var(--line)" } }), trailing);
|
|
766
782
|
}
|
|
767
|
-
Object.assign(window, { DkIdenticon, DkBot, DkAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
|
|
783
|
+
Object.assign(window, { DkIdenticon, DkBot, DkAvatar, DkImgAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
|
|
768
784
|
const __TWEAKS_STYLE = `
|
|
769
785
|
.twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
|
|
770
786
|
max-height:calc(100vh - 32px);display:flex;flex-direction:column;
|
|
@@ -1586,6 +1602,7 @@ function DkGroupSender({ grp, T, avatar }) {
|
|
|
1586
1602
|
const [open, setOpen] = React.useState(false);
|
|
1587
1603
|
const [busy, setBusy] = React.useState(false);
|
|
1588
1604
|
const [done, setDone] = React.useState(null);
|
|
1605
|
+
const [showProf, setShowProf] = React.useState(false);
|
|
1589
1606
|
const seed = grp.senderId || grp.sender;
|
|
1590
1607
|
const addFriend = () => {
|
|
1591
1608
|
setBusy(true);
|
|
@@ -1638,7 +1655,26 @@ function DkGroupSender({ grp, T, avatar }) {
|
|
|
1638
1655
|
}
|
|
1639
1656
|
},
|
|
1640
1657
|
done || (T && T.add || "Add friend")
|
|
1641
|
-
)
|
|
1658
|
+
), /* @__PURE__ */ React.createElement(
|
|
1659
|
+
"button",
|
|
1660
|
+
{
|
|
1661
|
+
onClick: () => {
|
|
1662
|
+
setOpen(false);
|
|
1663
|
+
setShowProf(true);
|
|
1664
|
+
},
|
|
1665
|
+
style: {
|
|
1666
|
+
padding: "6px 10px",
|
|
1667
|
+
borderRadius: 8,
|
|
1668
|
+
border: "1px solid var(--line)",
|
|
1669
|
+
background: "transparent",
|
|
1670
|
+
color: "var(--text)",
|
|
1671
|
+
fontFamily: "var(--ui)",
|
|
1672
|
+
fontSize: 12,
|
|
1673
|
+
cursor: "pointer"
|
|
1674
|
+
}
|
|
1675
|
+
},
|
|
1676
|
+
T && T.pubOpen || "View public profile"
|
|
1677
|
+
)) : /* @__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."))), showProf && grp.senderId && /* @__PURE__ */ React.createElement(DkPublicProfile, { T, userid: grp.senderId, fallbackName: grp.sender, onClose: () => setShowProf(false) }));
|
|
1642
1678
|
}
|
|
1643
1679
|
function dkThreadIsGroup(thread) {
|
|
1644
1680
|
return (thread || []).some((m) => !m.day && m.dir !== "out" && !m.file && (dkGroupParse(m.text) || {}).strong);
|
|
@@ -2149,6 +2185,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
2149
2185
|
const fileRef = React.useRef(null);
|
|
2150
2186
|
const rtcFileRef = React.useRef(null);
|
|
2151
2187
|
const [menu, setMenu] = React.useState(false);
|
|
2188
|
+
const [pubProfile, setPubProfile] = React.useState(false);
|
|
2152
2189
|
const [selMode, setSelMode] = React.useState(false);
|
|
2153
2190
|
const [sel, setSel] = React.useState(() => /* @__PURE__ */ new Set());
|
|
2154
2191
|
const [hidden, setHidden] = React.useState(() => /* @__PURE__ */ new Set());
|
|
@@ -2397,7 +2434,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
2397
2434
|
onDrop
|
|
2398
2435
|
},
|
|
2399
2436
|
dragOver && /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", inset: 0, zIndex: 30, background: "rgba(91,140,255,0.10)", border: "2px dashed var(--accent)", borderRadius: 12, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none", fontFamily: "var(--mono)", fontSize: 14, color: "var(--accent)" } }, lang === "zh" ? "\u677E\u5F00\u53D1\u9001\u6587\u4EF6" : "Drop to send file"),
|
|
2400
|
-
/* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
|
|
2437
|
+
/* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { onClick: () => setPubProfile(true), title: T && T.pubOpen || "View public profile", style: { cursor: "pointer", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { onClick: () => setPubProfile(true), style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)", cursor: "pointer" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
|
|
2401
2438
|
Mono,
|
|
2402
2439
|
{
|
|
2403
2440
|
size: 11.5,
|
|
@@ -2555,7 +2592,8 @@ ${peer.address}`
|
|
|
2555
2592
|
onClick: (e) => e.stopPropagation(),
|
|
2556
2593
|
style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6 }
|
|
2557
2594
|
}
|
|
2558
|
-
)))
|
|
2595
|
+
))),
|
|
2596
|
+
pubProfile && /* @__PURE__ */ React.createElement(DkPublicProfile, { T, userid: peer.userId, fallbackName: peer.alias, onClose: () => setPubProfile(false) })
|
|
2559
2597
|
);
|
|
2560
2598
|
}
|
|
2561
2599
|
function MenuItem({ icon, label, onClick, danger }) {
|
|
@@ -2890,6 +2928,40 @@ function DkPunkPicker({ T, onPick, onClose }) {
|
|
|
2890
2928
|
/* @__PURE__ */ React.createElement("img", { src: p.image, alt: `punk #${p.id}`, style: { width: "100%", height: "100%", imageRendering: "pixelated", display: "block" } })
|
|
2891
2929
|
))))));
|
|
2892
2930
|
}
|
|
2931
|
+
function DkPublicProfile({ T, userid, fallbackName, onClose }) {
|
|
2932
|
+
const [st, setSt] = React.useState({ loading: true });
|
|
2933
|
+
React.useEffect(() => {
|
|
2934
|
+
let dead = false;
|
|
2935
|
+
setSt({ loading: true });
|
|
2936
|
+
fetch(`/api/ens-profile?userid=${encodeURIComponent(userid)}`).then((r) => r.json()).then((d) => {
|
|
2937
|
+
if (!dead)
|
|
2938
|
+
setSt({ loading: false, prof: d.ok && d.profile || null });
|
|
2939
|
+
}).catch(() => {
|
|
2940
|
+
if (!dead)
|
|
2941
|
+
setSt({ loading: false, prof: null });
|
|
2942
|
+
});
|
|
2943
|
+
return () => {
|
|
2944
|
+
dead = true;
|
|
2945
|
+
};
|
|
2946
|
+
}, [userid]);
|
|
2947
|
+
const prof = st.prof;
|
|
2948
|
+
const LINK_BASE = { twitter: "https://x.com/", linkedin: "https://www.linkedin.com/in/", github: "https://github.com/" };
|
|
2949
|
+
const links = prof ? Object.entries(prof.links || {}) : [];
|
|
2950
|
+
const lbl = { width: 110, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 };
|
|
2951
|
+
const rowS = { display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderTop: "1px solid var(--line)" };
|
|
2952
|
+
return /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 90, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 420, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: "20px 22px", display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14 } }, prof && prof.avatarUrl ? /* @__PURE__ */ React.createElement(DkImgAvatar, { url: prof.avatarUrl, seed: userid, size: 56, radius: 14 }) : prof && prof.punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: prof.punkId, size: 56, radius: 14, fallbackSeed: userid }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: userid, size: 56, radius: 14 }), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 17, fontWeight: 700, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, prof && prof.displayName || fallbackName || shortKey(userid, 10, 6)), prof && prof.ens && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--accent)", marginTop: 2 } }, prof.ens)), /* @__PURE__ */ React.createElement(Btn, { icon: "x", size: "sm", onClick: onClose })), st.loading ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : !prof ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.pubNone || "No public profile \u2014 this user has not registered a beagles.eth name.") : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column" } }, prof.description && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--dim)", paddingBottom: 12, lineHeight: 1.5 } }, prof.description), links.length > 0 && /* @__PURE__ */ React.createElement("div", { style: { ...rowS, gap: 8, flexWrap: "wrap" } }, /* @__PURE__ */ React.createElement("span", { style: lbl }, T.linksCard || "links"), links.map(([k, h]) => /* @__PURE__ */ React.createElement(
|
|
2953
|
+
"a",
|
|
2954
|
+
{
|
|
2955
|
+
key: k,
|
|
2956
|
+
href: (LINK_BASE[k] || "") + h,
|
|
2957
|
+
target: "_blank",
|
|
2958
|
+
rel: "noreferrer",
|
|
2959
|
+
style: { display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 10px", borderRadius: 999, border: "1px solid var(--line)", textDecoration: "none", fontFamily: "var(--ui)", fontSize: 12, color: "var(--accent)" }
|
|
2960
|
+
},
|
|
2961
|
+
k,
|
|
2962
|
+
" \u2197"
|
|
2963
|
+
))), prof.eth && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "ethereum"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.eth, title: prof.eth }, shortKey(prof.eth, 8, 6))), prof.sol && /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "solana"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: prof.sol, title: prof.sol }, shortKey(prof.sol, 8, 6))), /* @__PURE__ */ React.createElement("div", { style: rowS }, /* @__PURE__ */ React.createElement("span", { style: lbl }, T.userId || "user id"), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: userid, title: userid }, shortKey(userid, 10, 6))))));
|
|
2964
|
+
}
|
|
2893
2965
|
function DkEnsCard({ T, me, onRecord }) {
|
|
2894
2966
|
const [st, setSt] = React.useState({ loading: true });
|
|
2895
2967
|
const [label, setLabel] = React.useState("");
|
|
@@ -3098,7 +3170,7 @@ function ProfileTab({ T, me, onEdit }) {
|
|
|
3098
3170
|
setEditing(false);
|
|
3099
3171
|
} }));
|
|
3100
3172
|
}
|
|
3101
|
-
Object.assign(window, { ProfileTab, DkPunkAvatar });
|
|
3173
|
+
Object.assign(window, { ProfileTab, DkPunkAvatar, DkPublicProfile });
|
|
3102
3174
|
function DkDirAvatar({ p, size }) {
|
|
3103
3175
|
const [broken, setBroken] = React.useState(false);
|
|
3104
3176
|
if (p.punkId != null && !(p.avatar && !broken)) {
|
|
@@ -4142,6 +4214,18 @@ const STR = {
|
|
|
4142
4214
|
linksCard: "Links",
|
|
4143
4215
|
linkPlaceholder: "username or profile URL",
|
|
4144
4216
|
save: "save",
|
|
4217
|
+
updTitle: "Update available",
|
|
4218
|
+
updLater: "Later",
|
|
4219
|
+
updNow: "Update now",
|
|
4220
|
+
updRestart: "Restart",
|
|
4221
|
+
updBusy: "Updating\u2026 (about a minute)",
|
|
4222
|
+
updDone: "Updated \u2014 restart to apply.",
|
|
4223
|
+
updRestarting: "Restarting\u2026 this page reloads when it\u2019s back. (If beagle was started by hand in a terminal, run it again.)",
|
|
4224
|
+
updNpx: "Running via npx \u2014 restarting picks up the latest version automatically.",
|
|
4225
|
+
updDev: "Running from a source checkout \u2014 update with:",
|
|
4226
|
+
updLanNote: "agentnet (lan) updates separately; the daemon restart is manual:",
|
|
4227
|
+
pubOpen: "View public profile",
|
|
4228
|
+
pubNone: "No public profile \u2014 this user has not registered a beagles.eth name.",
|
|
4145
4229
|
ensNoEthWallet: "no Ethereum wallet extension found (MetaMask\u2026)",
|
|
4146
4230
|
ensNoSolWallet: "no Solana wallet extension found (Phantom\u2026)",
|
|
4147
4231
|
ensWalletOwned: "registered via your mobile wallet as",
|
|
@@ -4286,6 +4370,18 @@ const STR = {
|
|
|
4286
4370
|
linksCard: "\u793E\u4EA4\u8D26\u53F7",
|
|
4287
4371
|
linkPlaceholder: "\u7528\u6237\u540D\u6216\u4E3B\u9875\u94FE\u63A5",
|
|
4288
4372
|
save: "\u4FDD\u5B58",
|
|
4373
|
+
updTitle: "\u53D1\u73B0\u65B0\u7248\u672C",
|
|
4374
|
+
updLater: "\u7A0D\u540E",
|
|
4375
|
+
updNow: "\u7ACB\u5373\u66F4\u65B0",
|
|
4376
|
+
updRestart: "\u91CD\u542F",
|
|
4377
|
+
updBusy: "\u66F4\u65B0\u4E2D\u2026(\u7EA6\u4E00\u5206\u949F)",
|
|
4378
|
+
updDone: "\u66F4\u65B0\u5B8C\u6210\u2014\u2014\u91CD\u542F\u540E\u751F\u6548\u3002",
|
|
4379
|
+
updRestarting: "\u6B63\u5728\u91CD\u542F\u2026\u5B8C\u6210\u540E\u672C\u9875\u4F1A\u81EA\u52A8\u5237\u65B0\u3002(\u5982\u679C beagle \u662F\u5728\u7EC8\u7AEF\u91CC\u624B\u52A8\u542F\u52A8\u7684,\u8BF7\u91CD\u65B0\u8FD0\u884C\u4E00\u6B21\u3002)",
|
|
4380
|
+
updNpx: "\u4F60\u662F\u7528 npx \u8FD0\u884C\u7684\u2014\u2014\u91CD\u542F\u5373\u81EA\u52A8\u83B7\u53D6\u6700\u65B0\u7248\u3002",
|
|
4381
|
+
updDev: "\u5F53\u524D\u4ECE\u6E90\u7801\u76EE\u5F55\u8FD0\u884C,\u8BF7\u624B\u52A8\u66F4\u65B0:",
|
|
4382
|
+
updLanNote: "agentnet(lan)\u9700\u5355\u72EC\u5347\u7EA7,daemon \u91CD\u542F\u9700\u624B\u52A8\u6267\u884C:",
|
|
4383
|
+
pubOpen: "\u67E5\u770B\u516C\u5F00\u8D44\u6599",
|
|
4384
|
+
pubNone: "\u6682\u65E0\u516C\u5F00\u8D44\u6599\u2014\u2014\u5BF9\u65B9\u8FD8\u6CA1\u6709\u6CE8\u518C beagles.eth \u540D\u5B57\u3002",
|
|
4289
4385
|
ensNoEthWallet: "\u672A\u68C0\u6D4B\u5230\u4EE5\u592A\u574A\u94B1\u5305\u63D2\u4EF6(MetaMask \u7B49)",
|
|
4290
4386
|
ensNoSolWallet: "\u672A\u68C0\u6D4B\u5230 Solana \u94B1\u5305\u63D2\u4EF6(Phantom \u7B49)",
|
|
4291
4387
|
ensWalletOwned: "\u5DF2\u901A\u8FC7\u624B\u673A\u94B1\u5305\u6CE8\u518C\u4E3A",
|
|
@@ -4333,12 +4429,82 @@ function RailBtn({ icon, label, active, soon, onClick }) {
|
|
|
4333
4429
|
gap: 3
|
|
4334
4430
|
} }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 20, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, fontWeight: 600, letterSpacing: 0.2, color: active ? "var(--accent)" : "var(--faint)" } }, label), soon && /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 5, right: 6, width: 6, height: 6, borderRadius: 999, background: "var(--warn)" } }));
|
|
4335
4431
|
}
|
|
4432
|
+
function DkUpdateModal({ T, info, onClose }) {
|
|
4433
|
+
const [phase, setPhase] = React.useState("idle");
|
|
4434
|
+
const [err, setErr] = React.useState(null);
|
|
4435
|
+
const behind = (info.updates || []).filter((u) => u.behind);
|
|
4436
|
+
const appBehind = behind.some((u) => u.pkg !== "@decentnetwork/lan");
|
|
4437
|
+
const lanBehind = behind.find((u) => u.pkg === "@decentnetwork/lan");
|
|
4438
|
+
const short = (pkg) => pkg.replace("@decentnetwork/", "");
|
|
4439
|
+
const doUpdate = () => {
|
|
4440
|
+
setPhase("running");
|
|
4441
|
+
setErr(null);
|
|
4442
|
+
fetch("/api/update-run", { method: "POST" }).then((r) => r.json()).then((d) => {
|
|
4443
|
+
if (d.ok)
|
|
4444
|
+
setPhase("done");
|
|
4445
|
+
else {
|
|
4446
|
+
setPhase("error");
|
|
4447
|
+
setErr(d.error || "update failed");
|
|
4448
|
+
}
|
|
4449
|
+
}).catch((e) => {
|
|
4450
|
+
setPhase("error");
|
|
4451
|
+
setErr(String(e && e.message || e));
|
|
4452
|
+
});
|
|
4453
|
+
};
|
|
4454
|
+
const doRestart = () => {
|
|
4455
|
+
setPhase("restarting");
|
|
4456
|
+
fetch("/api/update-restart", { method: "POST" }).catch(() => {
|
|
4457
|
+
});
|
|
4458
|
+
const poll = setInterval(() => {
|
|
4459
|
+
fetch("/api/update-check").then((r) => {
|
|
4460
|
+
if (r.ok) {
|
|
4461
|
+
clearInterval(poll);
|
|
4462
|
+
window.location.reload();
|
|
4463
|
+
}
|
|
4464
|
+
}).catch(() => {
|
|
4465
|
+
});
|
|
4466
|
+
}, 1500);
|
|
4467
|
+
};
|
|
4468
|
+
const btn = (label, onClick, tone) => /* @__PURE__ */ React.createElement("button", { onClick, style: {
|
|
4469
|
+
padding: "7px 14px",
|
|
4470
|
+
borderRadius: 9,
|
|
4471
|
+
cursor: "pointer",
|
|
4472
|
+
fontFamily: "var(--ui)",
|
|
4473
|
+
fontSize: 12.5,
|
|
4474
|
+
border: tone ? "none" : "1px solid var(--line)",
|
|
4475
|
+
background: tone ? "var(--accent)" : "transparent",
|
|
4476
|
+
color: tone ? "#fff" : "var(--text)"
|
|
4477
|
+
} }, label);
|
|
4478
|
+
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" && 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 === "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."), lanBehind && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)", lineHeight: 1.5 } }, T.updLanNote || "agentnet (lan) updates separately; the daemon restart is manual:", /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("code", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--text)", userSelect: "all" } }, "npm i -g @decentnetwork/lan@latest && sudo agentnet down && sudo agentnet up --real-tun")), 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" && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--danger)", wordBreak: "break-word" } }, err), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 } }, (phase === "idle" || phase === "error") && btn(T.updLater || "Later", onClose), phase === "idle" && appBehind && info.mode === "global" && btn(T.updNow || "Update now", doUpdate, true), phase === "idle" && appBehind && info.mode === "npx" && btn(T.updRestart || "Restart", doRestart, true), phase === "done" && btn(T.updRestart || "Restart", doRestart, true))));
|
|
4479
|
+
}
|
|
4336
4480
|
function DkApp() {
|
|
4337
4481
|
const [t, setTweak] = useTweaks(DK_DEFAULTS);
|
|
4338
4482
|
const [tab, setTab] = React.useState(t.startTab || "chat");
|
|
4339
4483
|
const [activeId, setActiveId] = React.useState(null);
|
|
4340
4484
|
const data = useDaemonData();
|
|
4341
4485
|
const me = data.me;
|
|
4486
|
+
const [upd, setUpd] = React.useState(null);
|
|
4487
|
+
React.useEffect(() => {
|
|
4488
|
+
let dead = false;
|
|
4489
|
+
const check = () => fetch("/api/update-check").then((r) => r.json()).then((d) => {
|
|
4490
|
+
if (dead || !d.ok)
|
|
4491
|
+
return;
|
|
4492
|
+
const behind = (d.updates || []).filter((u) => u.behind);
|
|
4493
|
+
if (!behind.length)
|
|
4494
|
+
return;
|
|
4495
|
+
const key = behind.map((u) => u.pkg + "@" + u.latest).join(",");
|
|
4496
|
+
if (localStorage.getItem("dk-upd-dismissed") === key)
|
|
4497
|
+
return;
|
|
4498
|
+
setUpd({ ...d, key });
|
|
4499
|
+
}).catch(() => {
|
|
4500
|
+
});
|
|
4501
|
+
check();
|
|
4502
|
+
const timer = setInterval(check, 6 * 3600 * 1e3);
|
|
4503
|
+
return () => {
|
|
4504
|
+
dead = true;
|
|
4505
|
+
clearInterval(timer);
|
|
4506
|
+
};
|
|
4507
|
+
}, []);
|
|
4342
4508
|
const [backend, setBackend] = React.useState(null);
|
|
4343
4509
|
const refreshBackend = React.useCallback(() => {
|
|
4344
4510
|
dkGet("/api/backend").then((b) => {
|
|
@@ -4555,6 +4721,16 @@ function DkApp() {
|
|
|
4555
4721
|
color: "#fff",
|
|
4556
4722
|
fontSize: 12,
|
|
4557
4723
|
fontWeight: 600
|
|
4558
|
-
} }, n.label)))), /* @__PURE__ */ React.createElement(IncomingCallModal, { T, ctl: callCtl, peers }), /* @__PURE__ */ React.createElement(CallOverlay, { T, ctl: callCtl, peers }), /* @__PURE__ */ React.createElement(RtcFileInbox, { T, peers, ctl: rtcFileCtl })
|
|
4724
|
+
} }, n.label)))), /* @__PURE__ */ React.createElement(IncomingCallModal, { T, ctl: callCtl, peers }), /* @__PURE__ */ React.createElement(CallOverlay, { T, ctl: callCtl, peers }), /* @__PURE__ */ React.createElement(RtcFileInbox, { T, peers, ctl: rtcFileCtl }), upd && /* @__PURE__ */ React.createElement(
|
|
4725
|
+
DkUpdateModal,
|
|
4726
|
+
{
|
|
4727
|
+
T,
|
|
4728
|
+
info: upd,
|
|
4729
|
+
onClose: () => {
|
|
4730
|
+
localStorage.setItem("dk-upd-dismissed", upd.key);
|
|
4731
|
+
setUpd(null);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
));
|
|
4559
4735
|
}
|
|
4560
4736
|
ReactDOM.createRoot(document.getElementById("root")).render(/* @__PURE__ */ React.createElement(DkApp, null));
|
package/dist/server.js
CHANGED
|
@@ -154,6 +154,126 @@ async function ensAvatarDelete(call, fullName) {
|
|
|
154
154
|
// best-effort only
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
function ensPubFromEntry(ens, v) {
|
|
158
|
+
const t = v?.texts ?? {};
|
|
159
|
+
return {
|
|
160
|
+
ens,
|
|
161
|
+
avatarUrl: t.avatar || null,
|
|
162
|
+
punkId: v?.nft === "CryptoPunks" && typeof v?.nftid === "number" && v.nftid > 0 ? v.nftid : null,
|
|
163
|
+
displayName: t.displayName || null,
|
|
164
|
+
description: t.description || null,
|
|
165
|
+
links: {
|
|
166
|
+
...(t["com.twitter"] ? { twitter: t["com.twitter"] } : {}),
|
|
167
|
+
...(t["com.linkedin"] ? { linkedin: t["com.linkedin"] } : {}),
|
|
168
|
+
...(t["com.github"] ? { github: t["com.github"] } : {}),
|
|
169
|
+
},
|
|
170
|
+
eth: v?.addresses?.["60"] || null,
|
|
171
|
+
sol: v?.addresses?.["501"] || null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** userid → EnsPub from the cached /names listing. Bounded wait so the UI
|
|
175
|
+
* poll never blocks on a cold cache — the fetch continues in the background
|
|
176
|
+
* and the next poll finds it cached. */
|
|
177
|
+
async function ensPublicByUserid(maxWaitMs = 300) {
|
|
178
|
+
const p = fetchDiscoverJson(`${ENS_GATEWAY}/names`).catch(() => null);
|
|
179
|
+
const raw = (await Promise.race([p, new Promise((r) => setTimeout(() => r(null), maxWaitMs))]));
|
|
180
|
+
const map = new Map();
|
|
181
|
+
if (raw && typeof raw === "object") {
|
|
182
|
+
for (const [ens, v] of Object.entries(raw)) {
|
|
183
|
+
const uid = v?.texts?.carrierUserId;
|
|
184
|
+
if (uid)
|
|
185
|
+
map.set(uid, ensPubFromEntry(ens, v));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return map;
|
|
189
|
+
}
|
|
190
|
+
// ── self-update ─────────────────────────────────────────────────────────────
|
|
191
|
+
// npm registry is consulted at most once per TTL (and only while a UI is
|
|
192
|
+
// open); the UI pops a confirm dialog when something is behind. "Update"
|
|
193
|
+
// reinstalls the global package in place and restarts this process —
|
|
194
|
+
// pm2/launchd/systemd bring it back; a bare terminal run exits with a hint.
|
|
195
|
+
const UPDATE_TTL_MS = 6 * 3600_000;
|
|
196
|
+
const BEAGLE_PKG = "@decentnetwork/beagle";
|
|
197
|
+
let updateCache = null;
|
|
198
|
+
const BEAGLE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
199
|
+
function beagleVersion() {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(readFileSync(join(BEAGLE_ROOT, "package.json"), "utf-8")).version ?? "";
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** How this copy of beagle got onto disk — decides what "update" can do.
|
|
208
|
+
* npx cache paths contain _npx AND node_modules, so test _npx first. */
|
|
209
|
+
function installMode() {
|
|
210
|
+
if (BEAGLE_ROOT.includes("_npx"))
|
|
211
|
+
return "npx";
|
|
212
|
+
if (BEAGLE_ROOT.includes(join("node_modules", "@decentnetwork", "beagle")))
|
|
213
|
+
return "global";
|
|
214
|
+
return "dev";
|
|
215
|
+
}
|
|
216
|
+
async function npmLatest(pkg) {
|
|
217
|
+
const ctl = new AbortController();
|
|
218
|
+
const t = setTimeout(() => ctl.abort(), 8000);
|
|
219
|
+
try {
|
|
220
|
+
const r = await fetch(`https://registry.npmjs.org/${pkg.replace("/", "%2F")}/latest`, { signal: ctl.signal });
|
|
221
|
+
if (!r.ok)
|
|
222
|
+
return null;
|
|
223
|
+
return (await r.json()).version ?? null;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
clearTimeout(t);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function verLt(a, b) {
|
|
233
|
+
const pa = a.split(".").map((x) => parseInt(x, 10));
|
|
234
|
+
const pb = b.split(".").map((x) => parseInt(x, 10));
|
|
235
|
+
if (pa.some(Number.isNaN) || pb.some(Number.isNaN))
|
|
236
|
+
return false;
|
|
237
|
+
for (let i = 0; i < 3; i++) {
|
|
238
|
+
const d = (pb[i] ?? 0) - (pa[i] ?? 0);
|
|
239
|
+
if (d !== 0)
|
|
240
|
+
return d > 0;
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
/** Run `npm i -g @decentnetwork/beagle@latest`. npm is found on PATH, else via
|
|
245
|
+
* npm-cli.js next to the running node (covers pm2/cron environments whose
|
|
246
|
+
* PATH lacks the nvm shims). */
|
|
247
|
+
async function npmSelfUpdate() {
|
|
248
|
+
const { spawn } = await import("node:child_process");
|
|
249
|
+
const args = ["i", "-g", `${BEAGLE_PKG}@latest`];
|
|
250
|
+
const npmCli = join(dirname(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
251
|
+
const attempts = [
|
|
252
|
+
{ cmd: "npm", args },
|
|
253
|
+
...(existsSync(npmCli) ? [{ cmd: process.execPath, args: [npmCli, ...args] }] : []),
|
|
254
|
+
];
|
|
255
|
+
for (const a of attempts) {
|
|
256
|
+
const r = await new Promise((resolveRun) => {
|
|
257
|
+
const ch = spawn(a.cmd, a.args, { shell: process.platform === "win32" });
|
|
258
|
+
let out = "";
|
|
259
|
+
const timer = setTimeout(() => ch.kill("SIGKILL"), 5 * 60_000);
|
|
260
|
+
ch.stdout?.on("data", (c) => (out += c));
|
|
261
|
+
ch.stderr?.on("data", (c) => (out += c));
|
|
262
|
+
ch.on("error", (e) => { clearTimeout(timer); resolveRun({ code: null, out, spawnErr: e.message }); });
|
|
263
|
+
ch.on("close", (code) => { clearTimeout(timer); resolveRun({ code, out }); });
|
|
264
|
+
});
|
|
265
|
+
if (r.spawnErr)
|
|
266
|
+
continue; // npm not found this way — try the next resolver
|
|
267
|
+
const tail = r.out.slice(-2000);
|
|
268
|
+
if (r.code === 0)
|
|
269
|
+
return { ok: true, log: tail };
|
|
270
|
+
const hint = /EACCES|permission denied/i.test(r.out)
|
|
271
|
+
? " — no write access to the global npm dir; run `sudo npm i -g @decentnetwork/beagle@latest` manually"
|
|
272
|
+
: "";
|
|
273
|
+
return { ok: false, error: `npm exited ${r.code}${hint}`, log: tail };
|
|
274
|
+
}
|
|
275
|
+
return { ok: false, error: "npm not found — update manually: npm i -g @decentnetwork/beagle@latest" };
|
|
276
|
+
}
|
|
157
277
|
/** True only when the request came from the local machine. Used to gate the
|
|
158
278
|
* "Sign in with Decent" routes so binding the UI to a LAN IP can't expose
|
|
159
279
|
* identity signing to other hosts. The popup always runs in the local user's
|
|
@@ -478,9 +598,9 @@ export function startBeagleServer(opts) {
|
|
|
478
598
|
}
|
|
479
599
|
}
|
|
480
600
|
// Punk directory (proxied): a random batch by type, and one punk by id.
|
|
481
|
-
if (req.method === "GET" && url
|
|
601
|
+
if (req.method === "GET" && url === "/api/punk-list") {
|
|
482
602
|
try {
|
|
483
|
-
const q = new URL(url, "http://localhost");
|
|
603
|
+
const q = new URL(req.url || "", "http://localhost");
|
|
484
604
|
const type = (q.searchParams.get("type") || "any").toLowerCase().replace(/[^a-z]/g, "") || "any";
|
|
485
605
|
const limit = Math.min(60, Math.max(1, Number(q.searchParams.get("limit")) || 24));
|
|
486
606
|
const list = await punksFetch(`/api/punks/filter/${type}/any?limit=${limit}`);
|
|
@@ -1092,11 +1212,103 @@ export function startBeagleServer(opts) {
|
|
|
1092
1212
|
// One bootstrap call for the desktop UI: composes the design's DK_*
|
|
1093
1213
|
// shapes (me / peers / requests / exits) from diag + friends-list +
|
|
1094
1214
|
// ipam + routes, so the client data layer is a single poll.
|
|
1215
|
+
// ── self-update: check (cached), run (localhost), restart (localhost) ──
|
|
1216
|
+
if (req.method === "GET" && url === "/api/update-check") {
|
|
1217
|
+
const force = new URL(req.url || "", "http://localhost").searchParams.get("force") === "1";
|
|
1218
|
+
if (!force && updateCache && Date.now() - updateCache.at < UPDATE_TTL_MS) {
|
|
1219
|
+
sendJson(res, 200, updateCache.body);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const [beagleLatest, peerLatest, lanLatest] = await Promise.all([
|
|
1223
|
+
npmLatest(BEAGLE_PKG),
|
|
1224
|
+
npmLatest("@decentnetwork/peer"),
|
|
1225
|
+
npmLatest("@decentnetwork/lan"),
|
|
1226
|
+
]);
|
|
1227
|
+
const curLan = opts.meExtra?.lanVer ?? "";
|
|
1228
|
+
const row = (pkg, current, latest) => ({
|
|
1229
|
+
pkg,
|
|
1230
|
+
current,
|
|
1231
|
+
latest,
|
|
1232
|
+
behind: !!(current && latest && verLt(current, latest)),
|
|
1233
|
+
});
|
|
1234
|
+
const body = {
|
|
1235
|
+
ok: true,
|
|
1236
|
+
checkedAt: Date.now(),
|
|
1237
|
+
mode: installMode(),
|
|
1238
|
+
updates: [
|
|
1239
|
+
row(BEAGLE_PKG, beagleVersion(), beagleLatest),
|
|
1240
|
+
// peer ships inside beagle: reinstalling beagle refreshes it too.
|
|
1241
|
+
row("@decentnetwork/peer", opts.meExtra?.peerVer ?? "", peerLatest),
|
|
1242
|
+
...(curLan && curLan !== "(none)" ? [row("@decentnetwork/lan", curLan, lanLatest)] : []),
|
|
1243
|
+
],
|
|
1244
|
+
};
|
|
1245
|
+
updateCache = { at: Date.now(), body };
|
|
1246
|
+
sendJson(res, 200, body);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (req.method === "POST" && url === "/api/update-run") {
|
|
1250
|
+
if (!isLocalRequest(req)) {
|
|
1251
|
+
sendJson(res, 403, { ok: false, error: "update runs only from this machine (localhost)" });
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const mode = installMode();
|
|
1255
|
+
if (mode === "dev") {
|
|
1256
|
+
sendJson(res, 400, { ok: false, error: "running from a source checkout — update with: git pull && npm run build" });
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (mode === "npx") {
|
|
1260
|
+
// npx with @latest re-resolves on every launch: restarting IS the update.
|
|
1261
|
+
sendJson(res, 200, { ok: true, npx: true });
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const r = await npmSelfUpdate();
|
|
1265
|
+
updateCache = null; // next check reflects the new install
|
|
1266
|
+
sendJson(res, r.ok ? 200 : 502, r);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (req.method === "POST" && url === "/api/update-restart") {
|
|
1270
|
+
if (!isLocalRequest(req)) {
|
|
1271
|
+
sendJson(res, 403, { ok: false, error: "restart only from this machine (localhost)" });
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
sendJson(res, 200, { ok: true });
|
|
1275
|
+
// Let the response flush, then exit 0: pm2/launchd/systemd restart the
|
|
1276
|
+
// process; a bare `beagle` in a terminal just exits (the UI says so).
|
|
1277
|
+
setTimeout(() => process.exit(0), 400);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
// Public profile card for any userid (friend or directory row): what
|
|
1281
|
+
// they published on their beagles.eth record. Read-only, no signing.
|
|
1282
|
+
if (req.method === "GET" && url === "/api/ens-profile") {
|
|
1283
|
+
const q = new URL(req.url || "", "http://localhost").searchParams;
|
|
1284
|
+
let userid = q.get("userid") || "";
|
|
1285
|
+
if (!userid) {
|
|
1286
|
+
sendJson(res, 400, { ok: false, error: "userid required" });
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
// A 52-char Carrier ADDRESS also works (group cards only have that):
|
|
1290
|
+
// its first 32 bytes are the pubkey, i.e. the userid.
|
|
1291
|
+
try {
|
|
1292
|
+
const bytes = peerAddr.base58ToBytes(userid);
|
|
1293
|
+
if (bytes.length === peerAddr.CARRIER_ADDRESS_SIZE)
|
|
1294
|
+
userid = peerAddr.bytesToBase58(bytes.slice(0, 32));
|
|
1295
|
+
}
|
|
1296
|
+
catch { /* not base58 — leave as-is */ }
|
|
1297
|
+
let prof = (await ensPublicByUserid(1500)).get(userid) ?? null;
|
|
1298
|
+
if (!prof) {
|
|
1299
|
+
const rec = await ensFetchJson(`/getAddress/${encodeURIComponent(userid)}`);
|
|
1300
|
+
if (rec)
|
|
1301
|
+
prof = ensPubFromEntry(rec.name, rec);
|
|
1302
|
+
}
|
|
1303
|
+
sendJson(res, 200, { ok: true, registered: !!prof, profile: prof });
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1095
1306
|
if (req.method === "GET" && url === "/api/desktop") {
|
|
1096
|
-
const [diag, pending, flist] = await Promise.all([
|
|
1307
|
+
const [diag, pending, flist, ensPub] = await Promise.all([
|
|
1097
1308
|
opts.call({ op: "diag" }),
|
|
1098
1309
|
opts.call({ op: "friends-pending" }),
|
|
1099
1310
|
opts.call({ op: "friends-list" }),
|
|
1311
|
+
ensPublicByUserid(),
|
|
1100
1312
|
]);
|
|
1101
1313
|
const d = (diag.ok ? diag.data : {}) ?? {};
|
|
1102
1314
|
const identity = d.identity ?? {};
|
|
@@ -1147,6 +1359,8 @@ export function startBeagleServer(opts) {
|
|
|
1147
1359
|
// null = backend didn't report it (daemon); the UI then hides the
|
|
1148
1360
|
// toggle rather than showing a state it cannot know.
|
|
1149
1361
|
autoAccept: typeof node.autoAccept === "boolean" ? node.autoAccept : null,
|
|
1362
|
+
avatarUrl: ensPub.get(identity.userid ?? "")?.avatarUrl ?? null,
|
|
1363
|
+
punkId: ensPub.get(identity.userid ?? "")?.punkId ?? null,
|
|
1150
1364
|
};
|
|
1151
1365
|
const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
|
|
1152
1366
|
const peers = fl.map((f) => {
|
|
@@ -1177,6 +1391,8 @@ export function startBeagleServer(opts) {
|
|
|
1177
1391
|
lastMsg: lm ? (lm.dir === "out" ? "you: " : "") + (lm.text ?? "") : "",
|
|
1178
1392
|
lastTime: fmtTime(lm?.ts),
|
|
1179
1393
|
wire: "163",
|
|
1394
|
+
avatarUrl: ensPub.get(uid)?.avatarUrl ?? null,
|
|
1395
|
+
punkId: ensPub.get(uid)?.punkId ?? null,
|
|
1180
1396
|
};
|
|
1181
1397
|
});
|
|
1182
1398
|
const pend = pending.ok ? (pending.data?.pending ?? []) : [];
|
package/package.json
CHANGED