@decentnetwork/beagle 0.1.118 → 0.1.120
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 +108 -13
- package/dist/embedded-host.js +14 -0
- package/dist/ipc.d.ts +1 -1
- package/dist/server.js +29 -13
- 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.120";
|
|
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.118";
|
|
|
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.118";
|
|
|
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) {
|
|
@@ -3484,6 +3488,65 @@ ${peer.address}`
|
|
|
3484
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 }));
|
|
3485
3489
|
}
|
|
3486
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
|
+
}
|
|
3487
3550
|
function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
|
|
3488
3551
|
const [status, setStatus] = React.useState(null);
|
|
3489
3552
|
const timer = React.useRef(null);
|
|
@@ -3897,7 +3960,7 @@ ${peer.address}`
|
|
|
3897
3960
|
const [msg, setMsg] = React.useState(null);
|
|
3898
3961
|
const reload = () => {
|
|
3899
3962
|
fetch("/api/ens-name").then((r) => r.json()).then((d) => {
|
|
3900
|
-
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 || "" });
|
|
3901
3964
|
if (d.registered && d.record && d.record.name)
|
|
3902
3965
|
setLabel(d.record.name.replace(/\.beagles\.eth$/i, ""));
|
|
3903
3966
|
if (onRecord)
|
|
@@ -3924,7 +3987,15 @@ ${peer.address}`
|
|
|
3924
3987
|
if (l)
|
|
3925
3988
|
post("/api/ens-register", { name: l, displayName: me.name, description: me.description || "" });
|
|
3926
3989
|
};
|
|
3927
|
-
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
|
+
};
|
|
3928
3999
|
const bindEth = async () => {
|
|
3929
4000
|
const eth = window.ethereum;
|
|
3930
4001
|
if (!eth) {
|
|
@@ -3933,10 +4004,13 @@ ${peer.address}`
|
|
|
3933
4004
|
}
|
|
3934
4005
|
try {
|
|
3935
4006
|
const a = await eth.request({ method: "eth_requestAccounts" });
|
|
3936
|
-
if (a
|
|
3937
|
-
|
|
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);
|
|
3938
4012
|
} catch (e) {
|
|
3939
|
-
|
|
4013
|
+
walletErr(e);
|
|
3940
4014
|
}
|
|
3941
4015
|
};
|
|
3942
4016
|
const bindSol = async () => {
|
|
@@ -3948,10 +4022,13 @@ ${peer.address}`
|
|
|
3948
4022
|
try {
|
|
3949
4023
|
const r = await sol.connect();
|
|
3950
4024
|
const a = r && r.publicKey && r.publicKey.toString();
|
|
3951
|
-
if (a)
|
|
3952
|
-
|
|
4025
|
+
if (!a)
|
|
4026
|
+
return;
|
|
4027
|
+
signing();
|
|
4028
|
+
const proof = await signSolProof(sol, { userid: me.userId, address: a });
|
|
4029
|
+
bind("sol", a, proof);
|
|
3953
4030
|
} catch (e) {
|
|
3954
|
-
|
|
4031
|
+
walletErr(e);
|
|
3955
4032
|
}
|
|
3956
4033
|
};
|
|
3957
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" };
|
|
@@ -4000,6 +4077,7 @@ ${peer.address}`
|
|
|
4000
4077
|
const [picking, setPicking] = React.useState(false);
|
|
4001
4078
|
const fileRef = React.useRef(null);
|
|
4002
4079
|
const canWrite = hostEnsWrite && !!st.mineOwned;
|
|
4080
|
+
const canBind = hostWalletBind && !!st.mineOwned && !st.writeBlocked;
|
|
4003
4081
|
const rec = st.record;
|
|
4004
4082
|
const boundEth = rec && rec.addresses && rec.addresses["60"];
|
|
4005
4083
|
const boundSol = rec && rec.addresses && rec.addresses["501"];
|
|
@@ -4022,7 +4100,16 @@ ${peer.address}`
|
|
|
4022
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) });
|
|
4023
4101
|
});
|
|
4024
4102
|
};
|
|
4025
|
-
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")));
|
|
4026
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(
|
|
4027
4114
|
"input",
|
|
4028
4115
|
{
|
|
@@ -4036,7 +4123,7 @@ ${peer.address}`
|
|
|
4036
4123
|
},
|
|
4037
4124
|
style: { ...field, minWidth: 110 }
|
|
4038
4125
|
}
|
|
4039
|
-
), /* @__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))));
|
|
4040
4127
|
}
|
|
4041
4128
|
function DkUpdateCheck({ T }) {
|
|
4042
4129
|
const [st, setSt] = React.useState("idle");
|
|
@@ -6414,6 +6501,10 @@ ${peer.address}`
|
|
|
6414
6501
|
ensUnbind: "unbind",
|
|
6415
6502
|
ensNotBound: "not bound",
|
|
6416
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",
|
|
6417
6508
|
ensAvatar: "avatar",
|
|
6418
6509
|
ensChoose: "choose",
|
|
6419
6510
|
ensClear: "clear",
|
|
@@ -6609,6 +6700,10 @@ ${peer.address}`
|
|
|
6609
6700
|
ensUnbind: "\u89E3\u7ED1",
|
|
6610
6701
|
ensNotBound: "\u672A\u7ED1\u5B9A",
|
|
6611
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",
|
|
6612
6707
|
ensAvatar: "\u5934\u50CF",
|
|
6613
6708
|
ensChoose: "\u9009\u62E9",
|
|
6614
6709
|
ensClear: "\u6E05\u9664",
|
|
@@ -6943,10 +7038,10 @@ ${peer.address}`
|
|
|
6943
7038
|
window.location.reload();
|
|
6944
7039
|
}, [me && me.beagleVer]);
|
|
6945
7040
|
const [assistantTs, setAssistantTs] = React.useState(loadAssistantTs);
|
|
6946
|
-
const peers = React.useMemo(() => (data.peers || []).concat([Object.assign({}, ASSISTANT_PEER, {
|
|
7041
|
+
const peers = React.useMemo(() => (data.peers || []).concat(hostAssistant ? [Object.assign({}, ASSISTANT_PEER, {
|
|
6947
7042
|
lastTs: assistantTs,
|
|
6948
7043
|
lastTime: assistantTs ? dkClock(assistantTs) : ""
|
|
6949
|
-
})]), [data.peers, assistantTs]);
|
|
7044
|
+
})] : []), [data.peers, assistantTs]);
|
|
6950
7045
|
const [pendingAddr, setPendingAddr] = React.useState(() => dkParseDeepLinkAddress());
|
|
6951
7046
|
React.useEffect(() => {
|
|
6952
7047
|
const onHash = () => {
|
package/dist/embedded-host.js
CHANGED
|
@@ -617,6 +617,20 @@ export class EmbeddedHost {
|
|
|
617
617
|
});
|
|
618
618
|
return {};
|
|
619
619
|
}
|
|
620
|
+
case "chat-retry": {
|
|
621
|
+
// Drain oldest-first (the named message among them) rather than sending
|
|
622
|
+
// that one message alone: a retry must not let a newer message overtake
|
|
623
|
+
// an older one in the same thread. The daemon path behaves the same.
|
|
624
|
+
if (!this.#sessionUsable(uid))
|
|
625
|
+
throw new Error("Peer is not online yet");
|
|
626
|
+
await this.#chainFlush(uid);
|
|
627
|
+
const still = this.#messages.queuedOutgoing(uid);
|
|
628
|
+
const id = typeof req.id === "string" ? req.id : undefined;
|
|
629
|
+
if (id && still.some((m) => m.id === id)) {
|
|
630
|
+
throw new Error("Still could not deliver it — it stays queued and will be retried");
|
|
631
|
+
}
|
|
632
|
+
return { queued: still.length };
|
|
633
|
+
}
|
|
620
634
|
case "chat-log-local":
|
|
621
635
|
this.#messages.append(uid, req.dir ?? "in", String(req.text ?? ""), Date.now());
|
|
622
636
|
this.#meta.ensure(uid);
|
package/dist/ipc.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Ops the daemon answers. Every one is peer-level — none of them needs the
|
|
2
2
|
* TUN — which is what makes the beagle/decentlan split possible at all. */
|
|
3
|
-
export type IpcOp = "ping" | "diag" | "friend-request" | "friends-pending" | "friends-accept" | "friends-reject" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "sign" | "chat-send" | "chat-history" | "chat-log-local" | "chat-mark-read" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "file-log-local" | "call-signal" | "call-poll";
|
|
3
|
+
export type IpcOp = "ping" | "diag" | "friend-request" | "friends-pending" | "friends-accept" | "friends-reject" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "sign" | "chat-send" | "chat-history" | "chat-log-local" | "chat-mark-read" | "chat-retry" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "file-log-local" | "call-signal" | "call-poll";
|
|
4
4
|
export interface IpcRequest {
|
|
5
5
|
op: IpcOp;
|
|
6
6
|
address?: string;
|
package/dist/server.js
CHANGED
|
@@ -26,6 +26,7 @@ import { bridgeRoster } from "./bridge-roster.js";
|
|
|
26
26
|
import { isSupervised } from "./relaunch.js";
|
|
27
27
|
import { historyStats } from "./assistant-stats.js";
|
|
28
28
|
import { createDiscoverFetch } from "./discover-cache.js";
|
|
29
|
+
import { applyWalletBind } from "@decentnetwork/beagle-ui/wallet-proof";
|
|
29
30
|
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
30
31
|
// scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
|
|
31
32
|
const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
@@ -1354,13 +1355,11 @@ export function startBeagleServer(opts) {
|
|
|
1354
1355
|
}
|
|
1355
1356
|
if (req.method === "POST" && url === "/api/ens-bind-wallet") {
|
|
1356
1357
|
const body = await readBody(req);
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (!coinType || !okAddr) {
|
|
1363
|
-
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 });
|
|
1364
1363
|
return;
|
|
1365
1364
|
}
|
|
1366
1365
|
const diag = await opts.call({ op: "diag" });
|
|
@@ -1378,16 +1377,22 @@ export function startBeagleServer(opts) {
|
|
|
1378
1377
|
sendJson(res, 403, { ok: false, error: "this name was registered by your mobile wallet — bind addresses there" });
|
|
1379
1378
|
return;
|
|
1380
1379
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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;
|
|
1386
1391
|
const r = await ensSignAndSet(opts.call, {
|
|
1387
1392
|
name: mine.name,
|
|
1388
1393
|
owner: identity.userid,
|
|
1389
1394
|
addresses,
|
|
1390
|
-
texts
|
|
1395
|
+
texts,
|
|
1391
1396
|
referee: mine.referee ?? "",
|
|
1392
1397
|
nft: mine.nft ?? "",
|
|
1393
1398
|
nftid: mine.nftid ?? 0,
|
|
@@ -2443,6 +2448,17 @@ export function startBeagleServer(opts) {
|
|
|
2443
2448
|
sendJson(res, r.ok ? 200 : 400, r);
|
|
2444
2449
|
return;
|
|
2445
2450
|
}
|
|
2451
|
+
// The UI has posted this since 0.1.119, but there was no route and no
|
|
2452
|
+
// daemon op behind it: the retry button on a queued message 404'd, so a
|
|
2453
|
+
// message that failed while the friend stayed connected had no way out
|
|
2454
|
+
// at all (mac-dev, 2026-10-31). Needs @decentnetwork/lan >= 0.1.308 in
|
|
2455
|
+
// daemon mode; embedded mode handles it in-process.
|
|
2456
|
+
if (req.method === "POST" && url === "/api/chat-retry") {
|
|
2457
|
+
const { userid, id } = await readBody(req);
|
|
2458
|
+
const r = await opts.call({ op: "chat-retry", userid, id });
|
|
2459
|
+
sendJson(res, r.ok ? 200 : 400, r);
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2446
2462
|
if (req.method === "POST" && url === "/api/chat-log-local") {
|
|
2447
2463
|
const { userid, dir, text } = await readBody(req);
|
|
2448
2464
|
const r = await opts.call({ op: "chat-log-local", userid, dir: dir === "out" ? "out" : "in", text });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.120",
|
|
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.32",
|
|
39
39
|
"@types/js-yaml": "^4.0.9",
|
|
40
40
|
"@types/node": "^20.11.0",
|
|
41
41
|
"@types/yargs": "^17.0.32",
|