@decentnetwork/beagle 0.1.19 → 0.1.21
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 +135 -8
- package/dist/server.js +90 -2
- 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.20";
|
|
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"/>',
|
|
@@ -2800,7 +2800,97 @@ function DkEditModal({ T, me, onClose, onSave }) {
|
|
|
2800
2800
|
};
|
|
2801
2801
|
return /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 90, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 440, maxWidth: "92vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: 22, display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700, color: "var(--text)" } }, T.editProfile), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { icon: "x", size: "sm", onClick: onClose })), /* @__PURE__ */ React.createElement("label", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "display name"), /* @__PURE__ */ React.createElement("input", { value: name, onChange: (e) => setName(e.target.value), onKeyDown: onKey, autoFocus: true, maxLength: 48, style: field })), /* @__PURE__ */ React.createElement("label", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: lbl }, "status message"), /* @__PURE__ */ React.createElement("input", { value: desc, onChange: (e) => setDesc(e.target.value), onKeyDown: onKey, maxLength: 120, placeholder: "optional \u2014 a short bio friends will see", style: field })), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--faint)" } }, "Your userid (the unique identity) can't change \u2014 only the display name + status."), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Btn, { size: "sm", onClick: onClose }, T.cancel || "cancel"), /* @__PURE__ */ React.createElement(Btn, { tone: "accent", size: "sm", onClick: save }, T.save || "save"))));
|
|
2802
2802
|
}
|
|
2803
|
-
|
|
2803
|
+
const dkPunkCache = /* @__PURE__ */ new Map();
|
|
2804
|
+
function DkPunkAvatar({ id, size, radius, fallbackSeed }) {
|
|
2805
|
+
const [img, setImg] = React.useState(() => (dkPunkCache.get(id) || {}).image || null);
|
|
2806
|
+
React.useEffect(() => {
|
|
2807
|
+
let dead = false;
|
|
2808
|
+
const hit = dkPunkCache.get(id);
|
|
2809
|
+
if (hit) {
|
|
2810
|
+
setImg(hit.image || null);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
setImg(null);
|
|
2814
|
+
fetch(`/api/punk/${id}`).then((r) => r.json()).then((d) => {
|
|
2815
|
+
if (d.ok && d.punk && d.punk.image) {
|
|
2816
|
+
dkPunkCache.set(id, d.punk);
|
|
2817
|
+
if (!dead)
|
|
2818
|
+
setImg(d.punk.image);
|
|
2819
|
+
}
|
|
2820
|
+
}).catch(() => {
|
|
2821
|
+
});
|
|
2822
|
+
return () => {
|
|
2823
|
+
dead = true;
|
|
2824
|
+
};
|
|
2825
|
+
}, [id]);
|
|
2826
|
+
if (!img)
|
|
2827
|
+
return /* @__PURE__ */ React.createElement(DkIdenticon, { seed: fallbackSeed || String(id), size, radius });
|
|
2828
|
+
return /* @__PURE__ */ React.createElement(
|
|
2829
|
+
"img",
|
|
2830
|
+
{
|
|
2831
|
+
src: img,
|
|
2832
|
+
alt: "",
|
|
2833
|
+
width: size,
|
|
2834
|
+
height: size,
|
|
2835
|
+
style: { width: size, height: size, borderRadius: radius, background: "#638596", imageRendering: "pixelated", flexShrink: 0 }
|
|
2836
|
+
}
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
function DkPunkPicker({ T, onPick, onClose }) {
|
|
2840
|
+
const TYPES = ["any", "female", "male", "zombie", "ape", "alien"];
|
|
2841
|
+
const tLabel = (t) => T && T["pt_" + t] || t;
|
|
2842
|
+
const [type, setType] = React.useState("any");
|
|
2843
|
+
const [batch, setBatch] = React.useState(null);
|
|
2844
|
+
const [err, setErr] = React.useState(null);
|
|
2845
|
+
const [gen, setGen] = React.useState(0);
|
|
2846
|
+
React.useEffect(() => {
|
|
2847
|
+
let dead = false;
|
|
2848
|
+
setBatch(null);
|
|
2849
|
+
setErr(null);
|
|
2850
|
+
fetch(`/api/punk-list?type=${type}&limit=24`).then((r) => r.json()).then((d) => {
|
|
2851
|
+
if (dead)
|
|
2852
|
+
return;
|
|
2853
|
+
if (d.ok)
|
|
2854
|
+
setBatch(d.list || []);
|
|
2855
|
+
else
|
|
2856
|
+
setErr(d.error || "load failed");
|
|
2857
|
+
}).catch((e) => {
|
|
2858
|
+
if (!dead)
|
|
2859
|
+
setErr(String(e && e.message || e));
|
|
2860
|
+
});
|
|
2861
|
+
return () => {
|
|
2862
|
+
dead = true;
|
|
2863
|
+
};
|
|
2864
|
+
}, [type, gen]);
|
|
2865
|
+
return /* @__PURE__ */ React.createElement("div", { onClick: onClose, style: { position: "fixed", inset: 0, zIndex: 90, background: "color-mix(in oklab, #000, transparent 38%)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { width: 480, maxWidth: "94vw", background: "var(--panel)", border: "1px solid var(--line)", borderRadius: 16, padding: 20, display: "flex", flexDirection: "column", gap: 14 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700, color: "var(--text)" } }, T.ensPickPunk || "choose a punk"), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { size: "sm", onClick: () => setGen(gen + 1) }, T.ensShuffle || "shuffle"), /* @__PURE__ */ React.createElement(Btn, { icon: "x", size: "sm", onClick: onClose })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" } }, TYPES.map((t) => /* @__PURE__ */ React.createElement(
|
|
2866
|
+
"button",
|
|
2867
|
+
{
|
|
2868
|
+
key: t,
|
|
2869
|
+
onClick: () => setType(t),
|
|
2870
|
+
style: {
|
|
2871
|
+
padding: "4px 10px",
|
|
2872
|
+
borderRadius: 999,
|
|
2873
|
+
border: "1px solid var(--line)",
|
|
2874
|
+
cursor: "pointer",
|
|
2875
|
+
background: type === t ? "var(--accent)" : "transparent",
|
|
2876
|
+
color: type === t ? "#fff" : "var(--dim)",
|
|
2877
|
+
fontFamily: "var(--ui)",
|
|
2878
|
+
fontSize: 12
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
tLabel(t)
|
|
2882
|
+
))), /* @__PURE__ */ React.createElement("div", { style: { minHeight: 232 } }, err ? /* @__PURE__ */ React.createElement("div", { style: { padding: 16, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, err) : !batch ? /* @__PURE__ */ React.createElement("div", { style: { padding: 16, fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)" } }, T.dirLoading || "loading\u2026") : /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 8 } }, batch.map((p) => /* @__PURE__ */ React.createElement(
|
|
2883
|
+
"button",
|
|
2884
|
+
{
|
|
2885
|
+
key: p.id,
|
|
2886
|
+
title: `#${p.id} \xB7 ${p.type}`,
|
|
2887
|
+
onClick: () => onPick(Number(p.id)),
|
|
2888
|
+
style: { padding: 0, border: "1px solid var(--line)", borderRadius: 10, cursor: "pointer", background: "#638596", overflow: "hidden", aspectRatio: "1" }
|
|
2889
|
+
},
|
|
2890
|
+
/* @__PURE__ */ React.createElement("img", { src: p.image, alt: `punk #${p.id}`, style: { width: "100%", height: "100%", imageRendering: "pixelated", display: "block" } })
|
|
2891
|
+
))))));
|
|
2892
|
+
}
|
|
2893
|
+
function DkEnsCard({ T, me, onRecord }) {
|
|
2804
2894
|
const [st, setSt] = React.useState({ loading: true });
|
|
2805
2895
|
const [label, setLabel] = React.useState("");
|
|
2806
2896
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -2810,6 +2900,8 @@ function DkEnsCard({ T, me }) {
|
|
|
2810
2900
|
setSt({ loading: false, registered: !!d.registered, record: d.record || null, mineOwned: !!d.mineOwned });
|
|
2811
2901
|
if (d.registered && d.record && d.record.name)
|
|
2812
2902
|
setLabel(d.record.name.replace(/\.beagles\.eth$/i, ""));
|
|
2903
|
+
if (onRecord)
|
|
2904
|
+
onRecord(d.record || null);
|
|
2813
2905
|
}).catch((e) => setSt({ loading: false, error: String(e && e.message || e) }));
|
|
2814
2906
|
};
|
|
2815
2907
|
React.useEffect(() => {
|
|
@@ -2864,10 +2956,16 @@ function DkEnsCard({ T, me }) {
|
|
|
2864
2956
|
};
|
|
2865
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" };
|
|
2866
2958
|
const row = { display: "flex", alignItems: "center", gap: 10, padding: "12px 16px", borderBottom: "1px solid var(--line)" };
|
|
2959
|
+
const [picking, setPicking] = React.useState(false);
|
|
2867
2960
|
const rec = st.record;
|
|
2868
2961
|
const boundEth = rec && rec.addresses && rec.addresses["60"];
|
|
2869
2962
|
const boundSol = rec && rec.addresses && rec.addresses["501"];
|
|
2870
|
-
const
|
|
2963
|
+
const punkId = rec && rec.nft === "CryptoPunks" && rec.nftid > 0 ? rec.nftid : null;
|
|
2964
|
+
const pickPunk = (id) => {
|
|
2965
|
+
setPicking(false);
|
|
2966
|
+
post("/api/ens-avatar", { nftid: id });
|
|
2967
|
+
};
|
|
2968
|
+
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")));
|
|
2871
2969
|
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(
|
|
2872
2970
|
"input",
|
|
2873
2971
|
{
|
|
@@ -2881,12 +2979,14 @@ function DkEnsCard({ T, me }) {
|
|
|
2881
2979
|
},
|
|
2882
2980
|
style: field
|
|
2883
2981
|
}
|
|
2884
|
-
), /* @__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)), walletRow(T.ensEth || "ethereum", boundEth, bindEth, false), walletRow(T.ensSol || "solana", 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)));
|
|
2982
|
+
), /* @__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"), 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: () => setPicking(true) }, T.ensChoose || "choose")), 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) }));
|
|
2885
2983
|
}
|
|
2886
2984
|
function ProfileTab({ T, me, onEdit }) {
|
|
2887
2985
|
const [qr, setQr] = React.useState(null);
|
|
2888
2986
|
const [editing, setEditing] = React.useState(false);
|
|
2889
|
-
|
|
2987
|
+
const [ensRec, setEnsRec] = React.useState(null);
|
|
2988
|
+
const punkId = ensRec && ensRec.nft === "CryptoPunks" && ensRec.nftid > 0 ? ensRec.nftid : null;
|
|
2989
|
+
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: punkId, size: 68, radius: 16, fallbackSeed: me.userId }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__PURE__ */ React.createElement(DkEnsCard, { T, me, onRecord: setEnsRec }), typeof me.autoAccept === "boolean" && /* @__PURE__ */ React.createElement(Card, { label: T && T.friendsSettings || "Friends" }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
|
|
2890
2990
|
"input",
|
|
2891
2991
|
{
|
|
2892
2992
|
type: "checkbox",
|
|
@@ -2904,9 +3004,12 @@ function ProfileTab({ T, me, onEdit }) {
|
|
|
2904
3004
|
setEditing(false);
|
|
2905
3005
|
} }));
|
|
2906
3006
|
}
|
|
2907
|
-
Object.assign(window, { ProfileTab });
|
|
3007
|
+
Object.assign(window, { ProfileTab, DkPunkAvatar });
|
|
2908
3008
|
function DkDirAvatar({ p, size }) {
|
|
2909
3009
|
const [broken, setBroken] = React.useState(false);
|
|
3010
|
+
if (p.punkId != null) {
|
|
3011
|
+
return /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: p.punkId, size, radius: Math.round(size / 4), fallbackSeed: p.userid || p.name });
|
|
3012
|
+
}
|
|
2910
3013
|
if (p.avatar && !broken) {
|
|
2911
3014
|
return /* @__PURE__ */ React.createElement(
|
|
2912
3015
|
"img",
|
|
@@ -3924,8 +4027,20 @@ const STR = {
|
|
|
3924
4027
|
ensEth: "ethereum",
|
|
3925
4028
|
ensSol: "solana",
|
|
3926
4029
|
ensBind: "bind",
|
|
3927
|
-
|
|
4030
|
+
ensUnbind: "unbind",
|
|
3928
4031
|
ensNotBound: "not bound",
|
|
4032
|
+
ensAvatar: "avatar",
|
|
4033
|
+
ensChoose: "choose",
|
|
4034
|
+
ensClear: "clear",
|
|
4035
|
+
ensNotSet: "not set",
|
|
4036
|
+
ensPickPunk: "choose a punk",
|
|
4037
|
+
ensShuffle: "shuffle",
|
|
4038
|
+
pt_any: "any",
|
|
4039
|
+
pt_female: "female",
|
|
4040
|
+
pt_male: "male",
|
|
4041
|
+
pt_zombie: "zombie",
|
|
4042
|
+
pt_ape: "ape",
|
|
4043
|
+
pt_alien: "alien",
|
|
3929
4044
|
ensNoEthWallet: "no Ethereum wallet extension found (MetaMask\u2026)",
|
|
3930
4045
|
ensNoSolWallet: "no Solana wallet extension found (Phantom\u2026)",
|
|
3931
4046
|
ensWalletOwned: "registered via your mobile wallet as",
|
|
@@ -4049,8 +4164,20 @@ const STR = {
|
|
|
4049
4164
|
ensEth: "ethereum",
|
|
4050
4165
|
ensSol: "solana",
|
|
4051
4166
|
ensBind: "\u7ED1\u5B9A",
|
|
4052
|
-
|
|
4167
|
+
ensUnbind: "\u89E3\u7ED1",
|
|
4053
4168
|
ensNotBound: "\u672A\u7ED1\u5B9A",
|
|
4169
|
+
ensAvatar: "\u5934\u50CF",
|
|
4170
|
+
ensChoose: "\u9009\u62E9",
|
|
4171
|
+
ensClear: "\u6E05\u9664",
|
|
4172
|
+
ensNotSet: "\u672A\u8BBE\u7F6E",
|
|
4173
|
+
ensPickPunk: "\u9009\u4E00\u4E2A punk",
|
|
4174
|
+
ensShuffle: "\u6362\u4E00\u6279",
|
|
4175
|
+
pt_any: "\u5168\u90E8",
|
|
4176
|
+
pt_female: "\u5973",
|
|
4177
|
+
pt_male: "\u7537",
|
|
4178
|
+
pt_zombie: "\u50F5\u5C38",
|
|
4179
|
+
pt_ape: "\u733F",
|
|
4180
|
+
pt_alien: "\u5916\u661F\u4EBA",
|
|
4054
4181
|
ensNoEthWallet: "\u672A\u68C0\u6D4B\u5230\u4EE5\u592A\u574A\u94B1\u5305\u63D2\u4EF6(MetaMask \u7B49)",
|
|
4055
4182
|
ensNoSolWallet: "\u672A\u68C0\u6D4B\u5230 Solana \u94B1\u5305\u63D2\u4EF6(Phantom \u7B49)",
|
|
4056
4183
|
ensWalletOwned: "\u5DF2\u901A\u8FC7\u624B\u673A\u94B1\u5305\u6CE8\u518C\u4E3A",
|
package/dist/server.js
CHANGED
|
@@ -111,6 +111,27 @@ async function ensSignAndSet(call, record) {
|
|
|
111
111
|
clearTimeout(timer);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
// ── CryptoPunks avatar directory ────────────────────────────────────────────
|
|
115
|
+
// Self-hosted punks API serving all 10k punks as inline SVG data URIs. The
|
|
116
|
+
// record convention (shared with the mobile apps) is nft="CryptoPunks" +
|
|
117
|
+
// nftid=<punk id> on the beagles.eth name. Proxied server-side (browser can't
|
|
118
|
+
// reach the origin's port cross-origin reliably); NOTE the upstream's TLS cert
|
|
119
|
+
// must be valid — renew it when these routes start failing.
|
|
120
|
+
const PUNKS_API_URL = process.env.PUNKS_API_URL || "https://app.beagle.chat:1337";
|
|
121
|
+
const punkCache = new Map(); // punk id → JSON; immutable set
|
|
122
|
+
async function punksFetch(path) {
|
|
123
|
+
const ctl = new AbortController();
|
|
124
|
+
const timer = setTimeout(() => ctl.abort(), 10_000);
|
|
125
|
+
try {
|
|
126
|
+
const r = await fetch(`${PUNKS_API_URL}${path}`, { signal: ctl.signal });
|
|
127
|
+
if (!r.ok)
|
|
128
|
+
throw new Error(`punks upstream ${r.status}`);
|
|
129
|
+
return await r.json();
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
114
135
|
/** True only when the request came from the local machine. Used to gate the
|
|
115
136
|
* "Sign in with Decent" routes so binding the UI to a LAN IP can't expose
|
|
116
137
|
* identity signing to other hosts. The popup always runs in the local user's
|
|
@@ -428,12 +449,77 @@ export function startBeagleServer(opts) {
|
|
|
428
449
|
// Register/update this node's *.beagles.eth name and bind wallet
|
|
429
450
|
// addresses into the record. The write routes trigger identity
|
|
430
451
|
// signatures, so they're LOCALHOST-ONLY like /connect.
|
|
431
|
-
if (url === "/api/ens-register" || url === "/api/ens-bind-wallet") {
|
|
452
|
+
if (url === "/api/ens-register" || url === "/api/ens-bind-wallet" || url === "/api/ens-avatar") {
|
|
432
453
|
if (!isLocalRequest(req)) {
|
|
433
454
|
sendJson(res, 403, { ok: false, error: "identity signing is available only on this machine (localhost)" });
|
|
434
455
|
return;
|
|
435
456
|
}
|
|
436
457
|
}
|
|
458
|
+
// Punk directory (proxied): a random batch by type, and one punk by id.
|
|
459
|
+
if (req.method === "GET" && url.startsWith("/api/punk-list")) {
|
|
460
|
+
try {
|
|
461
|
+
const q = new URL(url, "http://localhost");
|
|
462
|
+
const type = (q.searchParams.get("type") || "any").toLowerCase().replace(/[^a-z]/g, "") || "any";
|
|
463
|
+
const limit = Math.min(60, Math.max(1, Number(q.searchParams.get("limit")) || 24));
|
|
464
|
+
const list = await punksFetch(`/api/punks/filter/${type}/any?limit=${limit}`);
|
|
465
|
+
sendJson(res, 200, { ok: true, list });
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
sendJson(res, 502, { ok: false, error: String(err?.message ?? err) });
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (req.method === "GET" && /^\/api\/punk\/\d{1,5}$/.test(url)) {
|
|
473
|
+
const id = url.slice("/api/punk/".length);
|
|
474
|
+
try {
|
|
475
|
+
let punk = punkCache.get(id);
|
|
476
|
+
if (!punk) {
|
|
477
|
+
punk = await punksFetch(`/api/punks/${id}`);
|
|
478
|
+
punkCache.set(id, punk);
|
|
479
|
+
}
|
|
480
|
+
sendJson(res, 200, { ok: true, punk });
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
sendJson(res, 502, { ok: false, error: String(err?.message ?? err) });
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// Set (or clear) my punk avatar on the beagles.eth record.
|
|
488
|
+
if (req.method === "POST" && url === "/api/ens-avatar") {
|
|
489
|
+
const body = await readBody(req);
|
|
490
|
+
const clear = body.nftid === null || body.nftid === undefined;
|
|
491
|
+
const nftid = Number(body.nftid);
|
|
492
|
+
if (!clear && (!Number.isInteger(nftid) || nftid < 0 || nftid > 9999)) {
|
|
493
|
+
sendJson(res, 400, { ok: false, error: "invalid punk id" });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const diag = await opts.call({ op: "diag" });
|
|
497
|
+
const identity = (diag.ok ? diag.data?.identity : null) ?? {};
|
|
498
|
+
if (!identity.userid) {
|
|
499
|
+
sendJson(res, 502, { ok: false, error: "identity unavailable" });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const mine = await ensFetchJson(`/getAddress/${encodeURIComponent(identity.userid)}`);
|
|
503
|
+
if (!mine) {
|
|
504
|
+
sendJson(res, 404, { ok: false, error: "register a name first" });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (mine.owner !== identity.userid) {
|
|
508
|
+
sendJson(res, 403, { ok: false, error: "this name was registered by your mobile wallet — pick the avatar there" });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const r = await ensSignAndSet(opts.call, {
|
|
512
|
+
name: mine.name,
|
|
513
|
+
owner: identity.userid,
|
|
514
|
+
addresses: mine.addresses ?? {},
|
|
515
|
+
texts: mine.texts ?? {},
|
|
516
|
+
referee: mine.referee ?? "",
|
|
517
|
+
nft: clear ? "" : "CryptoPunks",
|
|
518
|
+
nftid: clear ? 0 : nftid,
|
|
519
|
+
});
|
|
520
|
+
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, nftid: clear ? null : nftid } : { ok: false, error: r.error });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
437
523
|
if (req.method === "GET" && url === "/api/ens-name") {
|
|
438
524
|
const diag = await opts.call({ op: "diag" });
|
|
439
525
|
const identity = (diag.ok ? diag.data?.identity : null) ?? {};
|
|
@@ -835,7 +921,7 @@ export function startBeagleServer(opts) {
|
|
|
835
921
|
}
|
|
836
922
|
if (req.method === "GET" && url === "/api/discover-registered") {
|
|
837
923
|
try {
|
|
838
|
-
const raw = (await fetchDiscoverJson(
|
|
924
|
+
const raw = (await fetchDiscoverJson(`${ENS_GATEWAY}/names`));
|
|
839
925
|
const list = Object.entries(raw && typeof raw === "object" ? raw : {})
|
|
840
926
|
.map(([ens, v]) => {
|
|
841
927
|
const t = v?.texts ?? {};
|
|
@@ -845,6 +931,8 @@ export function startBeagleServer(opts) {
|
|
|
845
931
|
name: t.displayName || ens,
|
|
846
932
|
ens,
|
|
847
933
|
description: t.description || null,
|
|
934
|
+
// punk avatar convention: nft="CryptoPunks" + nftid>0 (0 = unset)
|
|
935
|
+
punkId: v?.nft === "CryptoPunks" && typeof v?.nftid === "number" && v.nftid > 0 ? v.nftid : null,
|
|
848
936
|
};
|
|
849
937
|
})
|
|
850
938
|
.filter((e) => e.userid && e.address);
|
package/package.json
CHANGED