@decentnetwork/beagle 0.1.129 → 0.1.130
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 +27 -11
- package/dist/exit-probe.d.ts +16 -11
- package/dist/exit-probe.js +35 -29
- package/dist/server.d.ts +2 -0
- package/dist/server.js +64 -29
- package/package.json +2 -2
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.130";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -446,6 +446,8 @@ window.__DK_UI_VERSION="0.1.129";
|
|
|
446
446
|
// that failed three times in a row are never probed again on their own.
|
|
447
447
|
reconnectExit: (ip) => dkPost("/api/exits/reconnect", { ip }),
|
|
448
448
|
reconnectAllExits: () => dkPost("/api/exits/reconnect", { all: true }),
|
|
449
|
+
// Every exit once, now, failed ones included.
|
|
450
|
+
refreshExits: () => dkPost("/api/exits/refresh", {}),
|
|
449
451
|
// Used by the browser's welcome flow, which also picks an avatar and marks
|
|
450
452
|
// first run done. Harmless where there is no welcome flow: nothing calls it.
|
|
451
453
|
setProfileFull: (p) => dkPost("/api/set-profile", p),
|
|
@@ -585,6 +587,7 @@ window.__DK_UI_VERSION="0.1.129";
|
|
|
585
587
|
const wantExits = !!(opts && opts.exits);
|
|
586
588
|
const wantExitsRef = React.useRef(wantExits);
|
|
587
589
|
wantExitsRef.current = wantExits;
|
|
590
|
+
const exitsOpenedRef = React.useRef(false);
|
|
588
591
|
const [snap, setSnap] = React.useState({
|
|
589
592
|
me: DK_ME_FALLBACK,
|
|
590
593
|
peers: [],
|
|
@@ -601,7 +604,12 @@ window.__DK_UI_VERSION="0.1.129";
|
|
|
601
604
|
const refresh = React.useCallback(async () => {
|
|
602
605
|
var _a, _b, _c;
|
|
603
606
|
try {
|
|
604
|
-
|
|
607
|
+
let path = "/api/desktop";
|
|
608
|
+
if (wantExitsRef.current) {
|
|
609
|
+
path += exitsOpenedRef.current ? "?exits=1&open=1" : "?exits=1";
|
|
610
|
+
exitsOpenedRef.current = false;
|
|
611
|
+
}
|
|
612
|
+
const d = await dkGet(path);
|
|
605
613
|
if (d && d.locked) {
|
|
606
614
|
setSnap((s) => ({ ...s, locked: true, loaded: true }));
|
|
607
615
|
return;
|
|
@@ -633,8 +641,10 @@ window.__DK_UI_VERSION="0.1.129";
|
|
|
633
641
|
return () => clearInterval(t);
|
|
634
642
|
}, [refresh]);
|
|
635
643
|
React.useEffect(() => {
|
|
636
|
-
if (wantExits)
|
|
644
|
+
if (wantExits) {
|
|
645
|
+
exitsOpenedRef.current = true;
|
|
637
646
|
refresh();
|
|
647
|
+
}
|
|
638
648
|
}, [wantExits, refresh]);
|
|
639
649
|
const inFlight = React.useRef(/* @__PURE__ */ new Set());
|
|
640
650
|
const cache = React.useRef({});
|
|
@@ -6169,7 +6179,7 @@ ${peer.address}`
|
|
|
6169
6179
|
const stuck = n.online && !n.reachable;
|
|
6170
6180
|
const failed = n.status === "failed";
|
|
6171
6181
|
const usable = n.reachable && featureOn !== false;
|
|
6172
|
-
return /* @__PURE__ */ React.createElement("div", { key: n.ip, className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 12, rowGap: 6, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : failed ? /* @__PURE__ */ React.createElement(Tag, { tone: "danger", title: "Failed 3 checks in a row \u2014 no longer checked
|
|
6182
|
+
return /* @__PURE__ */ React.createElement("div", { key: n.ip, className: "dk-wrap", style: { display: "flex", alignItems: "center", gap: 12, rowGap: 6, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.reachable }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.reachable ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : failed ? /* @__PURE__ */ React.createElement(Tag, { tone: "danger", title: "Failed 3 checks in a row \u2014 no longer checked; Reconnect to try again" }, T.exitFailed || "failed") : n.status === "unknown" ? /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, T.exitNotChecked || "not checked") : stuck ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "Announces online but IP won't pass \u2014 session desync or NAT-blocked" }, "online \xB7 no route") : n.status === "down" ? /* @__PURE__ */ React.createElement(Tag, { tone: "warn", title: "No answer \u2014 checked again only when you open this panel or press Refresh; 3 in a row and it is no longer checked" }, T.exitDown || "down", " \xB7 ", n.fails, "/3") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), failed ? /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: n.ip, onReconnect }) : active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", disabled: !usable, onClick: () => usable && onSetExit(n.ip), style: { opacity: usable ? 1 : 0.4 } }, T.routeThru));
|
|
6173
6183
|
})));
|
|
6174
6184
|
}
|
|
6175
6185
|
function LanEnableCard({ backend, onArm, onCancel }) {
|
|
@@ -6273,14 +6283,14 @@ ${peer.address}`
|
|
|
6273
6283
|
"\u53D6\u6D88,\u6062\u590D\u6D88\u606F"
|
|
6274
6284
|
)));
|
|
6275
6285
|
}
|
|
6276
|
-
function NetworkTab({ T, me, peers, exits, activeExit, exitFeature, routeFallback, onReconnectExit, reqCount, onSetExit, onOpenChat, backend, onArmLan, onCancelLan }) {
|
|
6286
|
+
function NetworkTab({ T, me, peers, exits, activeExit, exitFeature, routeFallback, onReconnectExit, onRefreshExits, reqCount, onSetExit, onOpenChat, backend, onArmLan, onCancelLan }) {
|
|
6277
6287
|
const featureOn = !exitFeature || exitFeature.available !== false;
|
|
6278
6288
|
const anyFailed = (exits || []).some((r) => (r.nodes || []).some((n) => n.status === "failed"));
|
|
6279
6289
|
if (backend && backend.switchable && !backend.hasVirtualLan) {
|
|
6280
6290
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement(LanEnableCard, { backend, onArm: onArmLan, onCancel: onCancelLan })));
|
|
6281
6291
|
}
|
|
6282
6292
|
const lanless = !!backend && backend.hasVirtualLan === false;
|
|
6283
|
-
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat }), !peers.length && /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", borderRadius: 11, border: "1px dashed var(--line)", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.noPeersYet || "No friends yet \u2014 add one from Chat and it shows up here with its route.")), lanless ? null : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, anyFailed && onReconnectExit && /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: null, onReconnect: onReconnectExit, label: T.exitReconnectAll || "Reconnect all" }), /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), !featureOn && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, padding: "11px 14px", borderRadius: 10, background: "var(--panel-2)", border: "1px dashed var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 700, color: "var(--text)" } }, T.noExitAvailable || "No exit available \u2014 routing and proxy are off."), anyFailed && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)" } }, T.noExitHint || "Every exit failed 3 times and is no longer checked automatically. Press Reconnect to try one again.")), routeFallback && routeFallback.from && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.routeFellBack || "Routing fell back to direct: every exit it used failed. Reconnect one to route through it again.")), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid1", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit, onReconnect: onReconnectExit, featureOn }))))));
|
|
6293
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { className: "dk-page", style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat }), !peers.length && /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", borderRadius: 11, border: "1px dashed var(--line)", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.noPeersYet || "No friends yet \u2014 add one from Chat and it shows up here with its route.")), lanless ? null : /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, onRefreshExits && (exits || []).length > 0 && /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: null, onReconnect: () => onRefreshExits(), label: T.exitRefresh || "Refresh" }), anyFailed && onReconnectExit && /* @__PURE__ */ React.createElement(ReconnectBtn, { T, ip: null, onReconnect: onReconnectExit, label: T.exitReconnectAll || "Reconnect all" }), /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), !featureOn && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, padding: "11px 14px", borderRadius: 10, background: "var(--panel-2)", border: "1px dashed var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 700, color: "var(--text)" } }, T.noExitAvailable || "No exit available \u2014 routing and proxy are off."), anyFailed && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--dim)" } }, T.noExitHint || "Every exit failed 3 times and is no longer checked automatically. Press Reconnect to try one again.")), routeFallback && routeFallback.from && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.routeFellBack || "Routing fell back to direct: every exit it used failed. Reconnect one to route through it again.")), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { className: "dk-grid1", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit, onReconnect: onReconnectExit, featureOn }))))));
|
|
6284
6294
|
}
|
|
6285
6295
|
Object.assign(window, { NetworkTab });
|
|
6286
6296
|
var dkAudioCtx = null;
|
|
@@ -6697,9 +6707,10 @@ ${peer.address}`
|
|
|
6697
6707
|
exitReconnectAll: "Reconnect all",
|
|
6698
6708
|
exitChecking: "checking\u2026",
|
|
6699
6709
|
exitNotChecked: "not checked",
|
|
6700
|
-
|
|
6710
|
+
exitDown: "down",
|
|
6711
|
+
exitRefresh: "Refresh",
|
|
6701
6712
|
noExitAvailable: "No exit available \u2014 routing and proxy are off.",
|
|
6702
|
-
noExitHint: "Every exit failed 3
|
|
6713
|
+
noExitHint: "Every exit failed 3 checks in a row and is no longer checked. Press Reconnect to try one again.",
|
|
6703
6714
|
routeFellBack: "Routing fell back to direct: every exit it used failed. Reconnect one to route through it again.",
|
|
6704
6715
|
identity: "Identity",
|
|
6705
6716
|
userId: "user id",
|
|
@@ -6908,9 +6919,10 @@ ${peer.address}`
|
|
|
6908
6919
|
exitReconnectAll: "\u5168\u90E8\u91CD\u8FDE",
|
|
6909
6920
|
exitChecking: "\u68C0\u6D4B\u4E2D\u2026",
|
|
6910
6921
|
exitNotChecked: "\u672A\u68C0\u6D4B",
|
|
6911
|
-
|
|
6922
|
+
exitDown: "\u4E0D\u901A",
|
|
6923
|
+
exitRefresh: "\u5237\u65B0",
|
|
6912
6924
|
noExitAvailable: "\u6CA1\u6709\u53EF\u7528\u7684\u51FA\u53E3 \u2014\u2014 \u8DEF\u7531\u548C\u4EE3\u7406\u5DF2\u5173\u95ED\u3002",
|
|
6913
|
-
noExitHint: "\u6240\u6709\u51FA\u53E3\u90FD\u8FDE\u7EED
|
|
6925
|
+
noExitHint: "\u6240\u6709\u51FA\u53E3\u90FD\u8FDE\u7EED 3 \u6B21\u68C0\u6D4B\u5931\u8D25,\u5DF2\u4E0D\u518D\u68C0\u6D4B\u3002\u70B9\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u518D\u8BD5\u4E00\u6B21\u3002",
|
|
6914
6926
|
routeFellBack: "\u8DEF\u7531\u5DF2\u6539\u56DE\u76F4\u8FDE:\u5B83\u4F7F\u7528\u7684\u51FA\u53E3\u5168\u90E8\u5931\u8D25\u3002\u91CD\u65B0\u8FDE\u63A5\u5176\u4E2D\u4E00\u4E2A\u5373\u53EF\u6062\u590D\u3002",
|
|
6915
6927
|
identity: "\u8EAB\u4EFD",
|
|
6916
6928
|
userId: "\u7528\u6237 ID",
|
|
@@ -7573,6 +7585,10 @@ ${peer.address}`
|
|
|
7573
7585
|
data.refresh();
|
|
7574
7586
|
return r;
|
|
7575
7587
|
});
|
|
7588
|
+
const onRefreshExits = () => dkApi.refreshExits().then((r) => {
|
|
7589
|
+
data.refresh();
|
|
7590
|
+
return r;
|
|
7591
|
+
});
|
|
7576
7592
|
const onOpenChat = (id) => {
|
|
7577
7593
|
setTab("chat");
|
|
7578
7594
|
openConversation(id);
|
|
@@ -7652,7 +7668,7 @@ ${peer.address}`
|
|
|
7652
7668
|
fontWeight: 600,
|
|
7653
7669
|
background: tab === n.id ? "var(--accent)" : "transparent",
|
|
7654
7670
|
color: tab === n.id ? "#fff" : "var(--dim)"
|
|
7655
|
-
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, isProtocol, protocolFix, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), 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, exitFeature: data.exitFeature, routeFallback: data.routeFallback, onReconnectExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
|
|
7671
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: n.icon, size: 14, stroke: 2, color: tab === n.id ? "#fff" : "var(--dim)" }), n.label))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, phone, isProtocol, protocolFix, onBack: closeConversation, peers, requests, activeId, thread: activeId === ASSISTANT_ID ? assistant.thread : data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), hasEarlier: !!(activeId && activeId !== ASSISTANT_ID && data.earlier[activeId]), onAssistantRemove: activeId === ASSISTANT_ID ? assistant.removeFriends : null, onAssistantAdopt: activeId === ASSISTANT_ID ? assistant.adoptServicePeers : null, onLoadEarlier: () => activeId && data.loadEarlier(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr(""), muted: mutedIds, onToggleMute }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), 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, exitFeature: data.exitFeature, routeFallback: data.routeFallback, onReconnectExit, onRefreshExits, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t, me, peers, phone, setTab }), tabs.map((x) => x.kind === "host" && tab === x.id ? /* @__PURE__ */ React.createElement(HostTabPane, { key: x.id, tab: x, props: { T, t, me, peers, phone, setTab }, lang: t.lang }) : null), tab === "profile" && /* @__PURE__ */ React.createElement(
|
|
7656
7672
|
ProfileTab,
|
|
7657
7673
|
{
|
|
7658
7674
|
T,
|
package/dist/exit-probe.d.ts
CHANGED
|
@@ -2,11 +2,12 @@ export declare const EXIT_PROBE_PORT = 8888;
|
|
|
2
2
|
export declare const EXIT_PROBE_TIMEOUT_MS = 2000;
|
|
3
3
|
/** Consecutive failed probes after which an exit is `failed`. */
|
|
4
4
|
export declare const EXIT_MAX_FAILS = 3;
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
5
|
+
/** Panel polls (?exits=1) closer together than this are one panel visit, so
|
|
6
|
+
* they cost one probe round, not one per poll. Long, because a background
|
|
7
|
+
* browser tab is throttled to a poll a minute and must not count as
|
|
8
|
+
* re-opening the panel each time. Re-opening the tab says so explicitly. */
|
|
9
|
+
export declare const EXIT_PANEL_SESSION_GAP_MS: number;
|
|
10
|
+
export type ExitStatus = "unknown" | "ok" | "down" | "failed";
|
|
10
11
|
export interface ExitRecord {
|
|
11
12
|
/** Consecutive failed probes (reset by a success). */
|
|
12
13
|
fails: number;
|
|
@@ -14,8 +15,6 @@ export interface ExitRecord {
|
|
|
14
15
|
reachable: boolean;
|
|
15
16
|
ping: number | null;
|
|
16
17
|
lastProbeAt: number;
|
|
17
|
-
/** No automatic probe before this time. */
|
|
18
|
-
nextProbeAt: number;
|
|
19
18
|
}
|
|
20
19
|
export interface ExitView {
|
|
21
20
|
ip: string;
|
|
@@ -54,14 +53,20 @@ export declare class ExitProber {
|
|
|
54
53
|
/** True while at least one of `ips` is not failed — i.e. there is an exit
|
|
55
54
|
* the route/proxy feature could still use. None configured = false. */
|
|
56
55
|
usable(ips: string[]): boolean;
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
56
|
+
/** A network-panel poll arrived (?exits=1). True when it is a new visit —
|
|
57
|
+
* `opened` (the UI's first poll after the tab opens) or the first poll
|
|
58
|
+
* after a long gap — and so earns one probe round. Every other poll of
|
|
59
|
+
* the same visit is false: an open panel costs nothing. */
|
|
60
|
+
panelPoll(opened: boolean): boolean;
|
|
61
|
+
/** One access: probe every exit in `ips` once — never a failed one, never
|
|
62
|
+
* one already being probed. Resolves when those probes are done. */
|
|
61
63
|
refresh(ips: string[]): Promise<void>;
|
|
62
64
|
/** The Reconnect button: one probe now, whatever the state. Success makes
|
|
63
65
|
* the exit normal again; a failure leaves a failed exit failed. */
|
|
64
66
|
reconnect(ip: string): Promise<ExitView>;
|
|
67
|
+
/** The Refresh button: every exit once, failed ones included (with
|
|
68
|
+
* Reconnect's rule: a failed one that fails again stays failed). */
|
|
69
|
+
refreshAll(ips: string[]): Promise<ExitView[]>;
|
|
65
70
|
get fallback(): RouteFallback | null;
|
|
66
71
|
setFallback(f: RouteFallback | null): void;
|
|
67
72
|
}
|
package/dist/exit-probe.js
CHANGED
|
@@ -7,22 +7,25 @@
|
|
|
7
7
|
// of SYNs to seven hosts that never answered — whether or not routing used an
|
|
8
8
|
// exit and whether or not anyone was looking at the panel.
|
|
9
9
|
//
|
|
10
|
-
// The rules this file holds:
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// -
|
|
10
|
+
// The rules this file holds (Weili 2026-11-05: "有访问的时候再查,联系失败后停止查询"
|
|
11
|
+
// — check when something is accessed, stop checking once contact fails):
|
|
12
|
+
// - nothing here runs on a timer. An exit is probed only on an access: the
|
|
13
|
+
// network panel being OPENED (not while it stays open — see panelPoll),
|
|
14
|
+
// a Refresh / Reconnect press, or routing being set to go through it;
|
|
15
|
+
// - each failed access is one failure; MAX_FAILS in a row and the exit is
|
|
16
|
+
// `failed` and only Reconnect / Refresh probes it again. A success resets;
|
|
17
|
+
// - the record lives on disk, so a restart does not start over.
|
|
16
18
|
import net from "node:net";
|
|
17
19
|
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
18
20
|
export const EXIT_PROBE_PORT = 8888;
|
|
19
21
|
export const EXIT_PROBE_TIMEOUT_MS = 2000;
|
|
20
22
|
/** Consecutive failed probes after which an exit is `failed`. */
|
|
21
23
|
export const EXIT_MAX_FAILS = 3;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/** Panel polls (?exits=1) closer together than this are one panel visit, so
|
|
25
|
+
* they cost one probe round, not one per poll. Long, because a background
|
|
26
|
+
* browser tab is throttled to a poll a minute and must not count as
|
|
27
|
+
* re-opening the panel each time. Re-opening the tab says so explicitly. */
|
|
28
|
+
export const EXIT_PANEL_SESSION_GAP_MS = 5 * 60_000;
|
|
26
29
|
/** One TCP handshake to the exit's CONNECT-proxy port. A completed handshake
|
|
27
30
|
* proves the mesh path and the exit process are both live. ECONNREFUSED means
|
|
28
31
|
* the host answered (L3 reachable) but nothing listens — still reachable for
|
|
@@ -62,6 +65,7 @@ export class ExitProber {
|
|
|
62
65
|
#exits = new Map();
|
|
63
66
|
#fallback = null;
|
|
64
67
|
#inFlight = new Map();
|
|
68
|
+
#lastPanelPoll = -Infinity;
|
|
65
69
|
constructor(opts = {}) {
|
|
66
70
|
this.#path = opts.path;
|
|
67
71
|
this.#probe = opts.probe ?? ((ip) => tcpProbe(ip));
|
|
@@ -82,7 +86,6 @@ export class ExitProber {
|
|
|
82
86
|
reachable: !!r.reachable,
|
|
83
87
|
ping: typeof r.ping === "number" ? r.ping : null,
|
|
84
88
|
lastProbeAt: Number(r.lastProbeAt) || 0,
|
|
85
|
-
nextProbeAt: Number(r.nextProbeAt) || 0,
|
|
86
89
|
});
|
|
87
90
|
}
|
|
88
91
|
this.#fallback = f?.fallback && typeof f.fallback.from === "string" ? f.fallback : null;
|
|
@@ -107,7 +110,7 @@ export class ExitProber {
|
|
|
107
110
|
#record(ip) {
|
|
108
111
|
let r = this.#exits.get(ip);
|
|
109
112
|
if (!r) {
|
|
110
|
-
r = { fails: 0, failed: false, reachable: false, ping: null, lastProbeAt: 0
|
|
113
|
+
r = { fails: 0, failed: false, reachable: false, ping: null, lastProbeAt: 0 };
|
|
111
114
|
this.#exits.set(ip, r);
|
|
112
115
|
}
|
|
113
116
|
return r;
|
|
@@ -117,7 +120,7 @@ export class ExitProber {
|
|
|
117
120
|
if (!r || !r.lastProbeAt) {
|
|
118
121
|
return { ip, status: r?.failed ? "failed" : "unknown", reachable: false, ping: null, fails: r?.fails ?? 0, checkedAt: null };
|
|
119
122
|
}
|
|
120
|
-
const status = r.failed ? "failed" : r.reachable ? "ok" : r.fails > 0 ? "
|
|
123
|
+
const status = r.failed ? "failed" : r.reachable ? "ok" : r.fails > 0 ? "down" : "unknown";
|
|
121
124
|
return { ip, status, reachable: !r.failed && r.reachable, ping: r.failed ? null : r.ping, fails: r.fails, checkedAt: r.lastProbeAt };
|
|
122
125
|
}
|
|
123
126
|
isFailed(ip) {
|
|
@@ -128,14 +131,15 @@ export class ExitProber {
|
|
|
128
131
|
usable(ips) {
|
|
129
132
|
return ips.some((ip) => !this.isFailed(ip));
|
|
130
133
|
}
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
/** A network-panel poll arrived (?exits=1). True when it is a new visit —
|
|
135
|
+
* `opened` (the UI's first poll after the tab opens) or the first poll
|
|
136
|
+
* after a long gap — and so earns one probe round. Every other poll of
|
|
137
|
+
* the same visit is false: an open panel costs nothing. */
|
|
138
|
+
panelPoll(opened) {
|
|
139
|
+
const now = this.#now();
|
|
140
|
+
const fresh = opened || now - this.#lastPanelPoll > EXIT_PANEL_SESSION_GAP_MS;
|
|
141
|
+
this.#lastPanelPoll = now;
|
|
142
|
+
return fresh;
|
|
139
143
|
}
|
|
140
144
|
#apply(ip, ok, ping, manual) {
|
|
141
145
|
const r = this.#record(ip);
|
|
@@ -146,7 +150,6 @@ export class ExitProber {
|
|
|
146
150
|
r.failed = false;
|
|
147
151
|
r.reachable = true;
|
|
148
152
|
r.ping = ping;
|
|
149
|
-
r.nextProbeAt = now + EXIT_OK_CACHE_MS;
|
|
150
153
|
}
|
|
151
154
|
else {
|
|
152
155
|
r.reachable = false;
|
|
@@ -159,7 +162,6 @@ export class ExitProber {
|
|
|
159
162
|
if (r.fails >= EXIT_MAX_FAILS)
|
|
160
163
|
r.failed = true;
|
|
161
164
|
}
|
|
162
|
-
r.nextProbeAt = r.failed ? Number.MAX_SAFE_INTEGER : now + EXIT_RETRY_BACKOFF_MS[Math.min(r.fails - 1, EXIT_RETRY_BACKOFF_MS.length - 1)];
|
|
163
165
|
}
|
|
164
166
|
this.#save();
|
|
165
167
|
}
|
|
@@ -174,11 +176,11 @@ export class ExitProber {
|
|
|
174
176
|
this.#inFlight.set(ip, p);
|
|
175
177
|
return p;
|
|
176
178
|
}
|
|
177
|
-
/**
|
|
178
|
-
* already being probed
|
|
179
|
+
/** One access: probe every exit in `ips` once — never a failed one, never
|
|
180
|
+
* one already being probed. Resolves when those probes are done. */
|
|
179
181
|
async refresh(ips) {
|
|
180
|
-
const
|
|
181
|
-
await Promise.all(
|
|
182
|
+
const todo = [...new Set(ips)].filter((ip) => !this.#inFlight.has(ip) && !this.isFailed(ip));
|
|
183
|
+
await Promise.all(todo.map((ip) => this.#run(ip, false)));
|
|
182
184
|
}
|
|
183
185
|
/** The Reconnect button: one probe now, whatever the state. Success makes
|
|
184
186
|
* the exit normal again; a failure leaves a failed exit failed. */
|
|
@@ -186,10 +188,14 @@ export class ExitProber {
|
|
|
186
188
|
const running = this.#inFlight.get(ip);
|
|
187
189
|
if (running)
|
|
188
190
|
await running;
|
|
189
|
-
|
|
190
|
-
await this.#run(ip, true);
|
|
191
|
+
await this.#run(ip, true);
|
|
191
192
|
return this.view(ip);
|
|
192
193
|
}
|
|
194
|
+
/** The Refresh button: every exit once, failed ones included (with
|
|
195
|
+
* Reconnect's rule: a failed one that fails again stays failed). */
|
|
196
|
+
async refreshAll(ips) {
|
|
197
|
+
return Promise.all([...new Set(ips)].map((ip) => this.reconnect(ip)));
|
|
198
|
+
}
|
|
193
199
|
get fallback() {
|
|
194
200
|
return this.#fallback;
|
|
195
201
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface BeagleServerOptions {
|
|
|
43
43
|
/** How an exit is probed. Default: a TCP handshake to its :8888. Tests
|
|
44
44
|
* inject a counter here. */
|
|
45
45
|
exitProbe?: ProbeFn;
|
|
46
|
+
/** Clock for the exit prober (tests). Default Date.now. */
|
|
47
|
+
exitNow?: () => number;
|
|
46
48
|
/** The swappable backend, so the UI can show which one is live and drive the
|
|
47
49
|
* LAN handoff. Optional: a caller that pins one backend just omits it and
|
|
48
50
|
* the network panel falls back to "no virtual LAN here". */
|
package/dist/server.js
CHANGED
|
@@ -974,7 +974,48 @@ export function startBeagleServer(opts) {
|
|
|
974
974
|
const exitProber = new ExitProber({
|
|
975
975
|
path: opts.exitStatePath ?? join(dirname(opts.routesPath), "beagle-exit-state.json"),
|
|
976
976
|
probe: opts.exitProbe,
|
|
977
|
+
now: opts.exitNow,
|
|
977
978
|
});
|
|
979
|
+
const readRoutes = () => {
|
|
980
|
+
const none = { regions: [], default: "direct" };
|
|
981
|
+
if (!existsSync(opts.routesPath))
|
|
982
|
+
return none;
|
|
983
|
+
try {
|
|
984
|
+
return yaml.load(readFileSync(opts.routesPath, "utf-8")) ?? none;
|
|
985
|
+
}
|
|
986
|
+
catch {
|
|
987
|
+
return none;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
const knownExitIps = (routes) => {
|
|
991
|
+
const known = new Set(DEFAULT_EXITS.flatMap((e) => (e.virtual_ip ? [e.virtual_ip] : [])));
|
|
992
|
+
for (const r of routes.regions ?? [])
|
|
993
|
+
for (const ip of r.exits ?? [])
|
|
994
|
+
known.add(ip);
|
|
995
|
+
return known;
|
|
996
|
+
};
|
|
997
|
+
const routeTargetOf = (routes) => routes.default && routes.default !== "direct" ? routes.default : null;
|
|
998
|
+
/** Routing goes through an exit: probe that route's exits once (an access),
|
|
999
|
+
* never again on a timer. */
|
|
1000
|
+
const probeRoute = async (routes) => {
|
|
1001
|
+
const target = routeTargetOf(routes);
|
|
1002
|
+
if (!target)
|
|
1003
|
+
return;
|
|
1004
|
+
await exitProber.refresh(exitsForTarget(target, routes.regions ?? [], DEFAULT_EXITS, knownExitIps(routes)));
|
|
1005
|
+
};
|
|
1006
|
+
/** A fallback to direct whose exits work again goes back to its route. */
|
|
1007
|
+
const restoreFallback = (routes) => {
|
|
1008
|
+
const fb = exitProber.fallback;
|
|
1009
|
+
if (!fb)
|
|
1010
|
+
return null;
|
|
1011
|
+
const fbIps = exitsForTarget(fb.from, routes.regions ?? [], DEFAULT_EXITS, knownExitIps(routes));
|
|
1012
|
+
if (!fbIps.some((ip) => exitProber.view(ip).reachable))
|
|
1013
|
+
return null;
|
|
1014
|
+
exitProber.setFallback(null);
|
|
1015
|
+
return (routes.default ?? "direct") === "direct" && writeRoutesDefault(opts.routesPath, fb.from) ? fb.from : null;
|
|
1016
|
+
};
|
|
1017
|
+
// Daemon start with routing through an exit = one access.
|
|
1018
|
+
void probeRoute(readRoutes()).catch(() => undefined);
|
|
978
1019
|
const sendJson = (res, code, body) => {
|
|
979
1020
|
res.writeHead(code, { "content-type": "application/json" });
|
|
980
1021
|
res.end(JSON.stringify(body));
|
|
@@ -2263,16 +2304,16 @@ export function startBeagleServer(opts) {
|
|
|
2263
2304
|
// announcing on the DHT — it does NOT mean IP packets get through (a
|
|
2264
2305
|
// desynced/NAT-blocked exit shows "online" while `ping 10.86.x` times out,
|
|
2265
2306
|
// e.g. GFAX). The proof is a TCP handshake to the exit's CONNECT-proxy
|
|
2266
|
-
// port — but see exit-probe.ts:
|
|
2267
|
-
//
|
|
2268
|
-
//
|
|
2269
|
-
//
|
|
2307
|
+
// port — but see exit-probe.ts: nothing is probed on a timer. Opening
|
|
2308
|
+
// the network panel is one probe round (?exits=1&open=1, or the first
|
|
2309
|
+
// ?exits=1 after a long gap); the panel staying open costs nothing; a
|
|
2310
|
+
// dead exit goes `failed` after three failed accesses and only
|
|
2311
|
+
// Reconnect / Refresh touch it again.
|
|
2270
2312
|
const allNodes = [...byRegion.values()].flat();
|
|
2271
2313
|
const allIps = allNodes.map((n) => n.ip);
|
|
2272
2314
|
const qs = new URL(req.url || "", "http://localhost").searchParams;
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
if (panelOpen || routeTarget)
|
|
2315
|
+
const routeTarget = routeTargetOf(routes);
|
|
2316
|
+
if (qs.get("exits") === "1" && exitProber.panelPoll(qs.get("open") === "1"))
|
|
2276
2317
|
await exitProber.refresh(allIps);
|
|
2277
2318
|
const views = new Map(allIps.map((ip) => [ip, exitProber.view(ip)]));
|
|
2278
2319
|
for (const n of allNodes) {
|
|
@@ -2655,34 +2696,24 @@ export function startBeagleServer(opts) {
|
|
|
2655
2696
|
// dial arbitrary hosts. {all:true} = every failed exit.
|
|
2656
2697
|
if (req.method === "POST" && url === "/api/exits/reconnect") {
|
|
2657
2698
|
const body = (await readBody(req));
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
try {
|
|
2661
|
-
routes = yaml.load(readFileSync(opts.routesPath, "utf-8")) ?? routes;
|
|
2662
|
-
}
|
|
2663
|
-
catch { /* keep direct */ }
|
|
2664
|
-
}
|
|
2665
|
-
const known = new Set(DEFAULT_EXITS.flatMap((e) => (e.virtual_ip ? [e.virtual_ip] : [])));
|
|
2666
|
-
for (const r of routes.regions ?? [])
|
|
2667
|
-
for (const ip of r.exits ?? [])
|
|
2668
|
-
known.add(ip);
|
|
2699
|
+
const routes = readRoutes();
|
|
2700
|
+
const known = knownExitIps(routes);
|
|
2669
2701
|
const targets = body.all
|
|
2670
2702
|
? [...known].filter((ip) => exitProber.isFailed(ip))
|
|
2671
2703
|
: typeof body.ip === "string" && known.has(body.ip) ? [body.ip] : [];
|
|
2672
2704
|
if (!body.all && !targets.length)
|
|
2673
2705
|
return sendJson(res, 400, { ok: false, error: "unknown exit" });
|
|
2674
2706
|
const results = await Promise.all(targets.map((ip) => exitProber.reconnect(ip)));
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
}
|
|
2707
|
+
const restored = restoreFallback(routes);
|
|
2708
|
+
sendJson(res, 200, { ok: true, exits: results, restored, available: exitProber.usable([...known]) });
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
// The panel's Refresh: every known exit once, now — failed ones too.
|
|
2712
|
+
if (req.method === "POST" && url === "/api/exits/refresh") {
|
|
2713
|
+
const routes = readRoutes();
|
|
2714
|
+
const known = knownExitIps(routes);
|
|
2715
|
+
const results = await exitProber.refreshAll([...known]);
|
|
2716
|
+
const restored = restoreFallback(routes);
|
|
2686
2717
|
sendJson(res, 200, { ok: true, exits: results, restored, available: exitProber.usable([...known]) });
|
|
2687
2718
|
return;
|
|
2688
2719
|
}
|
|
@@ -2709,7 +2740,11 @@ export function startBeagleServer(opts) {
|
|
|
2709
2740
|
const body = await readBody(req);
|
|
2710
2741
|
try {
|
|
2711
2742
|
const routes = JSON.parse(body.routes ?? "{}");
|
|
2743
|
+
const before = routeTargetOf(readRoutes());
|
|
2712
2744
|
writeFileSync(opts.routesPath, yaml.dump(routes, { lineWidth: -1 }), "utf-8");
|
|
2745
|
+
// Routing just set to go through an exit = one access of its exits.
|
|
2746
|
+
if (routeTargetOf(routes) && routeTargetOf(routes) !== before)
|
|
2747
|
+
void probeRoute(routes).catch(() => undefined);
|
|
2713
2748
|
sendJson(res, 200, { ok: true });
|
|
2714
2749
|
}
|
|
2715
2750
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.130",
|
|
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",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"start": "node dist/cli.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
29
|
+
"@decentnetwork/beagle-ui": "^0.2.38",
|
|
30
30
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
31
31
|
"@decentnetwork/lan": "^0.1.313",
|
|
32
32
|
"@decentnetwork/peer": "^0.1.170",
|