@decentnetwork/beagle 0.1.22 → 0.1.23
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 +50 -3
- package/dist/server.js +54 -1
- package/package.json +1 -1
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.22";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
@@ -2956,6 +2956,47 @@ function DkEnsCard({ T, me, onRecord }) {
|
|
|
2956
2956
|
};
|
|
2957
2957
|
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" };
|
|
2958
2958
|
const row = { display: "flex", alignItems: "center", gap: 10, padding: "12px 16px", borderBottom: "1px solid var(--line)" };
|
|
2959
|
+
const rowLbl = { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 };
|
|
2960
|
+
const SOCIALS = [
|
|
2961
|
+
{ k: "twitter", base: "https://x.com/" },
|
|
2962
|
+
{ k: "linkedin", base: "https://www.linkedin.com/in/" },
|
|
2963
|
+
{ k: "github", base: "https://github.com/" }
|
|
2964
|
+
];
|
|
2965
|
+
const [draft, setDraft] = React.useState({});
|
|
2966
|
+
React.useEffect(() => {
|
|
2967
|
+
const t = st.record && st.record.texts || {};
|
|
2968
|
+
setDraft({ twitter: t["com.twitter"] || "", linkedin: t["com.linkedin"] || "", github: t["com.github"] || "" });
|
|
2969
|
+
}, [st.record]);
|
|
2970
|
+
const saveSocial = (k) => post("/api/ens-social", { key: k, value: (draft[k] || "").trim() });
|
|
2971
|
+
const socialRow = ({ k, base }, last) => {
|
|
2972
|
+
var _a;
|
|
2973
|
+
const saved = (st.record && st.record.texts || {})["com." + k] || "";
|
|
2974
|
+
const cur = (_a = draft[k]) != null ? _a : "";
|
|
2975
|
+
return /* @__PURE__ */ React.createElement("div", { key: k, style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { style: rowLbl }, k), /* @__PURE__ */ React.createElement(
|
|
2976
|
+
"input",
|
|
2977
|
+
{
|
|
2978
|
+
value: cur,
|
|
2979
|
+
onChange: (e) => setDraft({ ...draft, [k]: e.target.value }),
|
|
2980
|
+
disabled: busy || !st.mineOwned,
|
|
2981
|
+
placeholder: T.linkPlaceholder || "username or profile URL",
|
|
2982
|
+
onKeyDown: (e) => {
|
|
2983
|
+
if (e.key === "Enter")
|
|
2984
|
+
saveSocial(k);
|
|
2985
|
+
},
|
|
2986
|
+
style: field
|
|
2987
|
+
}
|
|
2988
|
+
), saved && /* @__PURE__ */ React.createElement(
|
|
2989
|
+
"a",
|
|
2990
|
+
{
|
|
2991
|
+
href: base + saved,
|
|
2992
|
+
target: "_blank",
|
|
2993
|
+
rel: "noreferrer",
|
|
2994
|
+
title: base + saved,
|
|
2995
|
+
style: { flexShrink: 0, fontFamily: "var(--mono)", fontSize: 13, color: "var(--accent)", textDecoration: "none" }
|
|
2996
|
+
},
|
|
2997
|
+
"\u2197"
|
|
2998
|
+
), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", disabled: busy || !st.mineOwned || cur.trim() === saved, onClick: () => saveSocial(k) }, saved && !cur.trim() ? T.ensUnbind || "unbind" : T.save || "save"));
|
|
2999
|
+
};
|
|
2959
3000
|
const [picking, setPicking] = React.useState(false);
|
|
2960
3001
|
const fileRef = React.useRef(null);
|
|
2961
3002
|
const rec = st.record;
|
|
@@ -3018,7 +3059,7 @@ function DkEnsCard({ T, me, onRecord }) {
|
|
|
3018
3059
|
img.src = url;
|
|
3019
3060
|
};
|
|
3020
3061
|
const walletRow = (lbl, chain, bound, onBind, last) => /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: last ? "none" : row.borderBottom } }, /* @__PURE__ */ React.createElement("span", { 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, fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, bound), /* @__PURE__ */ React.createElement(CopyBtn, { value: bound, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), /* @__PURE__ */ React.createElement(Btn, { size: "sm", tone: "danger", disabled: busy || !st.mineOwned, 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 || !st.mineOwned, onClick: onBind }, T.ensBind || "bind")));
|
|
3021
|
-
return /* @__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, /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { 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(
|
|
3062
|
+
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, /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { 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(
|
|
3022
3063
|
"input",
|
|
3023
3064
|
{
|
|
3024
3065
|
value: label,
|
|
@@ -3031,7 +3072,7 @@ function DkEnsCard({ T, me, onRecord }) {
|
|
|
3031
3072
|
},
|
|
3032
3073
|
style: field
|
|
3033
3074
|
}
|
|
3034
|
-
), /* @__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", { style: row }, /* @__PURE__ */ React.createElement("span", { 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 || !st.mineOwned, 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 || !st.mineOwned, 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 || !st.mineOwned, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, 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) }));
|
|
3075
|
+
), /* @__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", { style: row }, /* @__PURE__ */ React.createElement("span", { 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 || !st.mineOwned, 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 || !st.mineOwned, 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 || !st.mineOwned, onClick: () => fileRef.current && fileRef.current.click() }, T.ensUpload || "upload"), /* @__PURE__ */ React.createElement(Btn, { size: "sm", disabled: busy || !st.mineOwned, 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))));
|
|
3035
3076
|
}
|
|
3036
3077
|
function ProfileTab({ T, me, onEdit }) {
|
|
3037
3078
|
const [qr, setQr] = React.useState(null);
|
|
@@ -4098,6 +4139,9 @@ const STR = {
|
|
|
4098
4139
|
pt_zombie: "zombie",
|
|
4099
4140
|
pt_ape: "ape",
|
|
4100
4141
|
pt_alien: "alien",
|
|
4142
|
+
linksCard: "Links",
|
|
4143
|
+
linkPlaceholder: "username or profile URL",
|
|
4144
|
+
save: "save",
|
|
4101
4145
|
ensNoEthWallet: "no Ethereum wallet extension found (MetaMask\u2026)",
|
|
4102
4146
|
ensNoSolWallet: "no Solana wallet extension found (Phantom\u2026)",
|
|
4103
4147
|
ensWalletOwned: "registered via your mobile wallet as",
|
|
@@ -4239,6 +4283,9 @@ const STR = {
|
|
|
4239
4283
|
pt_zombie: "\u50F5\u5C38",
|
|
4240
4284
|
pt_ape: "\u733F",
|
|
4241
4285
|
pt_alien: "\u5916\u661F\u4EBA",
|
|
4286
|
+
linksCard: "\u793E\u4EA4\u8D26\u53F7",
|
|
4287
|
+
linkPlaceholder: "\u7528\u6237\u540D\u6216\u4E3B\u9875\u94FE\u63A5",
|
|
4288
|
+
save: "\u4FDD\u5B58",
|
|
4242
4289
|
ensNoEthWallet: "\u672A\u68C0\u6D4B\u5230\u4EE5\u592A\u574A\u94B1\u5305\u63D2\u4EF6(MetaMask \u7B49)",
|
|
4243
4290
|
ensNoSolWallet: "\u672A\u68C0\u6D4B\u5230 Solana \u94B1\u5305\u63D2\u4EF6(Phantom \u7B49)",
|
|
4244
4291
|
ensWalletOwned: "\u5DF2\u901A\u8FC7\u624B\u673A\u94B1\u5305\u6CE8\u518C\u4E3A",
|
package/dist/server.js
CHANGED
|
@@ -471,7 +471,7 @@ export function startBeagleServer(opts) {
|
|
|
471
471
|
// Register/update this node's *.beagles.eth name and bind wallet
|
|
472
472
|
// addresses into the record. The write routes trigger identity
|
|
473
473
|
// signatures, so they're LOCALHOST-ONLY like /connect.
|
|
474
|
-
if (url === "/api/ens-register" || url === "/api/ens-bind-wallet" || url === "/api/ens-avatar" || url === "/api/ens-avatar-upload") {
|
|
474
|
+
if (url === "/api/ens-register" || url === "/api/ens-bind-wallet" || url === "/api/ens-avatar" || url === "/api/ens-avatar-upload" || url === "/api/ens-social") {
|
|
475
475
|
if (!isLocalRequest(req)) {
|
|
476
476
|
sendJson(res, 403, { ok: false, error: "identity signing is available only on this machine (localhost)" });
|
|
477
477
|
return;
|
|
@@ -549,6 +549,59 @@ export function startBeagleServer(opts) {
|
|
|
549
549
|
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, nftid: clear ? null : nftid } : { ok: false, error: r.error });
|
|
550
550
|
return;
|
|
551
551
|
}
|
|
552
|
+
// Bind/update/remove a social handle on the record, stored under the
|
|
553
|
+
// standard ENS text keys (com.twitter / com.linkedin / com.github) so
|
|
554
|
+
// other ENS-aware clients can read them too. Empty value = remove.
|
|
555
|
+
if (req.method === "POST" && url === "/api/ens-social") {
|
|
556
|
+
const SOCIAL_KEYS = { twitter: "com.twitter", linkedin: "com.linkedin", github: "com.github" };
|
|
557
|
+
const body = await readBody(req);
|
|
558
|
+
const key = SOCIAL_KEYS[String(body.key ?? "")];
|
|
559
|
+
if (!key) {
|
|
560
|
+
sendJson(res, 400, { ok: false, error: "key must be twitter, linkedin or github" });
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// Accept a bare handle, @handle, or a pasted profile URL — keep the handle.
|
|
564
|
+
const value = String(body.value ?? "")
|
|
565
|
+
.trim()
|
|
566
|
+
.replace(/^https?:\/\/(www\.)?(x\.com|twitter\.com|github\.com|linkedin\.com\/(in|company))\//i, "")
|
|
567
|
+
.replace(/\/+$/, "")
|
|
568
|
+
.replace(/^@/, "");
|
|
569
|
+
if (value && !/^[A-Za-z0-9_.-]{1,64}$/.test(value)) {
|
|
570
|
+
sendJson(res, 400, { ok: false, error: "handle may only contain letters, digits, _ . -" });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const diag = await opts.call({ op: "diag" });
|
|
574
|
+
const identity = (diag.ok ? diag.data?.identity : null) ?? {};
|
|
575
|
+
if (!identity.userid) {
|
|
576
|
+
sendJson(res, 502, { ok: false, error: "identity unavailable" });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const mine = await ensFetchJson(`/getAddress/${encodeURIComponent(identity.userid)}`);
|
|
580
|
+
if (!mine) {
|
|
581
|
+
sendJson(res, 404, { ok: false, error: "register a name first" });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (mine.owner !== identity.userid) {
|
|
585
|
+
sendJson(res, 403, { ok: false, error: "this name was registered by your mobile wallet — edit links there" });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const texts = { ...(mine.texts ?? {}) };
|
|
589
|
+
if (value)
|
|
590
|
+
texts[key] = value;
|
|
591
|
+
else
|
|
592
|
+
delete texts[key];
|
|
593
|
+
const r = await ensSignAndSet(opts.call, {
|
|
594
|
+
name: mine.name,
|
|
595
|
+
owner: identity.userid,
|
|
596
|
+
addresses: mine.addresses ?? {},
|
|
597
|
+
texts,
|
|
598
|
+
referee: mine.referee ?? "",
|
|
599
|
+
nft: mine.nft ?? "",
|
|
600
|
+
nftid: mine.nftid ?? 0,
|
|
601
|
+
});
|
|
602
|
+
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, key: body.key, value: value || null } : { ok: false, error: r.error });
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
552
605
|
// Upload a custom avatar image: the browser sends a pre-scaled data URL,
|
|
553
606
|
// we push the bytes to the gateway's R2 (auth = identity signature) and
|
|
554
607
|
// point texts.avatar at it.
|
package/package.json
CHANGED