@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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.59";
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 punkId = ensRec && ensRec.nft === "CryptoPunks" && ensRec.nftid > 0 ? ensRec.nftid : null;
2932
- const upAvatar = ensRec && ensRec.texts && ensRec.texts.avatar || null;
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 Decent does.
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 Decent" routes so binding the UI to a LAN IP can't expose
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 Decent" consent popup. Self-contained (no external assets),
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 Decent</title>
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>Decent</div>
551
- <h1>Sign in</h1>
552
- <p class="sub"><span id="origin" class="origin">…</span> wants to sign you in with your Decent identity.</p>
553
- <div class="id"><div class="lbl">Your identity</div><div id="userid" class="userid">loading…</div></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){ if(window.opener&&validOrigin){ window.opener.postMessage(Object.assign({type:"decent-auth",nonce:nonce},data),origin);} }
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 sign-in request is missing a valid nonce."); }
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 Decent identity found — is the daemon running?");
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; approve.textContent="Signing…";
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 Decent ────────────────────────────────────────────
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 Decent is available only on this machine (localhost).");
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
- sendJson(res, 200, {
723
- ok: true,
724
- userid: r.data?.userid ?? "",
725
- sig: r.data?.sig ?? "",
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.59",
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.0",
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",