@decentnetwork/beagle 0.1.59 → 0.1.61
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 +8 -4
- package/dist/server.js +104 -22
- 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.61";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -2925,11 +2925,14 @@ ${peer.address}`
|
|
|
2925
2925
|
return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u663E\u793A" : "Display" }, /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u8BED\u8A00" : "Language"), seg("lang", t.lang, (v) => setTweak("lang", v), [["en", "EN"], ["zh", "\u4E2D\u6587"]])), /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u4E3B\u9898" : "Theme"), seg("theme", t.theme, (v) => setTweak("theme", v), [["dark", zh ? "\u6DF1\u8272" : "Dark"], ["light", zh ? "\u6D45\u8272" : "Light"]])));
|
|
2926
2926
|
}
|
|
2927
2927
|
function ProfileTab({ T, me, onEdit, t, setTweak }) {
|
|
2928
|
+
var _a;
|
|
2928
2929
|
const [qr, setQr] = React.useState(null);
|
|
2929
2930
|
const [editing, setEditing] = React.useState(false);
|
|
2930
2931
|
const [ensRec, setEnsRec] = React.useState(null);
|
|
2931
|
-
const
|
|
2932
|
-
const
|
|
2932
|
+
const pubPunk = ensRec && ensRec.nft === "CryptoPunks" && ensRec.nftid > 0 ? ensRec.nftid : null;
|
|
2933
|
+
const pubAvatar = ensRec && ensRec.texts && ensRec.texts.avatar || null;
|
|
2934
|
+
const punkId = (_a = me.punkId) != null ? _a : pubPunk;
|
|
2935
|
+
const upAvatar = me.avatarUrl || pubAvatar;
|
|
2933
2936
|
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" } }, upAvatar ? /* @__PURE__ */ React.createElement("img", { src: upAvatar, alt: "", width: 68, height: 68, style: { width: 68, height: 68, borderRadius: 16, objectFit: "cover", background: "var(--panel-2)" } }) : 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(
|
|
2934
2937
|
"input",
|
|
2935
2938
|
{
|
|
@@ -4983,7 +4986,8 @@ ${peer.address}`
|
|
|
4983
4986
|
onTakeover: () => window.BeagleWeb.takeover().then((st) => {
|
|
4984
4987
|
data.refresh();
|
|
4985
4988
|
return st;
|
|
4986
|
-
})
|
|
4989
|
+
}),
|
|
4990
|
+
onLocate: () => window.BeagleWeb.locate()
|
|
4987
4991
|
}
|
|
4988
4992
|
), !data.locked && welcome && /* @__PURE__ */ React.createElement(
|
|
4989
4993
|
DkWelcome,
|
package/dist/server.js
CHANGED
|
@@ -55,7 +55,7 @@ async function fetchDiscoverJson(url) {
|
|
|
55
55
|
// ── beagles.eth names (ens-gateway worker) ──────────────────────────────────
|
|
56
56
|
// The same registry the mobile apps register into. Web-side registration
|
|
57
57
|
// signs the record with THIS node's Carrier identity (XEdDSA via the backend
|
|
58
|
-
// `sign` op) — the gateway verifies it the same way Sign in with
|
|
58
|
+
// `sign` op) — the gateway verifies it the same way Sign in with Beagle does.
|
|
59
59
|
const ENS_GATEWAY = process.env.BEAGLE_ENS_GATEWAY || "https://ens-gateway.beaglechat.workers.dev";
|
|
60
60
|
async function ensFetchJson(path) {
|
|
61
61
|
const ctl = new AbortController();
|
|
@@ -414,7 +414,7 @@ function findSavedFile(downloadsDir, name) {
|
|
|
414
414
|
return null;
|
|
415
415
|
}
|
|
416
416
|
/** True only when the request came from the local machine. Used to gate the
|
|
417
|
-
* "Sign in with
|
|
417
|
+
* "Sign in with Beagle" routes so binding the UI to a LAN IP cannot expose
|
|
418
418
|
* identity signing to other hosts. The popup always runs in the local user's
|
|
419
419
|
* browser, so it connects over the loopback interface. */
|
|
420
420
|
function isLocalRequest(req) {
|
|
@@ -508,7 +508,7 @@ function validateOrigin(raw) {
|
|
|
508
508
|
return null;
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
|
-
// The "Sign in with
|
|
511
|
+
// The "Sign in with Beagle" consent popup. Self-contained (no external assets),
|
|
512
512
|
// dark theme matching the desktop UI. Reads origin+nonce from its own query,
|
|
513
513
|
// shows the requesting site and this node's identity, and on Approve calls the
|
|
514
514
|
// local /api/connect-approve to sign, then postMessages the result to the
|
|
@@ -517,7 +517,7 @@ function validateOrigin(raw) {
|
|
|
517
517
|
const CONNECT_PAGE = `<!doctype html>
|
|
518
518
|
<html lang="en"><head><meta charset="utf-8">
|
|
519
519
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
520
|
-
<title>Sign in with
|
|
520
|
+
<title>Sign in with Beagle</title>
|
|
521
521
|
<style>
|
|
522
522
|
:root{--bg:#0c0d11;--panel:#14161d;--line:#262a35;--text:#e7e9ef;--faint:#8b91a3;
|
|
523
523
|
--accent:#5b8cff;--good:#46a758;--bad:#e5604d;--mono:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
@@ -534,7 +534,9 @@ const CONNECT_PAGE = `<!doctype html>
|
|
|
534
534
|
.origin{color:var(--text);font-weight:600;word-break:break-all}
|
|
535
535
|
.id{background:#0e1016;border:1px solid var(--line);border-radius:10px;padding:11px 12px;margin-bottom:14px}
|
|
536
536
|
.lbl{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--faint);margin-bottom:4px}
|
|
537
|
+
.who{font-size:13.5px;font-weight:600;margin-bottom:3px}
|
|
537
538
|
.userid{font-family:var(--mono);font-size:12.5px;color:var(--text);word-break:break-all}
|
|
539
|
+
[hidden]{display:none !important}
|
|
538
540
|
.err{background:rgba(229,96,77,.12);border:1px solid rgba(229,96,77,.4);color:#ffb4a8;
|
|
539
541
|
border-radius:9px;padding:9px 11px;font-size:12.5px;margin-bottom:13px}
|
|
540
542
|
.row{display:flex;gap:10px;margin-top:4px}
|
|
@@ -547,44 +549,94 @@ const CONNECT_PAGE = `<!doctype html>
|
|
|
547
549
|
</style></head>
|
|
548
550
|
<body>
|
|
549
551
|
<div class="card">
|
|
550
|
-
<div class="brand"><span class="d"></span>
|
|
551
|
-
<h1>Sign in</h1>
|
|
552
|
-
<p class="sub"><span id="origin" class="origin">…</span> wants to sign you in with your
|
|
553
|
-
<div
|
|
552
|
+
<div class="brand"><span class="d"></span>Beagle</div>
|
|
553
|
+
<h1 id="headline">Sign in</h1>
|
|
554
|
+
<p class="sub"><span id="origin" class="origin">…</span><span id="leadTail"> wants to sign you in with your Beagle identity.</span></p>
|
|
555
|
+
<div id="targetRow" class="id" hidden>
|
|
556
|
+
<div class="lbl">Friend request to</div>
|
|
557
|
+
<div id="targetName" class="who" hidden></div>
|
|
558
|
+
<div id="targetAddr" class="userid"></div>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="id">
|
|
561
|
+
<div id="idLabel" class="lbl">Your identity</div>
|
|
562
|
+
<div id="who" class="who" hidden></div>
|
|
563
|
+
<div id="userid" class="userid">loading…</div>
|
|
564
|
+
</div>
|
|
554
565
|
<div id="err" class="err" hidden></div>
|
|
555
566
|
<div class="row">
|
|
556
567
|
<button id="deny" class="btn ghost">Deny</button>
|
|
557
568
|
<button id="approve" class="btn solid" disabled>Approve</button>
|
|
558
569
|
</div>
|
|
559
|
-
<p class="note">Approving sends the site a signature bound to that site, proving you control this identity. Your private key never leaves this device.</p>
|
|
570
|
+
<p id="note" class="note">Approving sends the site a signature bound to that site, proving you control this identity. Your private key never leaves this device.</p>
|
|
560
571
|
</div>
|
|
561
572
|
<script>
|
|
562
573
|
(function(){
|
|
563
574
|
var qs=new URLSearchParams(location.search);
|
|
564
575
|
var origin=qs.get("origin")||"", nonce=qs.get("nonce")||"";
|
|
576
|
+
// "signin" (default) or "add". One hands the site a signature; the other
|
|
577
|
+
// asks a stranger to be your friend. Both need consent, and only the second
|
|
578
|
+
// changes anything — so they are separate actions, never one grant.
|
|
579
|
+
var action=qs.get("action")==="add"?"add":"signin";
|
|
580
|
+
var target=(qs.get("address")||"").trim(), targetName=(qs.get("name")||"").trim();
|
|
565
581
|
var oEl=document.getElementById("origin"), errEl=document.getElementById("err");
|
|
566
582
|
var approve=document.getElementById("approve"), deny=document.getElementById("deny");
|
|
567
583
|
oEl.textContent=origin||"(unknown site)";
|
|
568
584
|
function fail(m){errEl.textContent=m;errEl.hidden=false;}
|
|
569
|
-
function reply(data){
|
|
585
|
+
function reply(data){
|
|
586
|
+
if(!window.opener||!validOrigin) return;
|
|
587
|
+
// A friend request is NOT an auth assertion and must never be mistaken for
|
|
588
|
+
// one by a listener written before this existed — hence its own type.
|
|
589
|
+
var base=action==="add"?{type:"beagle-action",nonce:nonce,action:"add"}:{type:"decent-auth",nonce:nonce};
|
|
590
|
+
window.opener.postMessage(Object.assign(base,data),origin);
|
|
591
|
+
}
|
|
570
592
|
var validOrigin=false;
|
|
571
593
|
try{var u=new URL(origin); validOrigin=(u.protocol==="http:"||u.protocol==="https:")&&u.origin===origin;}catch(e){}
|
|
572
594
|
if(!validOrigin) fail("This sign-in request has an invalid origin and was blocked.");
|
|
573
|
-
if(nonce.length===0||nonce.length>512){ validOrigin=false; fail("This
|
|
595
|
+
if(nonce.length===0||nonce.length>512){ validOrigin=false; fail("This request is missing a valid nonce."); }
|
|
596
|
+
if(action==="add"&&!(target.length>=50&&target.length<=60&&/^[1-9A-HJ-NP-Za-km-z]+$/.test(target))){
|
|
597
|
+
validOrigin=false; fail("That does not look like a Beagle address, so nothing was sent.");
|
|
598
|
+
}
|
|
599
|
+
if(action==="add"){
|
|
600
|
+
document.title="Add a friend on Beagle";
|
|
601
|
+
document.getElementById("headline").textContent="Add a friend";
|
|
602
|
+
// Set the TAIL, not the whole paragraph: rewriting it would remove the
|
|
603
|
+
// #origin span, and the site name would then render as "null".
|
|
604
|
+
document.getElementById("leadTail").textContent=" wants to send a friend request from your Beagle identity.";
|
|
605
|
+
document.getElementById("targetRow").hidden=false;
|
|
606
|
+
var tn=document.getElementById("targetName");
|
|
607
|
+
tn.textContent=targetName||""; tn.hidden=!targetName;
|
|
608
|
+
document.getElementById("targetAddr").textContent=target;
|
|
609
|
+
document.getElementById("idLabel").textContent="Sent as";
|
|
610
|
+
approve.textContent="Send request";
|
|
611
|
+
document.getElementById("note").textContent="The other person sees your name and has to accept. Nothing is shared with the site — it only asked; your Beagle sent it.";
|
|
612
|
+
}
|
|
574
613
|
fetch("/api/state").then(function(r){return r.json();}).then(function(s){
|
|
575
|
-
var uid=(s.me&&s.me.userid)||"";
|
|
614
|
+
var uid=(s.me&&s.me.userid)||"", nm=(s.me&&s.me.name)||"";
|
|
576
615
|
document.getElementById("userid").textContent=uid||"(no identity)";
|
|
616
|
+
if(nm){var w=document.getElementById("who"); w.textContent=nm; w.hidden=false;}
|
|
577
617
|
if(uid&&validOrigin) approve.disabled=false;
|
|
578
|
-
else if(!uid) fail("No local
|
|
618
|
+
else if(!uid) fail("No local Beagle identity found — is the daemon running?");
|
|
579
619
|
}).catch(function(){ fail("Could not reach the local agentnet daemon."); });
|
|
580
620
|
deny.onclick=function(){ reply({error:"denied"}); setTimeout(function(){window.close();},60); };
|
|
581
621
|
approve.onclick=function(){
|
|
582
|
-
approve.disabled=true;
|
|
622
|
+
approve.disabled=true;
|
|
623
|
+
if(action==="add"){
|
|
624
|
+
approve.textContent="Sending…";
|
|
625
|
+
fetch("/api/add",{method:"POST",headers:{"content-type":"application/json"},
|
|
626
|
+
body:JSON.stringify({address:target,hello:targetName?("Hi — we met on "+new URL(origin).host+"."):""})})
|
|
627
|
+
.then(function(r){return r.json();})
|
|
628
|
+
.then(function(j){ if(j.ok===false) throw new Error(j.error||"could not send");
|
|
629
|
+
reply({ok:true,address:target}); setTimeout(function(){window.close();},60); })
|
|
630
|
+
.catch(function(e){ approve.disabled=false; approve.textContent="Send request";
|
|
631
|
+
fail("Could not send the request: "+e.message); });
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
approve.textContent="Signing…";
|
|
583
635
|
fetch("/api/connect-approve",{method:"POST",headers:{"content-type":"application/json"},
|
|
584
636
|
body:JSON.stringify({origin:origin,nonce:nonce})})
|
|
585
637
|
.then(function(r){return r.json();})
|
|
586
638
|
.then(function(j){ if(!j.ok) throw new Error(j.error||"sign failed");
|
|
587
|
-
reply({userid:j.userid,sig:j.sig}); setTimeout(function(){window.close();},60); })
|
|
639
|
+
reply({userid:j.userid,sig:j.sig,name:j.name,avatar:j.avatar,punkId:j.punkId}); setTimeout(function(){window.close();},60); })
|
|
588
640
|
.catch(function(e){ approve.disabled=false; approve.textContent="Approve"; fail("Signing failed: "+e.message); });
|
|
589
641
|
};
|
|
590
642
|
})();
|
|
@@ -679,7 +731,7 @@ export function startBeagleServer(opts) {
|
|
|
679
731
|
res.end("not found");
|
|
680
732
|
return;
|
|
681
733
|
}
|
|
682
|
-
// ── Sign in with
|
|
734
|
+
// ── Sign in with Beagle ────────────────────────────────────────────
|
|
683
735
|
// A website opens http://localhost:8765/connect?origin=…&nonce=… in a
|
|
684
736
|
// popup; on Approve we sign "decent-auth\n<origin>\n<nonce>" with this
|
|
685
737
|
// node's Carrier identity and postMessage {userid,nonce,sig} back to the
|
|
@@ -690,7 +742,7 @@ export function startBeagleServer(opts) {
|
|
|
690
742
|
if (url === "/connect" || url === "/api/connect-approve") {
|
|
691
743
|
if (!isLocalRequest(req)) {
|
|
692
744
|
res.writeHead(403, { "content-type": "text/plain" });
|
|
693
|
-
res.end("Sign in with
|
|
745
|
+
res.end("Sign in with Beagle is available only on this machine (localhost).");
|
|
694
746
|
return;
|
|
695
747
|
}
|
|
696
748
|
}
|
|
@@ -719,11 +771,37 @@ export function startBeagleServer(opts) {
|
|
|
719
771
|
sendJson(res, 502, { ok: false, error: r.error || "sign failed" });
|
|
720
772
|
return;
|
|
721
773
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
774
|
+
const userid = r.data?.userid ?? "";
|
|
775
|
+
// Name and avatar ride along so the site can render the person without
|
|
776
|
+
// a second lookup it has no way to make — nothing public maps a userid
|
|
777
|
+
// to a display name unless that user registered a beagles.eth name.
|
|
778
|
+
// Additive: a caller that ignores them behaves exactly as before, and
|
|
779
|
+
// neither is part of what the signature proves.
|
|
780
|
+
let name = "";
|
|
781
|
+
let avatar = null;
|
|
782
|
+
let punkId = null;
|
|
783
|
+
const diagId = await opts.call({ op: "diag" });
|
|
784
|
+
try {
|
|
785
|
+
const diag = diagId;
|
|
786
|
+
name = ((diag.ok ? diag.data : {})?.node?.name) ?? "";
|
|
787
|
+
const pub = (await ensPublicByUserid(1200)).get(userid);
|
|
788
|
+
if (pub) {
|
|
789
|
+
if (!name)
|
|
790
|
+
name = pub.displayName ?? "";
|
|
791
|
+
avatar = pub.avatarUrl ?? null;
|
|
792
|
+
punkId = pub.punkId;
|
|
793
|
+
if (!avatar && pub.punkId != null) {
|
|
794
|
+
const punk = await punksFetch(`/api/punks/${pub.punkId}`).catch(() => null);
|
|
795
|
+
avatar = punk?.image ?? null;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
catch { /* identity is what matters; the trimmings are best-effort */ }
|
|
800
|
+
// The address, not just the userid: a friend request needs the full
|
|
801
|
+
// address (key + nospam + checksum), so without it a site that signed
|
|
802
|
+
// someone in still cannot offer to introduce them to anyone.
|
|
803
|
+
const address = ((diagId.ok ? diagId.data : {})?.identity?.address) ?? "";
|
|
804
|
+
sendJson(res, 200, { ok: true, userid, address, sig: r.data?.sig ?? "", name, avatar, punkId });
|
|
727
805
|
return;
|
|
728
806
|
}
|
|
729
807
|
// ── beagles.eth name registration ──
|
|
@@ -1321,9 +1399,13 @@ export function startBeagleServer(opts) {
|
|
|
1321
1399
|
const d = diag.ok ? (diag.data ?? {}) : {};
|
|
1322
1400
|
const identity = d.identity ?? {};
|
|
1323
1401
|
const tun = d.tun ?? {};
|
|
1402
|
+
const node = d.node ?? {};
|
|
1324
1403
|
const me = {
|
|
1325
1404
|
userid: identity.userid,
|
|
1326
1405
|
address: identity.address,
|
|
1406
|
+
// The sign-in popup shows this above the key: a userid alone does not
|
|
1407
|
+
// tell you which of your identities is about to answer.
|
|
1408
|
+
name: node.name ?? "",
|
|
1327
1409
|
// The configured TUN address is what this host can actually route.
|
|
1328
1410
|
// Prefer it over a stale init/default allocation during Dora outage.
|
|
1329
1411
|
ip: tun.ip ?? d.allocatedIp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"yargs": "^17.7.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@decentnetwork/beagle-ui": "0.1.
|
|
36
|
+
"@decentnetwork/beagle-ui": "0.1.2",
|
|
37
37
|
"@types/js-yaml": "^4.0.9",
|
|
38
38
|
"@types/node": "^20.11.0",
|
|
39
39
|
"@types/yargs": "^17.0.32",
|