@decentnetwork/beagle 0.1.16 → 0.1.18
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 +139 -4
- package/dist/server.js +66 -0
- 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.17";
|
|
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"/>',
|
|
@@ -1591,7 +1591,14 @@ function DkGroupSender({ grp, T, avatar }) {
|
|
|
1591
1591
|
setBusy(true);
|
|
1592
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
1593
|
};
|
|
1594
|
-
return /* @__PURE__ */ React.createElement("span", { style: {
|
|
1594
|
+
return /* @__PURE__ */ React.createElement("span", { style: {
|
|
1595
|
+
position: "relative",
|
|
1596
|
+
display: "inline-flex",
|
|
1597
|
+
alignItems: "center",
|
|
1598
|
+
gap: 5,
|
|
1599
|
+
margin: avatar ? 0 : "0 3px 2px",
|
|
1600
|
+
alignSelf: avatar ? "flex-start" : void 0
|
|
1601
|
+
} }, /* @__PURE__ */ React.createElement(
|
|
1595
1602
|
"button",
|
|
1596
1603
|
{
|
|
1597
1604
|
onClick: () => setOpen((v) => !v),
|
|
@@ -1602,7 +1609,7 @@ function DkGroupSender({ grp, T, avatar }) {
|
|
|
1602
1609
|
), 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
1610
|
position: "absolute",
|
|
1604
1611
|
left: 0,
|
|
1605
|
-
|
|
1612
|
+
top: avatar ? 28 : 20,
|
|
1606
1613
|
zIndex: 50,
|
|
1607
1614
|
width: 240,
|
|
1608
1615
|
padding: 12,
|
|
@@ -2815,6 +2822,112 @@ function ProfileTab({ T, me, onEdit }) {
|
|
|
2815
2822
|
} }));
|
|
2816
2823
|
}
|
|
2817
2824
|
Object.assign(window, { ProfileTab });
|
|
2825
|
+
function DkDirAvatar({ p, size }) {
|
|
2826
|
+
const [broken, setBroken] = React.useState(false);
|
|
2827
|
+
if (p.avatar && !broken) {
|
|
2828
|
+
return /* @__PURE__ */ React.createElement(
|
|
2829
|
+
"img",
|
|
2830
|
+
{
|
|
2831
|
+
src: p.avatar,
|
|
2832
|
+
alt: "",
|
|
2833
|
+
width: size,
|
|
2834
|
+
height: size,
|
|
2835
|
+
onError: () => setBroken(true),
|
|
2836
|
+
style: { width: size, height: size, borderRadius: Math.round(size / 4), objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" }
|
|
2837
|
+
}
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
return /* @__PURE__ */ React.createElement(DkIdenticon, { seed: p.userid || p.name, size, radius: Math.round(size / 4) });
|
|
2841
|
+
}
|
|
2842
|
+
function DkDirRow({ p, T, isFriend, isMe, onAdd, onOpenChat }) {
|
|
2843
|
+
const [state, setState] = React.useState(null);
|
|
2844
|
+
const add = () => {
|
|
2845
|
+
setState("busy");
|
|
2846
|
+
Promise.resolve(onAdd(p.address)).then((r) => setState(r && r.ok === false ? r.error || T.addFailed || "failed" : "sent")).catch((e) => setState(String(e && e.message || e)));
|
|
2847
|
+
};
|
|
2848
|
+
const btn = (label, onClick, disabled) => /* @__PURE__ */ React.createElement(
|
|
2849
|
+
"button",
|
|
2850
|
+
{
|
|
2851
|
+
onClick,
|
|
2852
|
+
disabled,
|
|
2853
|
+
style: {
|
|
2854
|
+
padding: "5px 11px",
|
|
2855
|
+
borderRadius: 8,
|
|
2856
|
+
border: "1px solid var(--line)",
|
|
2857
|
+
flexShrink: 0,
|
|
2858
|
+
background: disabled ? "transparent" : "var(--accent)",
|
|
2859
|
+
color: disabled ? "var(--faint)" : "#fff",
|
|
2860
|
+
fontFamily: "var(--ui)",
|
|
2861
|
+
fontSize: 12,
|
|
2862
|
+
cursor: disabled ? "default" : "pointer"
|
|
2863
|
+
}
|
|
2864
|
+
},
|
|
2865
|
+
label
|
|
2866
|
+
);
|
|
2867
|
+
return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 11, padding: "var(--row-pad)", borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement(DkDirAvatar, { p, size: 34 }), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "baseline", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 13.5, fontWeight: 600, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, p.name), p.points != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--accent)", flexShrink: 0 } }, p.points.toLocaleString(), " pts"), p.ens && p.ens !== p.name && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, p.ens)), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 } }, /* @__PURE__ */ React.createElement(Mono, { size: 10.5, dim: true, copy: p.userid, title: p.userid }, shortKey(p.userid, 10, 6)), p.description && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11, color: "var(--faint)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, p.description))), isMe ? /* @__PURE__ */ React.createElement(Tag, null, T.dirMe || "me") : isFriend ? btn(T.dirOpenChat || "chat", () => onOpenChat(p.userid), false) : state === "sent" ? /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, T.dirSent || "requested") : state && state !== "busy" ? /* @__PURE__ */ React.createElement("span", { title: state, style: { fontFamily: "var(--ui)", fontSize: 11, color: "var(--warn, #f59e0b)", flexShrink: 0 } }, T.addFailed || "failed") : btn(state === "busy" ? "\u2026" : T.dirAdd || "Add", add, state === "busy"));
|
|
2868
|
+
}
|
|
2869
|
+
function DiscoverTab({ T, kind, peers, meId, onAdd, onOpenChat }) {
|
|
2870
|
+
const [list, setList] = React.useState(null);
|
|
2871
|
+
const [err, setErr] = React.useState(null);
|
|
2872
|
+
const [q, setQ] = React.useState("");
|
|
2873
|
+
React.useEffect(() => {
|
|
2874
|
+
let dead = false;
|
|
2875
|
+
setList(null);
|
|
2876
|
+
setErr(null);
|
|
2877
|
+
fetch(kind === "recommended" ? "/api/discover-recommended" : "/api/discover-registered").then((r) => r.json()).then((d) => {
|
|
2878
|
+
if (dead)
|
|
2879
|
+
return;
|
|
2880
|
+
if (d.ok)
|
|
2881
|
+
setList(d.list || []);
|
|
2882
|
+
else
|
|
2883
|
+
setErr(d.error || "load failed");
|
|
2884
|
+
}).catch((e) => {
|
|
2885
|
+
if (!dead)
|
|
2886
|
+
setErr(String(e && e.message || e));
|
|
2887
|
+
});
|
|
2888
|
+
return () => {
|
|
2889
|
+
dead = true;
|
|
2890
|
+
};
|
|
2891
|
+
}, [kind]);
|
|
2892
|
+
const friendIds = React.useMemo(() => {
|
|
2893
|
+
const s = /* @__PURE__ */ new Set();
|
|
2894
|
+
for (const p of peers || []) {
|
|
2895
|
+
if (p.id)
|
|
2896
|
+
s.add(p.id);
|
|
2897
|
+
if (p.userId)
|
|
2898
|
+
s.add(p.userId);
|
|
2899
|
+
}
|
|
2900
|
+
return s;
|
|
2901
|
+
}, [peers]);
|
|
2902
|
+
const shown = React.useMemo(() => {
|
|
2903
|
+
if (!list)
|
|
2904
|
+
return [];
|
|
2905
|
+
const needle = q.trim().toLowerCase();
|
|
2906
|
+
if (!needle)
|
|
2907
|
+
return list;
|
|
2908
|
+
return list.filter((p) => (p.name || "").toLowerCase().includes(needle) || (p.ens || "").toLowerCase().includes(needle) || (p.userid || "").toLowerCase().includes(needle));
|
|
2909
|
+
}, [list, q]);
|
|
2910
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderBottom: "1px solid var(--line)", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "search", size: 15, stroke: 2, color: "var(--faint)" }), /* @__PURE__ */ React.createElement(
|
|
2911
|
+
"input",
|
|
2912
|
+
{
|
|
2913
|
+
value: q,
|
|
2914
|
+
onChange: (e) => setQ(e.target.value),
|
|
2915
|
+
placeholder: T.dirSearch || "search name / userid",
|
|
2916
|
+
style: { flex: 1, background: "transparent", border: "none", outline: "none", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13 }
|
|
2917
|
+
}
|
|
2918
|
+
), list && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, shown.length, "/", list.length)), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minHeight: 0, overflowY: "auto" } }, err ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, (T.dirError || "Could not load the directory:") + " " + err) : !list ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : shown.length === 0 ? /* @__PURE__ */ React.createElement("div", { style: { padding: 22, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirEmpty || "nobody here") : shown.map((p) => /* @__PURE__ */ React.createElement(
|
|
2919
|
+
DkDirRow,
|
|
2920
|
+
{
|
|
2921
|
+
key: p.userid,
|
|
2922
|
+
p,
|
|
2923
|
+
T,
|
|
2924
|
+
isMe: !!meId && p.userid === meId,
|
|
2925
|
+
isFriend: friendIds.has(p.userid),
|
|
2926
|
+
onAdd,
|
|
2927
|
+
onOpenChat
|
|
2928
|
+
}
|
|
2929
|
+
))));
|
|
2930
|
+
}
|
|
2818
2931
|
const DK_FILE_RTC_KIND = "file";
|
|
2819
2932
|
const DK_FILE_RTC_CHUNK = 16 * 1024;
|
|
2820
2933
|
const DK_FILE_RTC_OPEN_TIMEOUT_MS = 2e4;
|
|
@@ -3638,6 +3751,16 @@ const STR = {
|
|
|
3638
3751
|
chat: "Chat",
|
|
3639
3752
|
network: "Network",
|
|
3640
3753
|
profile: "Profile",
|
|
3754
|
+
recommended: "Discover",
|
|
3755
|
+
registered: "Names",
|
|
3756
|
+
dirAdd: "Add",
|
|
3757
|
+
dirSent: "requested",
|
|
3758
|
+
dirOpenChat: "chat",
|
|
3759
|
+
dirMe: "me",
|
|
3760
|
+
dirSearch: "search name / userid\u2026",
|
|
3761
|
+
dirLoading: "loading directory\u2026",
|
|
3762
|
+
dirEmpty: "nobody matches",
|
|
3763
|
+
dirError: "Could not load the directory:",
|
|
3641
3764
|
addPlaceholder: "paste a friend's carrier address\u2026",
|
|
3642
3765
|
add: "Add",
|
|
3643
3766
|
search: "search peers / ip\u2026",
|
|
@@ -3739,6 +3862,16 @@ const STR = {
|
|
|
3739
3862
|
chat: "\u804A\u5929",
|
|
3740
3863
|
network: "\u7F51\u7EDC",
|
|
3741
3864
|
profile: "\u6211\u7684",
|
|
3865
|
+
recommended: "\u63A8\u8350",
|
|
3866
|
+
registered: "\u540D\u5F55",
|
|
3867
|
+
dirAdd: "\u52A0\u597D\u53CB",
|
|
3868
|
+
dirSent: "\u5DF2\u8BF7\u6C42",
|
|
3869
|
+
dirOpenChat: "\u4F1A\u8BDD",
|
|
3870
|
+
dirMe: "\u6211",
|
|
3871
|
+
dirSearch: "\u641C\u7D22\u540D\u5B57 / userid\u2026",
|
|
3872
|
+
dirLoading: "\u6B63\u5728\u52A0\u8F7D\u540D\u5355\u2026",
|
|
3873
|
+
dirEmpty: "\u6CA1\u6709\u5339\u914D\u7684\u4EBA",
|
|
3874
|
+
dirError: "\u540D\u5355\u52A0\u8F7D\u5931\u8D25\uFF1A",
|
|
3742
3875
|
addPlaceholder: "\u7C98\u8D34\u597D\u53CB\u7684 carrier \u5730\u5740\u2026",
|
|
3743
3876
|
add: "\u6DFB\u52A0",
|
|
3744
3877
|
search: "\u641C\u7D22\u597D\u53CB / IP\u2026",
|
|
@@ -4028,10 +4161,12 @@ function DkApp() {
|
|
|
4028
4161
|
const onOpenNet = () => setTab("network");
|
|
4029
4162
|
const nav = [
|
|
4030
4163
|
{ id: "chat", icon: "message", label: T.chat },
|
|
4164
|
+
{ id: "recommended", icon: "sparkles", label: T.recommended },
|
|
4165
|
+
{ id: "registered", icon: "at", label: T.registered },
|
|
4031
4166
|
{ id: "network", icon: "network", label: T.network },
|
|
4032
4167
|
{ id: "profile", icon: "userRound", label: T.profile }
|
|
4033
4168
|
];
|
|
4034
|
-
return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr("") }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit })), /* @__PURE__ */ React.createElement(TweaksPanel, null, /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5916\u89C2" : "Appearance" }), /* @__PURE__ */ React.createElement(
|
|
4169
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr("") }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit })), /* @__PURE__ */ React.createElement(TweaksPanel, null, /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5916\u89C2" : "Appearance" }), /* @__PURE__ */ React.createElement(
|
|
4035
4170
|
TweakRadio,
|
|
4036
4171
|
{
|
|
4037
4172
|
label: t.lang === "zh" ? "\u4E3B\u9898" : "Theme",
|
package/dist/server.js
CHANGED
|
@@ -29,6 +29,28 @@ const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
|
29
29
|
// (which probes all exits on each poll) stays responsive over a lossy mesh.
|
|
30
30
|
const EXIT_PROBE_PORT = 8888;
|
|
31
31
|
const EXIT_PROBE_TIMEOUT_MS = 2000;
|
|
32
|
+
/** Server-side fetch for the discovery directories (leaderboard / ENS names),
|
|
33
|
+
* with a small TTL cache so tab switches don't hammer the upstreams. */
|
|
34
|
+
const DISCOVER_CACHE_TTL_MS = 5 * 60_000;
|
|
35
|
+
const discoverCache = new Map();
|
|
36
|
+
async function fetchDiscoverJson(url) {
|
|
37
|
+
const hit = discoverCache.get(url);
|
|
38
|
+
if (hit && Date.now() - hit.at < DISCOVER_CACHE_TTL_MS)
|
|
39
|
+
return hit.body;
|
|
40
|
+
const ctl = new AbortController();
|
|
41
|
+
const timer = setTimeout(() => ctl.abort(), 10_000);
|
|
42
|
+
try {
|
|
43
|
+
const r = await fetch(url, { signal: ctl.signal });
|
|
44
|
+
if (!r.ok)
|
|
45
|
+
throw new Error(`upstream ${r.status}`);
|
|
46
|
+
const body = await r.json();
|
|
47
|
+
discoverCache.set(url, { at: Date.now(), body });
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
32
54
|
/** True only when the request came from the local machine. Used to gate the
|
|
33
55
|
* "Sign in with Decent" routes so binding the UI to a LAN IP can't expose
|
|
34
56
|
* identity signing to other hosts. The popup always runs in the local user's
|
|
@@ -619,6 +641,50 @@ export function startBeagleServer(opts) {
|
|
|
619
641
|
sendJson(res, 200, { friends });
|
|
620
642
|
return;
|
|
621
643
|
}
|
|
644
|
+
// People-discovery directories, proxied server-side: the browser can't
|
|
645
|
+
// fetch these origins (CORS), and the cache keeps us polite upstream.
|
|
646
|
+
// recommended = points leaderboard; registered = mobile-app ENS names.
|
|
647
|
+
if (req.method === "GET" && url === "/api/discover-recommended") {
|
|
648
|
+
try {
|
|
649
|
+
const raw = (await fetchDiscoverJson("https://points.beagle.chat/leaderboard"));
|
|
650
|
+
const list = (Array.isArray(raw) ? raw : [])
|
|
651
|
+
.map((e) => ({
|
|
652
|
+
userid: typeof e.account === "string" ? e.account : "",
|
|
653
|
+
address: typeof e.carrier_address === "string" ? e.carrier_address : "",
|
|
654
|
+
name: (typeof e.name === "string" && e.name) || (typeof e.ens_name === "string" && e.ens_name) || "",
|
|
655
|
+
avatar: typeof e.avatar === "string" && /^https:\/\//.test(e.avatar) ? e.avatar : null,
|
|
656
|
+
points: typeof e.points === "number" ? e.points : null,
|
|
657
|
+
}))
|
|
658
|
+
.filter((e) => e.userid && e.address);
|
|
659
|
+
sendJson(res, 200, { ok: true, list });
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
sendJson(res, 502, { ok: false, error: String(err?.message ?? err) });
|
|
663
|
+
}
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (req.method === "GET" && url === "/api/discover-registered") {
|
|
667
|
+
try {
|
|
668
|
+
const raw = (await fetchDiscoverJson("https://ens-gateway.beaglechat.workers.dev/names"));
|
|
669
|
+
const list = Object.entries(raw && typeof raw === "object" ? raw : {})
|
|
670
|
+
.map(([ens, v]) => {
|
|
671
|
+
const t = v?.texts ?? {};
|
|
672
|
+
return {
|
|
673
|
+
userid: t.carrierUserId ?? "",
|
|
674
|
+
address: t.carrierAddress ?? "",
|
|
675
|
+
name: t.displayName || ens,
|
|
676
|
+
ens,
|
|
677
|
+
description: t.description || null,
|
|
678
|
+
};
|
|
679
|
+
})
|
|
680
|
+
.filter((e) => e.userid && e.address);
|
|
681
|
+
sendJson(res, 200, { ok: true, list });
|
|
682
|
+
}
|
|
683
|
+
catch (err) {
|
|
684
|
+
sendJson(res, 502, { ok: false, error: String(err?.message ?? err) });
|
|
685
|
+
}
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
622
688
|
// One bootstrap call for the desktop UI: composes the design's DK_*
|
|
623
689
|
// shapes (me / peers / requests / exits) from diag + friends-list +
|
|
624
690
|
// ipam + routes, so the client data layer is a single poll.
|
package/package.json
CHANGED