@decentnetwork/beagle 0.1.17 → 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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.16";
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"/>',
@@ -2822,6 +2822,112 @@ function ProfileTab({ T, me, onEdit }) {
2822
2822
  } }));
2823
2823
  }
2824
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
+ }
2825
2931
  const DK_FILE_RTC_KIND = "file";
2826
2932
  const DK_FILE_RTC_CHUNK = 16 * 1024;
2827
2933
  const DK_FILE_RTC_OPEN_TIMEOUT_MS = 2e4;
@@ -3645,6 +3751,16 @@ const STR = {
3645
3751
  chat: "Chat",
3646
3752
  network: "Network",
3647
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:",
3648
3764
  addPlaceholder: "paste a friend's carrier address\u2026",
3649
3765
  add: "Add",
3650
3766
  search: "search peers / ip\u2026",
@@ -3746,6 +3862,16 @@ const STR = {
3746
3862
  chat: "\u804A\u5929",
3747
3863
  network: "\u7F51\u7EDC",
3748
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",
3749
3875
  addPlaceholder: "\u7C98\u8D34\u597D\u53CB\u7684 carrier \u5730\u5740\u2026",
3750
3876
  add: "\u6DFB\u52A0",
3751
3877
  search: "\u641C\u7D22\u597D\u53CB / IP\u2026",
@@ -4035,10 +4161,12 @@ function DkApp() {
4035
4161
  const onOpenNet = () => setTab("network");
4036
4162
  const nav = [
4037
4163
  { id: "chat", icon: "message", label: T.chat },
4164
+ { id: "recommended", icon: "sparkles", label: T.recommended },
4165
+ { id: "registered", icon: "at", label: T.registered },
4038
4166
  { id: "network", icon: "network", label: T.network },
4039
4167
  { id: "profile", icon: "userRound", label: T.profile }
4040
4168
  ];
4041
- 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(
4042
4170
  TweakRadio,
4043
4171
  {
4044
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",