@decentnetwork/beagle 0.1.122 → 0.1.124
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 +9 -0
- package/dist/carrier-node.js +7 -0
- package/dist/desktop/app.js +59 -2
- package/dist/embedded-host.js +7 -0
- package/dist/pending-requests.d.ts +28 -0
- package/dist/pending-requests.js +69 -0
- package/dist/server.js +4 -12
- package/package.json +4 -4
package/dist/carrier-node.d.ts
CHANGED
|
@@ -97,6 +97,15 @@ export declare class CarrierNode extends EventEmitter {
|
|
|
97
97
|
force?: boolean;
|
|
98
98
|
}): Promise<void>;
|
|
99
99
|
acceptFriendRequest(pubkey: string): Promise<void>;
|
|
100
|
+
/** The SDK's pending inbound requests (empty before create or on an SDK
|
|
101
|
+
* without the call). Re-read when answering friends-pending: a late
|
|
102
|
+
* profile patches the SDK's entry without firing a new event. */
|
|
103
|
+
pendingFriendRequests(): Array<{
|
|
104
|
+
pubkey: string;
|
|
105
|
+
userid?: string;
|
|
106
|
+
punkId?: number;
|
|
107
|
+
avatarUrl?: string;
|
|
108
|
+
}>;
|
|
100
109
|
removeFriend(userid: string): boolean;
|
|
101
110
|
/** Decline an inbound request and clear what it created in the SDK. */
|
|
102
111
|
rejectFriendRequest(pubkey: string): void;
|
package/dist/carrier-node.js
CHANGED
|
@@ -186,6 +186,13 @@ export class CarrierNode extends EventEmitter {
|
|
|
186
186
|
acceptFriendRequest(pubkey) {
|
|
187
187
|
return this.#require().acceptFriendRequest(pubkey);
|
|
188
188
|
}
|
|
189
|
+
/** The SDK's pending inbound requests (empty before create or on an SDK
|
|
190
|
+
* without the call). Re-read when answering friends-pending: a late
|
|
191
|
+
* profile patches the SDK's entry without firing a new event. */
|
|
192
|
+
pendingFriendRequests() {
|
|
193
|
+
const list = this.#peer?.pendingFriendRequests?.();
|
|
194
|
+
return Array.isArray(list) ? list : [];
|
|
195
|
+
}
|
|
189
196
|
removeFriend(userid) {
|
|
190
197
|
return this.#require().removeFriend(userid);
|
|
191
198
|
}
|
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.124";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -1697,6 +1697,37 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
|
1697
1697
|
}, [append, lang, onReloadFriends]);
|
|
1698
1698
|
return { thread, handle, removeFriends, adoptServicePeers };
|
|
1699
1699
|
}
|
|
1700
|
+
var REQUEST_AVATAR_HOST = "ens-gateway.beaglechat.workers.dev";
|
|
1701
|
+
var MAX_URL = 2048;
|
|
1702
|
+
function requestAvatarUrl(r) {
|
|
1703
|
+
const u = r && r.avatarUrl;
|
|
1704
|
+
if (typeof u !== "string" || !u || u.length > MAX_URL)
|
|
1705
|
+
return null;
|
|
1706
|
+
let parsed;
|
|
1707
|
+
try {
|
|
1708
|
+
parsed = new URL(u);
|
|
1709
|
+
} catch {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
if (parsed.protocol !== "https:" || parsed.host !== REQUEST_AVATAR_HOST)
|
|
1713
|
+
return null;
|
|
1714
|
+
if (parsed.username || parsed.password)
|
|
1715
|
+
return null;
|
|
1716
|
+
return parsed.href;
|
|
1717
|
+
}
|
|
1718
|
+
function requestPunkId(r) {
|
|
1719
|
+
const p = r && r.punkId;
|
|
1720
|
+
return typeof p === "number" && Number.isInteger(p) && p >= 0 ? p : null;
|
|
1721
|
+
}
|
|
1722
|
+
function requestAvatarSource(r, skipUrl = false) {
|
|
1723
|
+
const url = skipUrl ? null : requestAvatarUrl(r);
|
|
1724
|
+
if (url)
|
|
1725
|
+
return { kind: "url", url };
|
|
1726
|
+
const id = requestPunkId(r);
|
|
1727
|
+
if (id != null)
|
|
1728
|
+
return { kind: "punk", id };
|
|
1729
|
+
return { kind: "ens" };
|
|
1730
|
+
}
|
|
1700
1731
|
var DK_CMD_HIST_KEY = "beagle.assistant.history";
|
|
1701
1732
|
var DK_CMD_HIST_MAX = 50;
|
|
1702
1733
|
function RequestsBlock({ T, requests, onAct }) {
|
|
@@ -1740,7 +1771,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
|
1740
1771
|
display: "flex",
|
|
1741
1772
|
flexDirection: "column",
|
|
1742
1773
|
gap: 8
|
|
1743
|
-
} }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(
|
|
1774
|
+
} }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkRequestAvatar, { r, size: 30, radius: 7 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, r.name ? /* @__PURE__ */ React.createElement("div", { style: {
|
|
1744
1775
|
fontFamily: "var(--ui)",
|
|
1745
1776
|
fontSize: 13,
|
|
1746
1777
|
fontWeight: 600,
|
|
@@ -1757,6 +1788,32 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
|
1757
1788
|
paddingLeft: 8
|
|
1758
1789
|
} }, r.descr), failed[r.id] && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11, color: "var(--danger)" } }, failed[r.id]), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => act(r, "accept"), style: { flex: 1 } }, T.accept), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "x", size: "sm", onClick: () => act(r, "reject"), style: { flex: 1 } }, T.reject))))));
|
|
1759
1790
|
}
|
|
1791
|
+
function DkRequestAvatar({ r, size, radius }) {
|
|
1792
|
+
const [urlBroken, setUrlBroken] = React.useState(false);
|
|
1793
|
+
const url = requestAvatarUrl(r);
|
|
1794
|
+
React.useEffect(() => {
|
|
1795
|
+
setUrlBroken(false);
|
|
1796
|
+
}, [url]);
|
|
1797
|
+
const src = requestAvatarSource(r, urlBroken);
|
|
1798
|
+
const seed = r.carrier || r.userid || r.id;
|
|
1799
|
+
if (src.kind === "url") {
|
|
1800
|
+
return /* @__PURE__ */ React.createElement(
|
|
1801
|
+
"img",
|
|
1802
|
+
{
|
|
1803
|
+
src: src.url,
|
|
1804
|
+
alt: "",
|
|
1805
|
+
width: size,
|
|
1806
|
+
height: size,
|
|
1807
|
+
referrerPolicy: "no-referrer",
|
|
1808
|
+
onError: () => setUrlBroken(true),
|
|
1809
|
+
style: { width: size, height: size, borderRadius: radius, objectFit: "cover", flexShrink: 0, background: "var(--panel-2)" }
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
if (src.kind === "punk")
|
|
1814
|
+
return /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: src.id, size, radius, fallbackSeed: seed });
|
|
1815
|
+
return /* @__PURE__ */ React.createElement(DkEnsAvatar, { userid: r.userid || r.id, name: r.name, fallbackSeed: r.carrier, size, radius });
|
|
1816
|
+
}
|
|
1760
1817
|
function PeerRow({ peer, T, active, onClick, muted, isProtocol, fix }) {
|
|
1761
1818
|
const name = peer.alias || peer.userId;
|
|
1762
1819
|
const row = rosterRow(peer, isProtocol || (() => false), fix);
|
package/dist/embedded-host.js
CHANGED
|
@@ -18,6 +18,7 @@ import { MessageStore } from "./message-store.js";
|
|
|
18
18
|
import { FriendMetaStore } from "./friend-meta.js";
|
|
19
19
|
import { Logger } from "./logger.js";
|
|
20
20
|
import { acquireIdentityLock } from "./identity-lock.js";
|
|
21
|
+
import { refreshPendingAvatars, requesterAvatar } from "./pending-requests.js";
|
|
21
22
|
/** Files bigger than this are not queued for an offline friend — the bytes sit
|
|
22
23
|
* on disk until they reconnect, and an unbounded queue is how you fill a
|
|
23
24
|
* user's disk by accident. Live transfers have no such limit. */
|
|
@@ -228,6 +229,9 @@ export class EmbeddedHost {
|
|
|
228
229
|
name: req.name,
|
|
229
230
|
descr: req.description,
|
|
230
231
|
hello: req.hello,
|
|
232
|
+
// The requester's punk / avatar URL (peer >= 0.1.170), so the card can
|
|
233
|
+
// draw it before accept. A later profile is picked up in friends-pending.
|
|
234
|
+
...requesterAvatar(req),
|
|
231
235
|
arrivedAt: Date.now(),
|
|
232
236
|
});
|
|
233
237
|
this.#events.emit("event", { type: "friend-request", userid });
|
|
@@ -493,6 +497,9 @@ export class EmbeddedHost {
|
|
|
493
497
|
return {};
|
|
494
498
|
}
|
|
495
499
|
case "friends-pending":
|
|
500
|
+
// Re-read the SDK's list: a requester's profile that arrived after its
|
|
501
|
+
// request patches only the SDK's entry, with no new event.
|
|
502
|
+
refreshPendingAvatars(this.#pending.values(), this.#node.pendingFriendRequests());
|
|
496
503
|
return { pending: [...this.#pending.values()] };
|
|
497
504
|
case "friends-accept": {
|
|
498
505
|
const entry = this.#pending.get(uid);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface RequesterAvatar {
|
|
2
|
+
punkId?: number;
|
|
3
|
+
avatarUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
/** The valid avatar fields of an SDK / backend friend-request record. Keys are
|
|
6
|
+
* present only with a value; punk 0 is a real CryptoPunk. */
|
|
7
|
+
export declare function requesterAvatar(src: {
|
|
8
|
+
punkId?: unknown;
|
|
9
|
+
avatarUrl?: unknown;
|
|
10
|
+
} | undefined | null): RequesterAvatar;
|
|
11
|
+
/** Fold the SDK's current pending list into the host's own entries, matched by
|
|
12
|
+
* pubkey or userid. A value the SDK has wins (a late profile can change it); a
|
|
13
|
+
* value only the host has is kept. Mutates `entries`; returns whether anything
|
|
14
|
+
* changed. */
|
|
15
|
+
export declare function refreshPendingAvatars<E extends {
|
|
16
|
+
userid: string;
|
|
17
|
+
pubkey: string;
|
|
18
|
+
} & RequesterAvatar>(entries: Iterable<E>, live: ReadonlyArray<{
|
|
19
|
+
pubkey?: string;
|
|
20
|
+
userid?: string;
|
|
21
|
+
punkId?: unknown;
|
|
22
|
+
avatarUrl?: unknown;
|
|
23
|
+
}>): boolean;
|
|
24
|
+
/** One `/api/desktop` `requests[]` row from a friends-pending entry (daemon or
|
|
25
|
+
* embedded). `punkId` / `avatarUrl` are null when the requester sent none —
|
|
26
|
+
* the same convention as `peers[]` — and the card then falls back to the
|
|
27
|
+
* beagles.eth lookup and a generated picture. */
|
|
28
|
+
export declare function desktopRequestRow(p: Record<string, unknown>, i: number, fmtTime: (ts?: number) => string): Record<string, unknown>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The requester's avatar on a pending friend request, before anyone accepts.
|
|
2
|
+
//
|
|
3
|
+
// peer >= 0.1.170 carries the requester's punk (friendreq field 3, `gender`)
|
|
4
|
+
// and custom avatar URL (field 4) in the request itself, and fills them from a
|
|
5
|
+
// profile the requester pushed before accept. When that profile arrives after
|
|
6
|
+
// the request, the SDK patches only its own pending list — no new event — so
|
|
7
|
+
// a host that copied the event once must re-read peer.pendingFriendRequests()
|
|
8
|
+
// to learn it. lan >= 0.1.313 does that for a daemon backend; the embedded host
|
|
9
|
+
// does it here. See decent-hq docs/tasks/TASK-2026-11-01-avatar-on-friend-request.md.
|
|
10
|
+
import { punkIdOf } from "./friend-store.js";
|
|
11
|
+
/** The valid avatar fields of an SDK / backend friend-request record. Keys are
|
|
12
|
+
* present only with a value; punk 0 is a real CryptoPunk. */
|
|
13
|
+
export function requesterAvatar(src) {
|
|
14
|
+
const out = {};
|
|
15
|
+
if (!src)
|
|
16
|
+
return out;
|
|
17
|
+
const punk = punkIdOf(src.punkId);
|
|
18
|
+
if (punk !== undefined && Number.isInteger(punk))
|
|
19
|
+
out.punkId = punk;
|
|
20
|
+
if (typeof src.avatarUrl === "string" && src.avatarUrl.length > 0)
|
|
21
|
+
out.avatarUrl = src.avatarUrl;
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
/** Fold the SDK's current pending list into the host's own entries, matched by
|
|
25
|
+
* pubkey or userid. A value the SDK has wins (a late profile can change it); a
|
|
26
|
+
* value only the host has is kept. Mutates `entries`; returns whether anything
|
|
27
|
+
* changed. */
|
|
28
|
+
export function refreshPendingAvatars(entries, live) {
|
|
29
|
+
if (!live.length)
|
|
30
|
+
return false;
|
|
31
|
+
let changed = false;
|
|
32
|
+
for (const e of entries) {
|
|
33
|
+
const l = live.find((x) => (!!x.pubkey && x.pubkey === e.pubkey) || (!!x.userid && x.userid === e.userid));
|
|
34
|
+
if (!l)
|
|
35
|
+
continue;
|
|
36
|
+
const a = requesterAvatar(l);
|
|
37
|
+
if (a.punkId !== undefined && a.punkId !== e.punkId) {
|
|
38
|
+
e.punkId = a.punkId;
|
|
39
|
+
changed = true;
|
|
40
|
+
}
|
|
41
|
+
if (a.avatarUrl !== undefined && a.avatarUrl !== e.avatarUrl) {
|
|
42
|
+
e.avatarUrl = a.avatarUrl;
|
|
43
|
+
changed = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return changed;
|
|
47
|
+
}
|
|
48
|
+
/** One `/api/desktop` `requests[]` row from a friends-pending entry (daemon or
|
|
49
|
+
* embedded). `punkId` / `avatarUrl` are null when the requester sent none —
|
|
50
|
+
* the same convention as `peers[]` — and the card then falls back to the
|
|
51
|
+
* beagles.eth lookup and a generated picture. */
|
|
52
|
+
export function desktopRequestRow(p, i, fmtTime) {
|
|
53
|
+
const a = requesterAvatar(p);
|
|
54
|
+
return {
|
|
55
|
+
id: p.userid || p.address || `r${i}`,
|
|
56
|
+
carrier: p.address || p.userid || "",
|
|
57
|
+
userid: p.userid || "",
|
|
58
|
+
// Name and introduction travel in the friend-request packet and were
|
|
59
|
+
// being dropped here, so every request looked identical and there
|
|
60
|
+
// was nothing to decide on.
|
|
61
|
+
name: p.name || "",
|
|
62
|
+
descr: p.descr || "",
|
|
63
|
+
via: p.hello || "lan",
|
|
64
|
+
time: fmtTime(p.arrivedAt),
|
|
65
|
+
// The requester's avatar, drawn before accept: URL first, then punk.
|
|
66
|
+
punkId: a.punkId ?? null,
|
|
67
|
+
avatarUrl: a.avatarUrl ?? null,
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -26,6 +26,7 @@ import { bridgeRoster } from "./bridge-roster.js";
|
|
|
26
26
|
import { isSupervised } from "./relaunch.js";
|
|
27
27
|
import { historyStats } from "./assistant-stats.js";
|
|
28
28
|
import { readFriendStore, pickPunk } from "./friend-store.js";
|
|
29
|
+
import { desktopRequestRow } from "./pending-requests.js";
|
|
29
30
|
import { createDiscoverFetch } from "./discover-cache.js";
|
|
30
31
|
import { applyWalletBind } from "@decentnetwork/beagle-ui/wallet-proof";
|
|
31
32
|
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
@@ -2093,18 +2094,9 @@ export function startBeagleServer(opts) {
|
|
|
2093
2094
|
};
|
|
2094
2095
|
});
|
|
2095
2096
|
const pend = pending.ok ? (pending.data?.pending ?? []) : [];
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
userid: p.userid || "",
|
|
2100
|
-
// Name and introduction travel in the friend-request packet and were
|
|
2101
|
-
// being dropped here, so every request looked identical and there
|
|
2102
|
-
// was nothing to decide on.
|
|
2103
|
-
name: p.name || "",
|
|
2104
|
-
descr: p.descr || "",
|
|
2105
|
-
via: p.hello || "lan",
|
|
2106
|
-
time: fmtTime(p.arrivedAt),
|
|
2107
|
-
}));
|
|
2097
|
+
// Same rows for both backends: lan >= 0.1.313 and the embedded host
|
|
2098
|
+
// both answer friends-pending with punkId / avatarUrl when known.
|
|
2099
|
+
const requests = pend.map((p, i) => desktopRequestRow(p, i, fmtTime));
|
|
2108
2100
|
// Exit nodes = the AVAILABLE exits this node knows about, grouped by
|
|
2109
2101
|
// region. The authoritative list is the shipped DEFAULT_EXITS
|
|
2110
2102
|
// (config/default-exits.yaml) — every install has it, so the panel is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.124",
|
|
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",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"start": "node dist/cli.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
29
|
+
"@decentnetwork/beagle-ui": "^0.2.35",
|
|
30
30
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
31
|
-
"@decentnetwork/lan": "^0.1.
|
|
32
|
-
"@decentnetwork/peer": "^0.1.
|
|
31
|
+
"@decentnetwork/lan": "^0.1.313",
|
|
32
|
+
"@decentnetwork/peer": "^0.1.170",
|
|
33
33
|
"@decentnetwork/peer-webrtc": "^0.2.17",
|
|
34
34
|
"js-yaml": "^4.1.0",
|
|
35
35
|
"ws": "^8.21.1",
|