@decentnetwork/beagle 0.1.117 → 0.1.119
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 +116 -15
- package/dist/discover-cache.d.ts +43 -0
- package/dist/discover-cache.js +90 -0
- package/dist/server.js +24 -36
- 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.119";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -123,6 +123,8 @@ window.__DK_UI_VERSION="0.1.117";
|
|
|
123
123
|
var hostPrefersRtcFile = false;
|
|
124
124
|
var hostHasKeyBackup = false;
|
|
125
125
|
var hostEnsWrite = true;
|
|
126
|
+
var hostWalletBind = true;
|
|
127
|
+
var hostAssistant = true;
|
|
126
128
|
var hostBrandHref = "";
|
|
127
129
|
var hostTabs = null;
|
|
128
130
|
var hostHideApps = null;
|
|
@@ -142,6 +144,8 @@ window.__DK_UI_VERSION="0.1.117";
|
|
|
142
144
|
hostPrefersRtcFile = !!H.hostPrefersRtcFile;
|
|
143
145
|
hostHasKeyBackup = !!H.hostHasKeyBackup;
|
|
144
146
|
hostEnsWrite = H.hostEnsWrite !== void 0 ? !!H.hostEnsWrite : !H.hostNeedsWelcome;
|
|
147
|
+
hostWalletBind = H.hostWalletBind !== void 0 ? !!H.hostWalletBind : hostEnsWrite;
|
|
148
|
+
hostAssistant = H.hostAssistant !== void 0 ? !!H.hostAssistant : true;
|
|
145
149
|
hostBrandHref = typeof H.hostBrandHref === "string" ? H.hostBrandHref : "";
|
|
146
150
|
}
|
|
147
151
|
function fileUrl(name) {
|
|
@@ -1340,7 +1344,9 @@ window.__DK_UI_VERSION="0.1.117";
|
|
|
1340
1344
|
userId: ASSISTANT_ID,
|
|
1341
1345
|
alias: "Beagle Assistant",
|
|
1342
1346
|
address: "",
|
|
1343
|
-
online:
|
|
1347
|
+
// Not a peer, so not "online": there is no session, nothing to be present
|
|
1348
|
+
// on, and no dot is drawn for an `app` row anywhere in the UI.
|
|
1349
|
+
online: false,
|
|
1344
1350
|
app: true,
|
|
1345
1351
|
agent: false,
|
|
1346
1352
|
pending: false,
|
|
@@ -1782,7 +1788,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
|
1782
1788
|
textOverflow: "ellipsis",
|
|
1783
1789
|
flex: 1,
|
|
1784
1790
|
minWidth: 0
|
|
1785
|
-
} }, dkRowPreview(row.preview)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, row.lastTime), row.unread ? /* @__PURE__ */ React.createElement(Unread, { n: row.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
1791
|
+
} }, dkRowPreview(row.preview)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, row.lastTime), peer.app ? null : row.unread ? /* @__PURE__ */ React.createElement(Unread, { n: row.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
1786
1792
|
}
|
|
1787
1793
|
function dkActivityTs(p, isProtocol, fix) {
|
|
1788
1794
|
const row = rosterRow(p, isProtocol || (() => false), fix);
|
|
@@ -3482,6 +3488,65 @@ ${peer.address}`
|
|
|
3482
3488
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed, muted, isProtocol, protocolFix }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, peers, onOpenChat: onSelect, thread, isProtocol, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread, hasEarlier, onLoadEarlier, onAssistantRemove, onAssistantAdopt, muted: !!muted && muted.includes(peer.id), onToggleMute }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
|
|
3483
3489
|
}
|
|
3484
3490
|
Object.assign(window, { ChatTab });
|
|
3491
|
+
var PROOF_KEY = { eth: "proof.eth", sol: "proof.sol" };
|
|
3492
|
+
var HEAD = "Beagle wallet binding";
|
|
3493
|
+
var LINE = "I bind this wallet to my Beagle identity.";
|
|
3494
|
+
var ADDRESS_RE = { eth: /^0x[0-9a-fA-F]{40}$/, sol: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/ };
|
|
3495
|
+
var USERID_RE = /^[1-9A-HJ-NP-Za-km-z]{32,48}$/;
|
|
3496
|
+
var ISSUED_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
3497
|
+
function isWalletAddress(chain, address) {
|
|
3498
|
+
const re = ADDRESS_RE[chain];
|
|
3499
|
+
return !!re && typeof address === "string" && re.test(address);
|
|
3500
|
+
}
|
|
3501
|
+
function proofTime(date = /* @__PURE__ */ new Date()) {
|
|
3502
|
+
return new Date(Math.floor(date.getTime() / 1e3) * 1e3).toISOString().replace(".000Z", "Z");
|
|
3503
|
+
}
|
|
3504
|
+
function walletProofMessage({ userid, chain, address, issued }) {
|
|
3505
|
+
if (!USERID_RE.test(String(userid || "")))
|
|
3506
|
+
throw new Error("wallet proof: bad userid");
|
|
3507
|
+
if (!isWalletAddress(chain, address))
|
|
3508
|
+
throw new Error("wallet proof: bad chain or address");
|
|
3509
|
+
if (!ISSUED_RE.test(String(issued || "")))
|
|
3510
|
+
throw new Error("wallet proof: bad issued time");
|
|
3511
|
+
return [HEAD, "", LINE, "", `userid: ${userid}`, `chain: ${chain}`, `address: ${address}`, `issued: ${issued}`].join("\n");
|
|
3512
|
+
}
|
|
3513
|
+
var B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
3514
|
+
function base58Encode(bytes) {
|
|
3515
|
+
const digits = [];
|
|
3516
|
+
for (const b of bytes) {
|
|
3517
|
+
let carry = b;
|
|
3518
|
+
for (let j = 0; j < digits.length; j++) {
|
|
3519
|
+
carry += digits[j] << 8;
|
|
3520
|
+
digits[j] = carry % 58;
|
|
3521
|
+
carry = carry / 58 | 0;
|
|
3522
|
+
}
|
|
3523
|
+
while (carry > 0) {
|
|
3524
|
+
digits.push(carry % 58);
|
|
3525
|
+
carry = carry / 58 | 0;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
let out = "";
|
|
3529
|
+
for (const b of bytes) {
|
|
3530
|
+
if (b !== 0)
|
|
3531
|
+
break;
|
|
3532
|
+
out += "1";
|
|
3533
|
+
}
|
|
3534
|
+
for (let i = digits.length - 1; i >= 0; i--)
|
|
3535
|
+
out += B58[digits[i]];
|
|
3536
|
+
return out;
|
|
3537
|
+
}
|
|
3538
|
+
var utf8Hex = (s) => "0x" + Array.from(new TextEncoder().encode(s), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3539
|
+
async function signEthProof(provider, { userid, address, issued = proofTime() }) {
|
|
3540
|
+
const message = walletProofMessage({ userid, chain: "eth", address, issued });
|
|
3541
|
+
const signature = await provider.request({ method: "personal_sign", params: [utf8Hex(message), address] });
|
|
3542
|
+
return { message, signature: String(signature) };
|
|
3543
|
+
}
|
|
3544
|
+
async function signSolProof(wallet, { userid, address, issued = proofTime() }) {
|
|
3545
|
+
const message = walletProofMessage({ userid, chain: "sol", address, issued });
|
|
3546
|
+
const r = await wallet.signMessage(new TextEncoder().encode(message), "utf8");
|
|
3547
|
+
const sig = r && r.signature ? r.signature : r;
|
|
3548
|
+
return { message, signature: base58Encode(Uint8Array.from(sig)) };
|
|
3549
|
+
}
|
|
3485
3550
|
function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
|
|
3486
3551
|
const [status, setStatus] = React.useState(null);
|
|
3487
3552
|
const timer = React.useRef(null);
|
|
@@ -3895,7 +3960,7 @@ ${peer.address}`
|
|
|
3895
3960
|
const [msg, setMsg] = React.useState(null);
|
|
3896
3961
|
const reload = () => {
|
|
3897
3962
|
fetch("/api/ens-name").then((r) => r.json()).then((d) => {
|
|
3898
|
-
setSt({ loading: false, registered: !!d.registered, record: d.record || null, mineOwned: !!d.mineOwned });
|
|
3963
|
+
setSt({ loading: false, registered: !!d.registered, record: d.record || null, mineOwned: !!d.mineOwned, writeBlocked: d.writeBlocked || "" });
|
|
3899
3964
|
if (d.registered && d.record && d.record.name)
|
|
3900
3965
|
setLabel(d.record.name.replace(/\.beagles\.eth$/i, ""));
|
|
3901
3966
|
if (onRecord)
|
|
@@ -3922,7 +3987,15 @@ ${peer.address}`
|
|
|
3922
3987
|
if (l)
|
|
3923
3988
|
post("/api/ens-register", { name: l, displayName: me.name, description: me.description || "" });
|
|
3924
3989
|
};
|
|
3925
|
-
const bind = (chain, address) => post("/api/ens-bind-wallet", { chain, address });
|
|
3990
|
+
const bind = (chain, address, proof) => post("/api/ens-bind-wallet", proof ? { chain, address, proof } : { chain, address });
|
|
3991
|
+
const signing = () => {
|
|
3992
|
+
setBusy(true);
|
|
3993
|
+
setMsg({ tone: "ok", text: T.ensSigning || "approve the signature in your wallet\u2026" });
|
|
3994
|
+
};
|
|
3995
|
+
const walletErr = (e) => {
|
|
3996
|
+
setBusy(false);
|
|
3997
|
+
setMsg({ tone: "err", text: String(e && e.message || e) });
|
|
3998
|
+
};
|
|
3926
3999
|
const bindEth = async () => {
|
|
3927
4000
|
const eth = window.ethereum;
|
|
3928
4001
|
if (!eth) {
|
|
@@ -3931,10 +4004,13 @@ ${peer.address}`
|
|
|
3931
4004
|
}
|
|
3932
4005
|
try {
|
|
3933
4006
|
const a = await eth.request({ method: "eth_requestAccounts" });
|
|
3934
|
-
if (a
|
|
3935
|
-
|
|
4007
|
+
if (!a || !a[0])
|
|
4008
|
+
return;
|
|
4009
|
+
signing();
|
|
4010
|
+
const proof = await signEthProof(eth, { userid: me.userId, address: a[0] });
|
|
4011
|
+
bind("eth", a[0], proof);
|
|
3936
4012
|
} catch (e) {
|
|
3937
|
-
|
|
4013
|
+
walletErr(e);
|
|
3938
4014
|
}
|
|
3939
4015
|
};
|
|
3940
4016
|
const bindSol = async () => {
|
|
@@ -3946,10 +4022,13 @@ ${peer.address}`
|
|
|
3946
4022
|
try {
|
|
3947
4023
|
const r = await sol.connect();
|
|
3948
4024
|
const a = r && r.publicKey && r.publicKey.toString();
|
|
3949
|
-
if (a)
|
|
3950
|
-
|
|
4025
|
+
if (!a)
|
|
4026
|
+
return;
|
|
4027
|
+
signing();
|
|
4028
|
+
const proof = await signSolProof(sol, { userid: me.userId, address: a });
|
|
4029
|
+
bind("sol", a, proof);
|
|
3951
4030
|
} catch (e) {
|
|
3952
|
-
|
|
4031
|
+
walletErr(e);
|
|
3953
4032
|
}
|
|
3954
4033
|
};
|
|
3955
4034
|
const field = { flex: 1, minWidth: 0, height: 34, borderRadius: 8, border: "1px solid var(--line)", background: "var(--panel-2)", color: "var(--text)", fontFamily: "var(--mono)", fontSize: 13, padding: "0 10px", outline: "none" };
|
|
@@ -3998,6 +4077,7 @@ ${peer.address}`
|
|
|
3998
4077
|
const [picking, setPicking] = React.useState(false);
|
|
3999
4078
|
const fileRef = React.useRef(null);
|
|
4000
4079
|
const canWrite = hostEnsWrite && !!st.mineOwned;
|
|
4080
|
+
const canBind = hostWalletBind && !!st.mineOwned && !st.writeBlocked;
|
|
4001
4081
|
const rec = st.record;
|
|
4002
4082
|
const boundEth = rec && rec.addresses && rec.addresses["60"];
|
|
4003
4083
|
const boundSol = rec && rec.addresses && rec.addresses["501"];
|
|
@@ -4020,7 +4100,16 @@ ${peer.address}`
|
|
|
4020
4100
|
setMsg({ tone: "err", text: m === "too-big" ? T.ensTooBig || "image too large even after scaling" : m === "bad-image" ? T.ensBadImage || "cannot read that image" : String(m || err) });
|
|
4021
4101
|
});
|
|
4022
4102
|
};
|
|
4023
|
-
const
|
|
4103
|
+
const proven = (chain, bound) => {
|
|
4104
|
+
if (rec && rec.owner && String(rec.owner).toLowerCase() === String(bound).toLowerCase())
|
|
4105
|
+
return true;
|
|
4106
|
+
try {
|
|
4107
|
+
return JSON.parse((rec && rec.texts || {})[PROOF_KEY[chain]]).msg.indexOf("address: " + bound + "\n") >= 0;
|
|
4108
|
+
} catch {
|
|
4109
|
+
return false;
|
|
4110
|
+
}
|
|
4111
|
+
};
|
|
4112
|
+
const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { className: "dk-row", style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, lbl), bound ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 11, color: proven(chain, bound) ? "var(--online)" : "var(--faint)" } }, proven(chain, bound) ? T.ensProven || "signed by this wallet" : T.ensUnproven || "no wallet signature yet")), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), !proven(chain, bound) && /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canBind, onClick: onBind }, T.ensProve || "sign"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canBind, onClick: () => bind(chain, "") }, T.ensUnbind || "unbind")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotBound || "not bound"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canBind, onClick: onBind }, T.ensBind || "bind")));
|
|
4024
4113
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { label: T.ensCard || "Name \xB7 beagles.eth" }, st.loading ? /* @__PURE__ */ React.createElement("div", { style: { padding: "14px 16px", fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : /* @__PURE__ */ React.createElement(React.Fragment, null, !hostEnsWrite ? /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), st.registered && rec && rec.name ? /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 13, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, rec.name) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12, lineHeight: 1.5, color: "var(--faint)" } }, T.ensWebOnly || "Registering a beagles.eth name needs the Beagle phone or desktop app. Your avatar can be set above without one.")) : /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensName || "name"), /* @__PURE__ */ React.createElement(
|
|
4025
4114
|
"input",
|
|
4026
4115
|
{
|
|
@@ -4034,7 +4123,7 @@ ${peer.address}`
|
|
|
4034
4123
|
},
|
|
4035
4124
|
style: { ...field, minWidth: 110 }
|
|
4036
4125
|
}
|
|
4037
|
-
), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
|
|
4126
|
+
), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--dim)", flexShrink: 0 } }, ".beagles.eth"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !label.trim(), onClick: register }, busy ? "\u2026" : st.registered && st.mineOwned ? T.ensUpdate || "update" : T.ensRegister || "register")), st.registered && !st.mineOwned && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: "1px solid var(--line)", fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, (T.ensWalletOwned || "registered via your mobile wallet as") + " ", /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)" } }, rec && rec.name)), st.registered && /* @__PURE__ */ React.createElement("div", { className: "dk-row dk-wrap", style: row }, /* @__PURE__ */ React.createElement("span", { className: "dk-lbl", style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, T.ensAvatar || "avatar"), upAvatar ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 34, height: 34, style: { width: 34, height: 34, borderRadius: 9, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" } }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--text)" } }, T.ensCustom || "custom image"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : punkId != null ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 34, radius: 9, fallbackSeed: me.userId }), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, `CryptoPunk #${punkId}`), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !canWrite, onClick: () => post("/api/ens-avatar", { nftid: null }) }, T.ensClear || "clear")) : /* @__PURE__ */ React.createElement("span", { style: { flex: 1, fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)" } }, T.ensNotSet || "not set"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !canWrite, onClick: () => setPicking(true) }, T.ensChoose || "choose"), /* @__PURE__ */ React.createElement("input", { ref: fileRef, type: "file", accept: "image/*", onChange: onUploadFile, style: { display: "none" } })), walletRow(T.ensEth || "ethereum", "eth", boundEth, bindEth, false), walletRow(T.ensSol || "solana", "sol", boundSol, bindSol, !msg && !st.writeBlocked), st.writeBlocked && hostWalletBind && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", borderBottom: msg ? "1px solid var(--line)" : "none", fontFamily: "var(--ui)", fontSize: 12, lineHeight: 1.5, color: "var(--faint)" } }, st.writeBlocked), msg && /* @__PURE__ */ React.createElement("div", { style: { padding: "10px 16px", fontFamily: "var(--ui)", fontSize: 12, color: msg.tone === "ok" ? "var(--online)" : "var(--danger)" } }, msg.text)), picking && /* @__PURE__ */ React.createElement(DkPunkPicker, { T, onPick: pickPunk, onClose: () => setPicking(false) })), !st.loading && st.registered && /* @__PURE__ */ React.createElement(Card, { label: T.linksCard || "Links" }, SOCIALS.map((s, i) => socialRow(s, i === SOCIALS.length - 1))));
|
|
4038
4127
|
}
|
|
4039
4128
|
function DkUpdateCheck({ T }) {
|
|
4040
4129
|
const [st, setSt] = React.useState("idle");
|
|
@@ -4387,6 +4476,8 @@ ${peer.address}`
|
|
|
4387
4476
|
}
|
|
4388
4477
|
function DkAvatar({ peer, size = 30, radius = 7, dot = true }) {
|
|
4389
4478
|
const d = Math.max(8, size * 0.3);
|
|
4479
|
+
if (peer && peer.app)
|
|
4480
|
+
dot = false;
|
|
4390
4481
|
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: {
|
|
4391
4482
|
position: "absolute",
|
|
4392
4483
|
right: -2,
|
|
@@ -6132,6 +6223,8 @@ ${peer.address}`
|
|
|
6132
6223
|
return;
|
|
6133
6224
|
const byId = /* @__PURE__ */ new Map();
|
|
6134
6225
|
for (const p of peers || []) {
|
|
6226
|
+
if (p && p.app)
|
|
6227
|
+
continue;
|
|
6135
6228
|
const dir = p.lastDir || (String(p.lastMsg || "").startsWith("you: ") ? "out" : "in");
|
|
6136
6229
|
const quiet = typeof isProtocol === "function" && lastIsProtocol(p, isProtocol);
|
|
6137
6230
|
byId.set(p.id, { ts: p.lastTs || 0, dir, quiet });
|
|
@@ -6408,6 +6501,10 @@ ${peer.address}`
|
|
|
6408
6501
|
ensUnbind: "unbind",
|
|
6409
6502
|
ensNotBound: "not bound",
|
|
6410
6503
|
ensWebOnly: "Registering a beagles.eth name needs the Beagle phone or desktop app. Your avatar can be set above without one.",
|
|
6504
|
+
ensProven: "signed by this wallet",
|
|
6505
|
+
ensUnproven: "no wallet signature yet",
|
|
6506
|
+
ensProve: "sign",
|
|
6507
|
+
ensSigning: "approve the signature in your wallet\u2026",
|
|
6411
6508
|
ensAvatar: "avatar",
|
|
6412
6509
|
ensChoose: "choose",
|
|
6413
6510
|
ensClear: "clear",
|
|
@@ -6603,6 +6700,10 @@ ${peer.address}`
|
|
|
6603
6700
|
ensUnbind: "\u89E3\u7ED1",
|
|
6604
6701
|
ensNotBound: "\u672A\u7ED1\u5B9A",
|
|
6605
6702
|
ensWebOnly: "\u6CE8\u518C beagles.eth \u540D\u5B57\u9700\u8981 Beagle \u624B\u673A\u6216\u684C\u9762\u5E94\u7528\u3002\u5934\u50CF\u4E0D\u9700\u8981\u540D\u5B57\uFF0C\u53EF\u5728\u4E0A\u65B9\u76F4\u63A5\u8BBE\u7F6E\u3002",
|
|
6703
|
+
ensProven: "\u5DF2\u7531\u8BE5\u94B1\u5305\u7B7E\u540D",
|
|
6704
|
+
ensUnproven: "\u5C1A\u65E0\u94B1\u5305\u7B7E\u540D",
|
|
6705
|
+
ensProve: "\u7B7E\u540D",
|
|
6706
|
+
ensSigning: "\u8BF7\u5728\u94B1\u5305\u4E2D\u786E\u8BA4\u7B7E\u540D\u2026",
|
|
6606
6707
|
ensAvatar: "\u5934\u50CF",
|
|
6607
6708
|
ensChoose: "\u9009\u62E9",
|
|
6608
6709
|
ensClear: "\u6E05\u9664",
|
|
@@ -6937,10 +7038,10 @@ ${peer.address}`
|
|
|
6937
7038
|
window.location.reload();
|
|
6938
7039
|
}, [me && me.beagleVer]);
|
|
6939
7040
|
const [assistantTs, setAssistantTs] = React.useState(loadAssistantTs);
|
|
6940
|
-
const peers = React.useMemo(() => (data.peers || []).concat([Object.assign({}, ASSISTANT_PEER, {
|
|
7041
|
+
const peers = React.useMemo(() => (data.peers || []).concat(hostAssistant ? [Object.assign({}, ASSISTANT_PEER, {
|
|
6941
7042
|
lastTs: assistantTs,
|
|
6942
7043
|
lastTime: assistantTs ? dkClock(assistantTs) : ""
|
|
6943
|
-
})]), [data.peers, assistantTs]);
|
|
7044
|
+
})] : []), [data.peers, assistantTs]);
|
|
6944
7045
|
const [pendingAddr, setPendingAddr] = React.useState(() => dkParseDeepLinkAddress());
|
|
6945
7046
|
React.useEffect(() => {
|
|
6946
7047
|
const onHash = () => {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached fetches for the discovery directories (the leaderboard and the
|
|
3
|
+
* beagles.eth /names listing).
|
|
4
|
+
*
|
|
5
|
+
* This used to cache successes only. When the gateway was down every UI poll —
|
|
6
|
+
* one every 2.5 s, per open tab, per machine — started a fresh request, so the
|
|
7
|
+
* upstream got hammered exactly when it was least able to answer. On
|
|
8
|
+
* 2026-09-28 the beagle servers on one home network were about 82% of the
|
|
9
|
+
* gateway's traffic and the day's total reached 139k requests against a 100k
|
|
10
|
+
* free limit. A failing upstream must cost LESS traffic than a working one,
|
|
11
|
+
* not more.
|
|
12
|
+
*
|
|
13
|
+
* Three rules, and they are the whole module:
|
|
14
|
+
* • a success is cached for `ttlMs` (5 minutes),
|
|
15
|
+
* • a failure is remembered for `failTtlMs` (60 s) and nothing is refetched
|
|
16
|
+
* inside that window — a stale body is served if we have one, because a
|
|
17
|
+
* five-minute-old directory listing is worth more than an error,
|
|
18
|
+
* • one in-flight request per URL, shared by every caller, so the poll that
|
|
19
|
+
* arrives while a fetch is running joins it instead of starting another.
|
|
20
|
+
*/
|
|
21
|
+
export interface DiscoverFetchOptions {
|
|
22
|
+
/** How long a good answer stays good. */
|
|
23
|
+
ttlMs?: number;
|
|
24
|
+
/** How long a failure suppresses refetching. */
|
|
25
|
+
failTtlMs?: number;
|
|
26
|
+
/** Abort a request that takes longer than this. */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** Injected for tests. */
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
now?: () => number;
|
|
31
|
+
}
|
|
32
|
+
export declare const DISCOVER_TTL_MS: number;
|
|
33
|
+
export declare const DISCOVER_FAIL_TTL_MS = 60000;
|
|
34
|
+
export interface DiscoverFetch {
|
|
35
|
+
(url: string): Promise<unknown>;
|
|
36
|
+
/** Forget everything about this URL: the next call refetches. Used when we
|
|
37
|
+
* KNOW the upstream changed (we just wrote a record), so the Names tab
|
|
38
|
+
* reflects it now instead of in five minutes. A request already in flight
|
|
39
|
+
* when this is called carries pre-change data, so its result is discarded
|
|
40
|
+
* rather than cached. */
|
|
41
|
+
invalidate(url: string): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createDiscoverFetch(opts?: DiscoverFetchOptions): DiscoverFetch;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached fetches for the discovery directories (the leaderboard and the
|
|
3
|
+
* beagles.eth /names listing).
|
|
4
|
+
*
|
|
5
|
+
* This used to cache successes only. When the gateway was down every UI poll —
|
|
6
|
+
* one every 2.5 s, per open tab, per machine — started a fresh request, so the
|
|
7
|
+
* upstream got hammered exactly when it was least able to answer. On
|
|
8
|
+
* 2026-09-28 the beagle servers on one home network were about 82% of the
|
|
9
|
+
* gateway's traffic and the day's total reached 139k requests against a 100k
|
|
10
|
+
* free limit. A failing upstream must cost LESS traffic than a working one,
|
|
11
|
+
* not more.
|
|
12
|
+
*
|
|
13
|
+
* Three rules, and they are the whole module:
|
|
14
|
+
* • a success is cached for `ttlMs` (5 minutes),
|
|
15
|
+
* • a failure is remembered for `failTtlMs` (60 s) and nothing is refetched
|
|
16
|
+
* inside that window — a stale body is served if we have one, because a
|
|
17
|
+
* five-minute-old directory listing is worth more than an error,
|
|
18
|
+
* • one in-flight request per URL, shared by every caller, so the poll that
|
|
19
|
+
* arrives while a fetch is running joins it instead of starting another.
|
|
20
|
+
*/
|
|
21
|
+
export const DISCOVER_TTL_MS = 5 * 60_000;
|
|
22
|
+
export const DISCOVER_FAIL_TTL_MS = 60_000;
|
|
23
|
+
export function createDiscoverFetch(opts = {}) {
|
|
24
|
+
const ttlMs = opts.ttlMs ?? DISCOVER_TTL_MS;
|
|
25
|
+
const failTtlMs = opts.failTtlMs ?? DISCOVER_FAIL_TTL_MS;
|
|
26
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
27
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
28
|
+
const now = opts.now ?? Date.now;
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
const inFlight = new Map();
|
|
31
|
+
const generation = new Map();
|
|
32
|
+
const fetchDiscoverJson = async function fetchDiscoverJson(url) {
|
|
33
|
+
const hit = cache.get(url);
|
|
34
|
+
if (hit && hit.at !== undefined && now() - hit.at < ttlMs)
|
|
35
|
+
return hit.body;
|
|
36
|
+
// Someone is already asking. Join them rather than making it worse — under
|
|
37
|
+
// a slow upstream this is the difference between one request and one per
|
|
38
|
+
// poll per tab.
|
|
39
|
+
const running = inFlight.get(url);
|
|
40
|
+
if (running)
|
|
41
|
+
return running;
|
|
42
|
+
if (hit && hit.failedAt !== undefined && now() - hit.failedAt < failTtlMs) {
|
|
43
|
+
// Inside the failure window nothing is fetched. Stale beats nothing;
|
|
44
|
+
// with nothing to serve, the caller gets the error it would have got.
|
|
45
|
+
if (hit.at !== undefined)
|
|
46
|
+
return hit.body;
|
|
47
|
+
throw hit.error ?? new Error("upstream unavailable");
|
|
48
|
+
}
|
|
49
|
+
const gen = generation.get(url) ?? 0;
|
|
50
|
+
const attempt = (async () => {
|
|
51
|
+
const ctl = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
const r = await doFetch(url, { signal: ctl.signal });
|
|
55
|
+
if (!r.ok)
|
|
56
|
+
throw new Error(`upstream ${r.status}`);
|
|
57
|
+
const body = await r.json();
|
|
58
|
+
if ((generation.get(url) ?? 0) === gen)
|
|
59
|
+
cache.set(url, { body, at: now() });
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
64
|
+
const prev = cache.get(url);
|
|
65
|
+
// Keep whatever good body we had: it is what the next 60 s will serve.
|
|
66
|
+
if ((generation.get(url) ?? 0) === gen) {
|
|
67
|
+
cache.set(url, { body: prev?.body, at: prev?.at, failedAt: now(), error: err });
|
|
68
|
+
}
|
|
69
|
+
if (prev && prev.at !== undefined)
|
|
70
|
+
return prev.body;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
// After an invalidate the entry is already gone and a newer request
|
|
76
|
+
// may own the slot; the generation says which of us this is.
|
|
77
|
+
if ((generation.get(url) ?? 0) === gen)
|
|
78
|
+
inFlight.delete(url);
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
inFlight.set(url, attempt);
|
|
82
|
+
return attempt;
|
|
83
|
+
};
|
|
84
|
+
fetchDiscoverJson.invalidate = (url) => {
|
|
85
|
+
cache.delete(url);
|
|
86
|
+
inFlight.delete(url);
|
|
87
|
+
generation.set(url, (generation.get(url) ?? 0) + 1);
|
|
88
|
+
};
|
|
89
|
+
return fetchDiscoverJson;
|
|
90
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -25,6 +25,8 @@ import * as peerAddr from "@decentnetwork/peer";
|
|
|
25
25
|
import { bridgeRoster } from "./bridge-roster.js";
|
|
26
26
|
import { isSupervised } from "./relaunch.js";
|
|
27
27
|
import { historyStats } from "./assistant-stats.js";
|
|
28
|
+
import { createDiscoverFetch } from "./discover-cache.js";
|
|
29
|
+
import { applyWalletBind } from "@decentnetwork/beagle-ui/wallet-proof";
|
|
28
30
|
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
29
31
|
// scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
|
|
30
32
|
const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
@@ -33,28 +35,10 @@ const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
|
33
35
|
// (which probes all exits on each poll) stays responsive over a lossy mesh.
|
|
34
36
|
const EXIT_PROBE_PORT = 8888;
|
|
35
37
|
const EXIT_PROBE_TIMEOUT_MS = 2000;
|
|
36
|
-
/** Server-side fetch for the discovery directories (leaderboard / ENS names)
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
async function fetchDiscoverJson(url) {
|
|
41
|
-
const hit = discoverCache.get(url);
|
|
42
|
-
if (hit && Date.now() - hit.at < DISCOVER_CACHE_TTL_MS)
|
|
43
|
-
return hit.body;
|
|
44
|
-
const ctl = new AbortController();
|
|
45
|
-
const timer = setTimeout(() => ctl.abort(), 10_000);
|
|
46
|
-
try {
|
|
47
|
-
const r = await fetch(url, { signal: ctl.signal });
|
|
48
|
-
if (!r.ok)
|
|
49
|
-
throw new Error(`upstream ${r.status}`);
|
|
50
|
-
const body = await r.json();
|
|
51
|
-
discoverCache.set(url, { at: Date.now(), body });
|
|
52
|
-
return body;
|
|
53
|
-
}
|
|
54
|
-
finally {
|
|
55
|
-
clearTimeout(timer);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
38
|
+
/** Server-side fetch for the discovery directories (leaderboard / ENS names).
|
|
39
|
+
* Successes are cached, FAILURES ARE TOO, and concurrent callers share one
|
|
40
|
+
* request — see discover-cache.ts for the outage this comes from. */
|
|
41
|
+
const fetchDiscoverJson = createDiscoverFetch();
|
|
58
42
|
// ── beagles.eth names (ens-gateway worker) ──────────────────────────────────
|
|
59
43
|
// The same registry the mobile apps register into. Web-side registration
|
|
60
44
|
// signs the record with THIS node's Carrier identity (XEdDSA via the backend
|
|
@@ -190,7 +174,7 @@ async function ensSignAndSet(call, record) {
|
|
|
190
174
|
}
|
|
191
175
|
// The record changed — drop the cached /names listing so the Names tab
|
|
192
176
|
// reflects it immediately instead of after the 5-min TTL.
|
|
193
|
-
|
|
177
|
+
fetchDiscoverJson.invalidate(`${ENS_GATEWAY}/names`);
|
|
194
178
|
return { ok: true };
|
|
195
179
|
}
|
|
196
180
|
catch (e) {
|
|
@@ -1371,13 +1355,11 @@ export function startBeagleServer(opts) {
|
|
|
1371
1355
|
}
|
|
1372
1356
|
if (req.method === "POST" && url === "/api/ens-bind-wallet") {
|
|
1373
1357
|
const body = await readBody(req);
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
if (!coinType || !okAddr) {
|
|
1380
|
-
sendJson(res, 400, { ok: false, error: "invalid chain or address" });
|
|
1358
|
+
// Format check up front, before any identity or gateway round trip;
|
|
1359
|
+
// applyWalletBind repeats it with the record in hand.
|
|
1360
|
+
const pre = applyWalletBind(null, { chain: String(body.chain ?? ""), address: body.address, userid: "" });
|
|
1361
|
+
if (!pre.ok) {
|
|
1362
|
+
sendJson(res, pre.status, { ok: false, error: pre.error });
|
|
1381
1363
|
return;
|
|
1382
1364
|
}
|
|
1383
1365
|
const diag = await opts.call({ op: "diag" });
|
|
@@ -1395,16 +1377,22 @@ export function startBeagleServer(opts) {
|
|
|
1395
1377
|
sendJson(res, 403, { ok: false, error: "this name was registered by your mobile wallet — bind addresses there" });
|
|
1396
1378
|
return;
|
|
1397
1379
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1380
|
+
// The wallet's signed proof (wallet-proof.js) is checked here and
|
|
1381
|
+
// stored as texts["proof.eth"|"proof.sol"] beside the address.
|
|
1382
|
+
const bound = applyWalletBind(mine, {
|
|
1383
|
+
chain: String(body.chain), address: body.address, userid: identity.userid,
|
|
1384
|
+
proof: (body.proof ?? null),
|
|
1385
|
+
});
|
|
1386
|
+
if (!bound.ok) {
|
|
1387
|
+
sendJson(res, bound.status, { ok: false, error: bound.error });
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const { addresses, texts } = bound;
|
|
1403
1391
|
const r = await ensSignAndSet(opts.call, {
|
|
1404
1392
|
name: mine.name,
|
|
1405
1393
|
owner: identity.userid,
|
|
1406
1394
|
addresses,
|
|
1407
|
-
texts
|
|
1395
|
+
texts,
|
|
1408
1396
|
referee: mine.referee ?? "",
|
|
1409
1397
|
nft: mine.nft ?? "",
|
|
1410
1398
|
nftid: mine.nftid ?? 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.119",
|
|
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,6 +26,7 @@
|
|
|
26
26
|
"start": "node dist/cli.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@decentnetwork/beagle-ui": "^0.2.33",
|
|
29
30
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
30
31
|
"@decentnetwork/lan": "^0.1.283",
|
|
31
32
|
"@decentnetwork/peer": "^0.1.168",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"yargs": "^17.7.2"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@decentnetwork/beagle-ui": "^0.2.31",
|
|
39
39
|
"@types/js-yaml": "^4.0.9",
|
|
40
40
|
"@types/node": "^20.11.0",
|
|
41
41
|
"@types/yargs": "^17.0.32",
|