@decentnetwork/beagle 0.1.60 → 0.1.63
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/carrier-node.d.ts +5 -0
- package/dist/carrier-node.js +2 -0
- package/dist/desktop/app.js +510 -417
- package/dist/embedded-host.d.ts +3 -0
- package/dist/embedded-host.js +20 -2
- package/dist/server.js +165 -16
- package/package.json +3 -3
package/dist/embedded-host.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export interface EmbeddedHostOptions {
|
|
|
18
18
|
}[];
|
|
19
19
|
nickname?: string;
|
|
20
20
|
statusMessage?: string;
|
|
21
|
+
/** Our CryptoPunks id, advertised to friends in the userinfo `gender`
|
|
22
|
+
* field — the only avatar path that does not need a server. */
|
|
23
|
+
punkId?: number;
|
|
21
24
|
autoAcceptFriends: boolean;
|
|
22
25
|
onProfileChange?: (info: {
|
|
23
26
|
name?: string;
|
package/dist/embedded-host.js
CHANGED
|
@@ -138,6 +138,9 @@ export class EmbeddedHost {
|
|
|
138
138
|
expressNodes: this.#opts.expressNodes,
|
|
139
139
|
nickname: this.#opts.nickname,
|
|
140
140
|
statusMessage: this.#opts.statusMessage,
|
|
141
|
+
// Advertised from startup, not only after a profile edit — otherwise a
|
|
142
|
+
// restart silently stops telling friends what our avatar is.
|
|
143
|
+
punkId: this.#opts.punkId,
|
|
141
144
|
fileResumeDir: resolve(this.#opts.configDir, "file-resume"),
|
|
142
145
|
});
|
|
143
146
|
this.#wire();
|
|
@@ -402,7 +405,9 @@ export class EmbeddedHost {
|
|
|
402
405
|
const id = this.#node.identity();
|
|
403
406
|
return {
|
|
404
407
|
identity: id,
|
|
405
|
-
|
|
408
|
+
// punkId so the UI can report what is actually being advertised,
|
|
409
|
+
// which is not necessarily what beagles.eth says.
|
|
410
|
+
node: { name: this.#opts.nickname ?? "", backend: "embedded", autoAccept: this.#autoAccept, punkId: this.#opts.punkId ?? null },
|
|
406
411
|
// No TUN in this backend — say so explicitly rather than omitting
|
|
407
412
|
// the key, so the UI can distinguish "no LAN" from "unknown".
|
|
408
413
|
tun: null,
|
|
@@ -448,6 +453,10 @@ export class EmbeddedHost {
|
|
|
448
453
|
f.status === "online" ||
|
|
449
454
|
this.#messages.hasInbound(f.carrierId),
|
|
450
455
|
lastSeen: f.acceptedAt,
|
|
456
|
+
// The avatar they advertised over Carrier (userinfo `gender`).
|
|
457
|
+
// Peer-to-peer, so it works for a friend who never registered a
|
|
458
|
+
// beagles.eth name — until now that meant an identicon.
|
|
459
|
+
punkId: f.punkId ?? null,
|
|
451
460
|
pinned: meta?.pinned ?? false,
|
|
452
461
|
lastMessage: lastMsg ? { dir: lastMsg.dir, text: lastMsg.text, ts: lastMsg.ts } : undefined,
|
|
453
462
|
unread: this.#messages.unreadCount(f.carrierId, meta?.lastReadTs ?? 0),
|
|
@@ -511,12 +520,21 @@ export class EmbeddedHost {
|
|
|
511
520
|
this.#meta.setAlias(uid, req.alias);
|
|
512
521
|
return {};
|
|
513
522
|
case "set-profile": {
|
|
514
|
-
const info = {
|
|
523
|
+
const info = {
|
|
524
|
+
name: req.name,
|
|
525
|
+
description: req.description,
|
|
526
|
+
// null clears, undefined leaves alone. setUserInfo re-sends the
|
|
527
|
+
// profile to every established friend, so a new avatar lands now
|
|
528
|
+
// rather than on their next reconnect.
|
|
529
|
+
punkId: req.punkId === undefined ? undefined : (req.punkId === null ? null : Number(req.punkId)),
|
|
530
|
+
};
|
|
515
531
|
this.#node.setUserInfo(info);
|
|
516
532
|
if (info.name)
|
|
517
533
|
this.#opts.nickname = info.name;
|
|
518
534
|
if (info.description !== undefined)
|
|
519
535
|
this.#opts.statusMessage = info.description;
|
|
536
|
+
if (info.punkId !== undefined)
|
|
537
|
+
this.#opts.punkId = info.punkId ?? undefined;
|
|
520
538
|
this.#opts.onProfileChange?.(info);
|
|
521
539
|
return {};
|
|
522
540
|
}
|
package/dist/server.js
CHANGED
|
@@ -273,7 +273,14 @@ function installMode() {
|
|
|
273
273
|
return "npx";
|
|
274
274
|
if (BEAGLE_ROOT.includes(join("node_modules", "@decentnetwork", "beagle")))
|
|
275
275
|
return "global";
|
|
276
|
-
|
|
276
|
+
// "dev" has to be PROVEN, not assumed. It used to be the fallthrough for
|
|
277
|
+
// any layout this function did not recognise, so a user with a perfectly
|
|
278
|
+
// ordinary install was told to run `git pull` — against a private repo they
|
|
279
|
+
// cannot clone, with no other option offered. A dead end presented as
|
|
280
|
+
// instructions.
|
|
281
|
+
if (existsSync(join(BEAGLE_ROOT, ".git")))
|
|
282
|
+
return "dev";
|
|
283
|
+
return "unknown";
|
|
277
284
|
}
|
|
278
285
|
async function npmLatest(pkg) {
|
|
279
286
|
const ctl = new AbortController();
|
|
@@ -550,10 +557,15 @@ const CONNECT_PAGE = `<!doctype html>
|
|
|
550
557
|
<body>
|
|
551
558
|
<div class="card">
|
|
552
559
|
<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>
|
|
560
|
+
<h1 id="headline">Sign in</h1>
|
|
561
|
+
<p class="sub"><span id="origin" class="origin">…</span><span id="leadTail"> wants to sign you in with your Beagle identity.</span></p>
|
|
562
|
+
<div id="targetRow" class="id" hidden>
|
|
563
|
+
<div class="lbl">Friend request to</div>
|
|
564
|
+
<div id="targetName" class="who" hidden></div>
|
|
565
|
+
<div id="targetAddr" class="userid"></div>
|
|
566
|
+
</div>
|
|
555
567
|
<div class="id">
|
|
556
|
-
<div class="lbl">Your identity</div>
|
|
568
|
+
<div id="idLabel" class="lbl">Your identity</div>
|
|
557
569
|
<div id="who" class="who" hidden></div>
|
|
558
570
|
<div id="userid" class="userid">loading…</div>
|
|
559
571
|
</div>
|
|
@@ -562,21 +574,49 @@ const CONNECT_PAGE = `<!doctype html>
|
|
|
562
574
|
<button id="deny" class="btn ghost">Deny</button>
|
|
563
575
|
<button id="approve" class="btn solid" disabled>Approve</button>
|
|
564
576
|
</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>
|
|
577
|
+
<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
578
|
</div>
|
|
567
579
|
<script>
|
|
568
580
|
(function(){
|
|
569
581
|
var qs=new URLSearchParams(location.search);
|
|
570
582
|
var origin=qs.get("origin")||"", nonce=qs.get("nonce")||"";
|
|
583
|
+
// "signin" (default) or "add". One hands the site a signature; the other
|
|
584
|
+
// asks a stranger to be your friend. Both need consent, and only the second
|
|
585
|
+
// changes anything — so they are separate actions, never one grant.
|
|
586
|
+
var action=qs.get("action")==="add"?"add":"signin";
|
|
587
|
+
var target=(qs.get("address")||"").trim(), targetName=(qs.get("name")||"").trim();
|
|
571
588
|
var oEl=document.getElementById("origin"), errEl=document.getElementById("err");
|
|
572
589
|
var approve=document.getElementById("approve"), deny=document.getElementById("deny");
|
|
573
590
|
oEl.textContent=origin||"(unknown site)";
|
|
574
591
|
function fail(m){errEl.textContent=m;errEl.hidden=false;}
|
|
575
|
-
function reply(data){
|
|
592
|
+
function reply(data){
|
|
593
|
+
if(!window.opener||!validOrigin) return;
|
|
594
|
+
// A friend request is NOT an auth assertion and must never be mistaken for
|
|
595
|
+
// one by a listener written before this existed — hence its own type.
|
|
596
|
+
var base=action==="add"?{type:"beagle-action",nonce:nonce,action:"add"}:{type:"decent-auth",nonce:nonce};
|
|
597
|
+
window.opener.postMessage(Object.assign(base,data),origin);
|
|
598
|
+
}
|
|
576
599
|
var validOrigin=false;
|
|
577
600
|
try{var u=new URL(origin); validOrigin=(u.protocol==="http:"||u.protocol==="https:")&&u.origin===origin;}catch(e){}
|
|
578
601
|
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
|
|
602
|
+
if(nonce.length===0||nonce.length>512){ validOrigin=false; fail("This request is missing a valid nonce."); }
|
|
603
|
+
if(action==="add"&&!(target.length>=50&&target.length<=60&&/^[1-9A-HJ-NP-Za-km-z]+$/.test(target))){
|
|
604
|
+
validOrigin=false; fail("That does not look like a Beagle address, so nothing was sent.");
|
|
605
|
+
}
|
|
606
|
+
if(action==="add"){
|
|
607
|
+
document.title="Add a friend on Beagle";
|
|
608
|
+
document.getElementById("headline").textContent="Add a friend";
|
|
609
|
+
// Set the TAIL, not the whole paragraph: rewriting it would remove the
|
|
610
|
+
// #origin span, and the site name would then render as "null".
|
|
611
|
+
document.getElementById("leadTail").textContent=" wants to send a friend request from your Beagle identity.";
|
|
612
|
+
document.getElementById("targetRow").hidden=false;
|
|
613
|
+
var tn=document.getElementById("targetName");
|
|
614
|
+
tn.textContent=targetName||""; tn.hidden=!targetName;
|
|
615
|
+
document.getElementById("targetAddr").textContent=target;
|
|
616
|
+
document.getElementById("idLabel").textContent="Sent as";
|
|
617
|
+
approve.textContent="Send request";
|
|
618
|
+
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.";
|
|
619
|
+
}
|
|
580
620
|
fetch("/api/state").then(function(r){return r.json();}).then(function(s){
|
|
581
621
|
var uid=(s.me&&s.me.userid)||"", nm=(s.me&&s.me.name)||"";
|
|
582
622
|
document.getElementById("userid").textContent=uid||"(no identity)";
|
|
@@ -586,12 +626,24 @@ const CONNECT_PAGE = `<!doctype html>
|
|
|
586
626
|
}).catch(function(){ fail("Could not reach the local agentnet daemon."); });
|
|
587
627
|
deny.onclick=function(){ reply({error:"denied"}); setTimeout(function(){window.close();},60); };
|
|
588
628
|
approve.onclick=function(){
|
|
589
|
-
approve.disabled=true;
|
|
629
|
+
approve.disabled=true;
|
|
630
|
+
if(action==="add"){
|
|
631
|
+
approve.textContent="Sending…";
|
|
632
|
+
fetch("/api/add",{method:"POST",headers:{"content-type":"application/json"},
|
|
633
|
+
body:JSON.stringify({address:target,hello:targetName?("Hi — we met on "+new URL(origin).host+"."):""})})
|
|
634
|
+
.then(function(r){return r.json();})
|
|
635
|
+
.then(function(j){ if(j.ok===false) throw new Error(j.error||"could not send");
|
|
636
|
+
reply({ok:true,address:target}); setTimeout(function(){window.close();},60); })
|
|
637
|
+
.catch(function(e){ approve.disabled=false; approve.textContent="Send request";
|
|
638
|
+
fail("Could not send the request: "+e.message); });
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
approve.textContent="Signing…";
|
|
590
642
|
fetch("/api/connect-approve",{method:"POST",headers:{"content-type":"application/json"},
|
|
591
643
|
body:JSON.stringify({origin:origin,nonce:nonce})})
|
|
592
644
|
.then(function(r){return r.json();})
|
|
593
645
|
.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); })
|
|
646
|
+
reply({userid:j.userid,sig:j.sig,name:j.name,avatar:j.avatar,punkId:j.punkId}); setTimeout(function(){window.close();},60); })
|
|
595
647
|
.catch(function(e){ approve.disabled=false; approve.textContent="Approve"; fail("Signing failed: "+e.message); });
|
|
596
648
|
};
|
|
597
649
|
})();
|
|
@@ -615,6 +667,10 @@ const fmtTime = (ts) => {
|
|
|
615
667
|
return `${d.getDate()} ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][d.getMonth()]}`;
|
|
616
668
|
};
|
|
617
669
|
const viaFromTransport = (t) => t === "udp" || t === "both" ? "direct" : t === "tcp-relay" ? "relay" : null;
|
|
670
|
+
// What we last told the daemon to advertise. Module-scoped on purpose: the
|
|
671
|
+
// value is per-process, and re-sending it on every /api/desktop poll would
|
|
672
|
+
// re-push the profile to every friend several times a second.
|
|
673
|
+
let lastAdvertisedPunk;
|
|
618
674
|
export function startBeagleServer(opts) {
|
|
619
675
|
const host = opts.listenHost ?? "127.0.0.1";
|
|
620
676
|
const port = opts.listenPort ?? 8765;
|
|
@@ -640,6 +696,32 @@ export function startBeagleServer(opts) {
|
|
|
640
696
|
const handler = async (req, res) => {
|
|
641
697
|
try {
|
|
642
698
|
const url = (req.url || "/").split("?")[0];
|
|
699
|
+
// Let Sign-in-with-Beagle pages (meet, billing, …) detect this daemon
|
|
700
|
+
// from a public https origin. Chrome Local Network Access blocks the
|
|
701
|
+
// probe unless we answer the private-network preflight.
|
|
702
|
+
const detect = url === "/api/state" || url === "/api/ping" || url === "/connect";
|
|
703
|
+
if (detect) {
|
|
704
|
+
const origin = String(req.headers.origin || "");
|
|
705
|
+
if (origin) {
|
|
706
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
707
|
+
res.setHeader("Vary", "Origin");
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
711
|
+
}
|
|
712
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
713
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
714
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
715
|
+
if (req.method === "OPTIONS") {
|
|
716
|
+
res.writeHead(204);
|
|
717
|
+
res.end();
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (req.method === "GET" && url === "/api/ping") {
|
|
722
|
+
sendJson(res, 200, { ok: true });
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
643
725
|
// Desktop UI bundle (the design). Falls back to the classic page when
|
|
644
726
|
// the bundle hasn't been built (dist/ui/desktop missing).
|
|
645
727
|
const desktopIndex = join(DESKTOP_DIR, "index.html");
|
|
@@ -694,7 +776,7 @@ export function startBeagleServer(opts) {
|
|
|
694
776
|
// LOCALHOST-ONLY: the popup always runs in the local user's browser
|
|
695
777
|
// (localhost), so binding the UI to a LAN IP must never let a remote
|
|
696
778
|
// host request signatures with this identity.
|
|
697
|
-
if (url === "/connect" || url === "/api/connect-approve") {
|
|
779
|
+
if (url === "/connect" || url === "/api/connect-approve" || url === "/api/launch-token") {
|
|
698
780
|
if (!isLocalRequest(req)) {
|
|
699
781
|
res.writeHead(403, { "content-type": "text/plain" });
|
|
700
782
|
res.end("Sign in with Beagle is available only on this machine (localhost).");
|
|
@@ -734,14 +816,17 @@ export function startBeagleServer(opts) {
|
|
|
734
816
|
// neither is part of what the signature proves.
|
|
735
817
|
let name = "";
|
|
736
818
|
let avatar = null;
|
|
819
|
+
let punkId = null;
|
|
820
|
+
const diagId = await opts.call({ op: "diag" });
|
|
737
821
|
try {
|
|
738
|
-
const diag =
|
|
822
|
+
const diag = diagId;
|
|
739
823
|
name = ((diag.ok ? diag.data : {})?.node?.name) ?? "";
|
|
740
824
|
const pub = (await ensPublicByUserid(1200)).get(userid);
|
|
741
825
|
if (pub) {
|
|
742
826
|
if (!name)
|
|
743
827
|
name = pub.displayName ?? "";
|
|
744
828
|
avatar = pub.avatarUrl ?? null;
|
|
829
|
+
punkId = pub.punkId;
|
|
745
830
|
if (!avatar && pub.punkId != null) {
|
|
746
831
|
const punk = await punksFetch(`/api/punks/${pub.punkId}`).catch(() => null);
|
|
747
832
|
avatar = punk?.image ?? null;
|
|
@@ -749,7 +834,47 @@ export function startBeagleServer(opts) {
|
|
|
749
834
|
}
|
|
750
835
|
}
|
|
751
836
|
catch { /* identity is what matters; the trimmings are best-effort */ }
|
|
752
|
-
|
|
837
|
+
// The address, not just the userid: a friend request needs the full
|
|
838
|
+
// address (key + nospam + checksum), so without it a site that signed
|
|
839
|
+
// someone in still cannot offer to introduce them to anyone.
|
|
840
|
+
const address = ((diagId.ok ? diagId.data : {})?.identity?.address) ?? "";
|
|
841
|
+
sendJson(res, 200, { ok: true, userid, address, sig: r.data?.sig ?? "", name, avatar, punkId });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Mint the "launch an app as me" assertion the Apps tab puts in an app's
|
|
845
|
+
// URL fragment, so someone opening an app from their own client is not
|
|
846
|
+
// asked to sign in to it. LOCALHOST-ONLY like /connect: it is a
|
|
847
|
+
// signature with this identity.
|
|
848
|
+
if (req.method === "POST" && url === "/api/launch-token") {
|
|
849
|
+
const body = await readBody(req);
|
|
850
|
+
const target = validateOrigin(body.origin);
|
|
851
|
+
if (!target) {
|
|
852
|
+
sendJson(res, 400, { ok: false, error: "invalid origin" });
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const ts = Date.now();
|
|
856
|
+
// A DIFFERENT prefix from decent-auth on purpose: an assertion minted
|
|
857
|
+
// here must never be replayable as a "a site asked me to sign in"
|
|
858
|
+
// proof, nor the reverse.
|
|
859
|
+
const signed = await opts.call({ op: "sign", text: `decent-launch\n${target}\n${ts}` });
|
|
860
|
+
if (!signed.ok) {
|
|
861
|
+
sendJson(res, 502, { ok: false, error: signed.error || "sign failed" });
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const diag = await opts.call({ op: "diag" });
|
|
865
|
+
const d = (diag.ok ? diag.data : {}) ?? {};
|
|
866
|
+
const identity = d.identity ?? {};
|
|
867
|
+
const node = d.node ?? {};
|
|
868
|
+
sendJson(res, 200, {
|
|
869
|
+
ok: true,
|
|
870
|
+
v: 1,
|
|
871
|
+
userid: signed.data?.userid ?? identity.userid ?? "",
|
|
872
|
+
address: identity.address ?? "",
|
|
873
|
+
name: node.name ?? "",
|
|
874
|
+
punkId: null,
|
|
875
|
+
ts,
|
|
876
|
+
sig: signed.data?.sig ?? "",
|
|
877
|
+
});
|
|
753
878
|
return;
|
|
754
879
|
}
|
|
755
880
|
// ── beagles.eth name registration ──
|
|
@@ -1515,6 +1640,10 @@ export function startBeagleServer(opts) {
|
|
|
1515
1640
|
sendJson(res, 400, { ok: false, error: "running from a source checkout — update with: git pull && npm run build" });
|
|
1516
1641
|
return;
|
|
1517
1642
|
}
|
|
1643
|
+
// Layout we do not recognise: try the thing that works for almost
|
|
1644
|
+
// everyone rather than refusing. If it genuinely cannot install, the
|
|
1645
|
+
// npm error says so — which is still more use than a git command
|
|
1646
|
+
// against a repo the user has no access to.
|
|
1518
1647
|
if (mode === "npx") {
|
|
1519
1648
|
// npx with @latest re-resolves on every launch: restarting IS the update.
|
|
1520
1649
|
sendJson(res, 200, { ok: true, npx: true });
|
|
@@ -1592,6 +1721,20 @@ export function startBeagleServer(opts) {
|
|
|
1592
1721
|
]);
|
|
1593
1722
|
const d = (diag.ok ? diag.data : {}) ?? {};
|
|
1594
1723
|
const identity = d.identity ?? {};
|
|
1724
|
+
// A desktop install has no locally-picked punk: its avatar is whatever
|
|
1725
|
+
// it registered on beagles.eth. Advertise THAT over Carrier, so an iOS
|
|
1726
|
+
// friend sees a picture instead of nothing. Sent only when the value
|
|
1727
|
+
// CHANGES — set-profile re-pushes the profile to every established
|
|
1728
|
+
// friend, and doing that on every poll would be a packet storm.
|
|
1729
|
+
const myPunk = ensPub.get(identity.userid ?? "")?.punkId ?? null;
|
|
1730
|
+
// Only ever PUSH a real punk, never a null. A null here means "nothing
|
|
1731
|
+
// registered on beagles.eth", which is not the same as "no avatar" —
|
|
1732
|
+
// pushing it wiped a punk set directly through /api/set-profile, on
|
|
1733
|
+
// the very next poll. Registration seeds the avatar; it does not own it.
|
|
1734
|
+
if (myPunk != null && myPunk !== lastAdvertisedPunk) {
|
|
1735
|
+
lastAdvertisedPunk = myPunk;
|
|
1736
|
+
void opts.call({ op: "set-profile", punkId: myPunk }).catch(() => undefined);
|
|
1737
|
+
}
|
|
1595
1738
|
const tun = d.tun ?? {};
|
|
1596
1739
|
const node = d.node ?? {};
|
|
1597
1740
|
const diagFriends = d.friends ?? [];
|
|
@@ -1647,7 +1790,7 @@ export function startBeagleServer(opts) {
|
|
|
1647
1790
|
// toggle rather than showing a state it cannot know.
|
|
1648
1791
|
autoAccept: typeof node.autoAccept === "boolean" ? node.autoAccept : null,
|
|
1649
1792
|
avatarUrl: ensPub.get(identity.userid ?? "")?.avatarUrl ?? null,
|
|
1650
|
-
punkId:
|
|
1793
|
+
punkId: myPunk ?? node.punkId ?? null,
|
|
1651
1794
|
// The UI compares this against its baked-in __DK_UI_VERSION and
|
|
1652
1795
|
// reloads itself when they differ — an open tab otherwise runs a
|
|
1653
1796
|
// stale bundle forever after a beagle update (multi-tab call fixes
|
|
@@ -1688,7 +1831,11 @@ export function startBeagleServer(opts) {
|
|
|
1688
1831
|
lastTs: lm?.ts ?? 0,
|
|
1689
1832
|
wire: "163",
|
|
1690
1833
|
avatarUrl: ensPub.get(uid)?.avatarUrl ?? null,
|
|
1691
|
-
|
|
1834
|
+
// What they advertised over Carrier wins over the directory: it is
|
|
1835
|
+
// what they are using on their device right now, it needs no
|
|
1836
|
+
// gateway, and it is the only avatar a friend who never registered
|
|
1837
|
+
// a beagles.eth name will ever have.
|
|
1838
|
+
punkId: f.punkId ?? ensPub.get(uid)?.punkId ?? null,
|
|
1692
1839
|
};
|
|
1693
1840
|
});
|
|
1694
1841
|
const pend = pending.ok ? (pending.data?.pending ?? []) : [];
|
|
@@ -1921,8 +2068,10 @@ export function startBeagleServer(opts) {
|
|
|
1921
2068
|
return;
|
|
1922
2069
|
}
|
|
1923
2070
|
if (req.method === "POST" && url === "/api/set-profile") {
|
|
1924
|
-
|
|
1925
|
-
|
|
2071
|
+
// punkId passes through so an avatar can be set without a beagles.eth
|
|
2072
|
+
// registration — the daemon advertises it over Carrier either way.
|
|
2073
|
+
const { name, description, punkId } = await readBody(req);
|
|
2074
|
+
const r = await opts.call({ op: "set-profile", name, description, punkId });
|
|
1926
2075
|
sendJson(res, r.ok ? 200 : 400, r);
|
|
1927
2076
|
return;
|
|
1928
2077
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
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",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
29
29
|
"@decentnetwork/lan": "^0.1.283",
|
|
30
|
-
"@decentnetwork/peer": "
|
|
30
|
+
"@decentnetwork/peer": "0.1.143",
|
|
31
31
|
"@decentnetwork/peer-webrtc": "^0.2.16",
|
|
32
32
|
"js-yaml": "^4.1.0",
|
|
33
33
|
"yargs": "^17.7.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@decentnetwork/beagle-ui": "0.
|
|
36
|
+
"@decentnetwork/beagle-ui": "0.2.0",
|
|
37
37
|
"@types/js-yaml": "^4.0.9",
|
|
38
38
|
"@types/node": "^20.11.0",
|
|
39
39
|
"@types/yargs": "^17.0.32",
|