@decentnetwork/beagle 0.1.60 → 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.60";
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
  {
package/dist/server.js CHANGED
@@ -550,10 +550,15 @@ const CONNECT_PAGE = `<!doctype html>
550
550
  <body>
551
551
  <div class="card">
552
552
  <div class="brand"><span class="d"></span>Beagle</div>
553
- <h1>Sign in</h1>
554
- <p class="sub"><span id="origin" class="origin">…</span> wants to sign you in with your Beagle identity.</p>
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>
555
560
  <div class="id">
556
- <div class="lbl">Your identity</div>
561
+ <div id="idLabel" class="lbl">Your identity</div>
557
562
  <div id="who" class="who" hidden></div>
558
563
  <div id="userid" class="userid">loading…</div>
559
564
  </div>
@@ -562,21 +567,49 @@ const CONNECT_PAGE = `<!doctype html>
562
567
  <button id="deny" class="btn ghost">Deny</button>
563
568
  <button id="approve" class="btn solid" disabled>Approve</button>
564
569
  </div>
565
- <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>
566
571
  </div>
567
572
  <script>
568
573
  (function(){
569
574
  var qs=new URLSearchParams(location.search);
570
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();
571
581
  var oEl=document.getElementById("origin"), errEl=document.getElementById("err");
572
582
  var approve=document.getElementById("approve"), deny=document.getElementById("deny");
573
583
  oEl.textContent=origin||"(unknown site)";
574
584
  function fail(m){errEl.textContent=m;errEl.hidden=false;}
575
- 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
+ }
576
592
  var validOrigin=false;
577
593
  try{var u=new URL(origin); validOrigin=(u.protocol==="http:"||u.protocol==="https:")&&u.origin===origin;}catch(e){}
578
594
  if(!validOrigin) fail("This sign-in request has an invalid origin and was blocked.");
579
- 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
+ }
580
613
  fetch("/api/state").then(function(r){return r.json();}).then(function(s){
581
614
  var uid=(s.me&&s.me.userid)||"", nm=(s.me&&s.me.name)||"";
582
615
  document.getElementById("userid").textContent=uid||"(no identity)";
@@ -586,12 +619,24 @@ const CONNECT_PAGE = `<!doctype html>
586
619
  }).catch(function(){ fail("Could not reach the local agentnet daemon."); });
587
620
  deny.onclick=function(){ reply({error:"denied"}); setTimeout(function(){window.close();},60); };
588
621
  approve.onclick=function(){
589
- 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…";
590
635
  fetch("/api/connect-approve",{method:"POST",headers:{"content-type":"application/json"},
591
636
  body:JSON.stringify({origin:origin,nonce:nonce})})
592
637
  .then(function(r){return r.json();})
593
638
  .then(function(j){ if(!j.ok) throw new Error(j.error||"sign failed");
594
- reply({userid:j.userid,sig:j.sig,name:j.name,avatar:j.avatar}); 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); })
595
640
  .catch(function(e){ approve.disabled=false; approve.textContent="Approve"; fail("Signing failed: "+e.message); });
596
641
  };
597
642
  })();
@@ -734,14 +779,17 @@ export function startBeagleServer(opts) {
734
779
  // neither is part of what the signature proves.
735
780
  let name = "";
736
781
  let avatar = null;
782
+ let punkId = null;
783
+ const diagId = await opts.call({ op: "diag" });
737
784
  try {
738
- const diag = await opts.call({ op: "diag" });
785
+ const diag = diagId;
739
786
  name = ((diag.ok ? diag.data : {})?.node?.name) ?? "";
740
787
  const pub = (await ensPublicByUserid(1200)).get(userid);
741
788
  if (pub) {
742
789
  if (!name)
743
790
  name = pub.displayName ?? "";
744
791
  avatar = pub.avatarUrl ?? null;
792
+ punkId = pub.punkId;
745
793
  if (!avatar && pub.punkId != null) {
746
794
  const punk = await punksFetch(`/api/punks/${pub.punkId}`).catch(() => null);
747
795
  avatar = punk?.image ?? null;
@@ -749,7 +797,11 @@ export function startBeagleServer(opts) {
749
797
  }
750
798
  }
751
799
  catch { /* identity is what matters; the trimmings are best-effort */ }
752
- sendJson(res, 200, { ok: true, userid, sig: r.data?.sig ?? "", name, avatar });
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 });
753
805
  return;
754
806
  }
755
807
  // ── beagles.eth name registration ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.60",
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.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",