@decentnetwork/beagle 0.1.91 → 0.1.93
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 +2 -2
- package/dist/server.js +114 -25
- package/package.json +1 -1
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.93";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -2443,7 +2443,7 @@ ${peer.address}`
|
|
|
2443
2443
|
const register = () => {
|
|
2444
2444
|
const l = label.trim();
|
|
2445
2445
|
if (l)
|
|
2446
|
-
post("/api/ens-register", { name: l, displayName: me.name });
|
|
2446
|
+
post("/api/ens-register", { name: l, displayName: me.name, description: me.description || "" });
|
|
2447
2447
|
};
|
|
2448
2448
|
const bind = (chain, address) => post("/api/ens-bind-wallet", { chain, address });
|
|
2449
2449
|
const bindEth = async () => {
|
package/dist/server.js
CHANGED
|
@@ -75,6 +75,73 @@ async function ensFetchJson(path) {
|
|
|
75
75
|
clearTimeout(timer);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/** requestedAt / acceptedAt per friend, read from the SDK's own friend store
|
|
79
|
+
* (`<keyFile>.friends.json`, next to the identity). The daemon's friends-list
|
|
80
|
+
* reports neither for a request we sent and only acceptedAt (as lastSeen)
|
|
81
|
+
* otherwise, so a friend just added had no stamp at all and sorted to the
|
|
82
|
+
* bottom — the third time this was reported. The store is the source of
|
|
83
|
+
* truth both backends write, and it is a file in our own config dir, so
|
|
84
|
+
* read it. Cached by mtime: the list is polled every 2.5s per tab. */
|
|
85
|
+
let friendStampsCache = null;
|
|
86
|
+
function friendStoreStamps(keyFile) {
|
|
87
|
+
const empty = new Map();
|
|
88
|
+
if (!keyFile)
|
|
89
|
+
return empty;
|
|
90
|
+
const path = `${keyFile}.friends.json`;
|
|
91
|
+
try {
|
|
92
|
+
const st = statSync(path);
|
|
93
|
+
if (friendStampsCache && friendStampsCache.mtimeMs === st.mtimeMs)
|
|
94
|
+
return friendStampsCache.map;
|
|
95
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
96
|
+
const recs = Array.isArray(raw)
|
|
97
|
+
? raw
|
|
98
|
+
: raw && typeof raw === "object" && Array.isArray(raw.friends)
|
|
99
|
+
? raw.friends
|
|
100
|
+
: Object.values(raw ?? {});
|
|
101
|
+
const map = new Map();
|
|
102
|
+
for (const r of recs) {
|
|
103
|
+
if (!r || typeof r !== "object")
|
|
104
|
+
continue;
|
|
105
|
+
const id = r.userid || r.pubkey;
|
|
106
|
+
if (!id)
|
|
107
|
+
continue;
|
|
108
|
+
map.set(id, {
|
|
109
|
+
requestedAt: typeof r.requestedAt === "number" ? r.requestedAt : undefined,
|
|
110
|
+
acceptedAt: typeof r.acceptedAt === "number" ? r.acceptedAt : undefined,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
friendStampsCache = { mtimeMs: st.mtimeMs, map };
|
|
114
|
+
return map;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Unreadable or absent (a fresh install has no friends yet): whatever we
|
|
118
|
+
// last read, else nothing — the list still renders, just without stamps.
|
|
119
|
+
return friendStampsCache?.map ?? empty;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** The record beagle writes for its own name: what the profile page shows
|
|
123
|
+
* (displayName, description) on top of whatever the record already held —
|
|
124
|
+
* wallet binds, socials, avatar, referee — so a profile edit never strips a
|
|
125
|
+
* bind. Shared by /api/ens-register and /api/set-profile; the two used to
|
|
126
|
+
* build it separately, and only register did, which is why a status message
|
|
127
|
+
* edited in Profile never reached https://app.beagle.chat/<name>. */
|
|
128
|
+
function ensProfileRecord(identity, name, carried, displayName, description) {
|
|
129
|
+
return {
|
|
130
|
+
name,
|
|
131
|
+
owner: identity.userid,
|
|
132
|
+
addresses: carried?.addresses ?? {},
|
|
133
|
+
texts: {
|
|
134
|
+
...(carried?.texts ?? {}),
|
|
135
|
+
description,
|
|
136
|
+
displayName,
|
|
137
|
+
carrierAddress: identity.address,
|
|
138
|
+
carrierUserId: identity.userid,
|
|
139
|
+
},
|
|
140
|
+
referee: carried?.referee ?? "",
|
|
141
|
+
nft: carried?.nft ?? "",
|
|
142
|
+
nftid: carried?.nftid ?? 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
78
145
|
/** Sign a name record with the Carrier identity and POST it to the gateway.
|
|
79
146
|
* The record itself (as a JSON string) is what gets signed; the gateway
|
|
80
147
|
* re-parses `signature.message` and verifies against `owner` inside it. */
|
|
@@ -1219,24 +1286,15 @@ export function startBeagleServer(opts) {
|
|
|
1219
1286
|
const mine = existing ?? (await ensFetchJson(`/getAddress/${encodeURIComponent(identity.userid)}`));
|
|
1220
1287
|
const carried = mine && mine.owner === identity.userid ? mine : null;
|
|
1221
1288
|
const displayName = (typeof body.displayName === "string" && body.displayName.trim().slice(0, 64)) || label;
|
|
1289
|
+
// The description IS the Profile status message. It used to fall
|
|
1290
|
+
// through to "<name>'s Beagle Chat Profile", a placeholder that then
|
|
1291
|
+
// sat on the public page as if the person had written it.
|
|
1292
|
+
const node = (diag.ok ? diag.data?.node : null) ?? {};
|
|
1222
1293
|
const description = (typeof body.description === "string" && body.description.trim().slice(0, 200)) ||
|
|
1294
|
+
(typeof node.statusMessage === "string" && node.statusMessage.trim().slice(0, 200)) ||
|
|
1223
1295
|
carried?.texts?.description ||
|
|
1224
|
-
|
|
1225
|
-
const r = await ensSignAndSet(opts.call, {
|
|
1226
|
-
name: fullName,
|
|
1227
|
-
owner: identity.userid,
|
|
1228
|
-
addresses: carried?.addresses ?? {},
|
|
1229
|
-
texts: {
|
|
1230
|
-
...(carried?.texts ?? {}),
|
|
1231
|
-
description,
|
|
1232
|
-
displayName,
|
|
1233
|
-
carrierAddress: identity.address,
|
|
1234
|
-
carrierUserId: identity.userid,
|
|
1235
|
-
},
|
|
1236
|
-
referee: carried?.referee ?? "",
|
|
1237
|
-
nft: carried?.nft ?? "",
|
|
1238
|
-
nftid: carried?.nftid ?? 0,
|
|
1239
|
-
});
|
|
1296
|
+
"";
|
|
1297
|
+
const r = await ensSignAndSet(opts.call, ensProfileRecord({ userid: identity.userid, address: identity.address }, fullName, carried, displayName, description));
|
|
1240
1298
|
sendJson(res, r.ok ? 200 : 502, r.ok ? { ok: true, name: fullName } : { ok: false, error: r.error });
|
|
1241
1299
|
return;
|
|
1242
1300
|
}
|
|
@@ -1952,10 +2010,12 @@ export function startBeagleServer(opts) {
|
|
|
1952
2010
|
beagleVer: beagleVersion(),
|
|
1953
2011
|
};
|
|
1954
2012
|
const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
|
|
2013
|
+
const stamps = friendStoreStamps(opts.callIce?.keyFile);
|
|
1955
2014
|
const peers = fl.map((f) => {
|
|
1956
2015
|
const uid = f.userid ?? "";
|
|
1957
2016
|
const sess = sessByUserid.get(uid);
|
|
1958
2017
|
const status = f.status;
|
|
2018
|
+
const stamp = stamps.get(uid);
|
|
1959
2019
|
const online = status === "connected" || status === "online";
|
|
1960
2020
|
const realName = typeof f.name === "string" && f.name !== uid ? f.name : undefined;
|
|
1961
2021
|
const lm = f.lastMessage;
|
|
@@ -1968,10 +2028,12 @@ export function startBeagleServer(opts) {
|
|
|
1968
2028
|
address: f.address || "",
|
|
1969
2029
|
online,
|
|
1970
2030
|
// Friendship not yet accepted by the other side. Only trusted when
|
|
1971
|
-
// the backend states it explicitly
|
|
1972
|
-
//
|
|
1973
|
-
//
|
|
1974
|
-
|
|
2031
|
+
// the backend states it explicitly: the embedded host's `accepted`,
|
|
2032
|
+
// or the SDK status "requested" that the daemon passes through
|
|
2033
|
+
// (the SDK demotes it the moment the other side proves itself).
|
|
2034
|
+
// Anything else defaults to established, so the banner can never
|
|
2035
|
+
// wrongly appear on an old backend.
|
|
2036
|
+
pending: f.accepted === false || status === "requested",
|
|
1975
2037
|
via: online ? viaFromTransport(sess?.transport) : null,
|
|
1976
2038
|
ping: null,
|
|
1977
2039
|
ip: ipByUserid.get(uid) ?? "",
|
|
@@ -1991,11 +2053,13 @@ export function startBeagleServer(opts) {
|
|
|
1991
2053
|
// The sidebar ranks a friend by the newest of these and the last
|
|
1992
2054
|
// message, so someone you just accepted — or just added — lands
|
|
1993
2055
|
// at the top like a fresh message would, not in a no-history
|
|
1994
|
-
// group at the bottom.
|
|
1995
|
-
//
|
|
1996
|
-
//
|
|
1997
|
-
|
|
1998
|
-
|
|
2056
|
+
// group at the bottom. The backend's own fields first; the SDK's
|
|
2057
|
+
// friend store on disk fills what the backend does not report — a
|
|
2058
|
+
// daemon backend gives only lastSeen (its acceptedAt) and nothing
|
|
2059
|
+
// for a request we sent, which left a just-added friend with no
|
|
2060
|
+
// stamp at all.
|
|
2061
|
+
acceptedAt: f.lastSeen ?? stamp?.acceptedAt ?? 0,
|
|
2062
|
+
requestedAt: f.requestedAt ?? stamp?.requestedAt ?? 0,
|
|
1999
2063
|
wire: "163",
|
|
2000
2064
|
avatarUrl: ensPub.get(uid)?.avatarUrl ?? null,
|
|
2001
2065
|
// What they advertised over Carrier wins over the directory: it is
|
|
@@ -2257,6 +2321,31 @@ export function startBeagleServer(opts) {
|
|
|
2257
2321
|
const { name, description, punkId } = await readBody(req);
|
|
2258
2322
|
const r = await opts.call({ op: "set-profile", name, description, punkId });
|
|
2259
2323
|
sendJson(res, r.ok ? 200 : 400, r);
|
|
2324
|
+
// Also the public record, when this identity registered a beagles.eth
|
|
2325
|
+
// name here: displayName and description on the record ARE the
|
|
2326
|
+
// profile name and status message, and editing them in Profile used
|
|
2327
|
+
// to change only what Carrier advertises, never the page. Best
|
|
2328
|
+
// effort, after the reply: the gateway being down must not turn a
|
|
2329
|
+
// profile save into a failure. A punk-only call (the avatar advertise
|
|
2330
|
+
// on poll) carries neither field and leaves the record alone.
|
|
2331
|
+
if (r.ok && (typeof name === "string" || typeof description === "string")) {
|
|
2332
|
+
void (async () => {
|
|
2333
|
+
const diag = await opts.call({ op: "diag" });
|
|
2334
|
+
const identity = (diag.ok ? diag.data?.identity : null) ?? {};
|
|
2335
|
+
if (!identity.userid || !identity.address)
|
|
2336
|
+
return;
|
|
2337
|
+
const mine = await ensFetchJson(`/getAddress/${encodeURIComponent(identity.userid)}`);
|
|
2338
|
+
if (!mine || mine.owner !== identity.userid)
|
|
2339
|
+
return; // not registered here — nothing to sign
|
|
2340
|
+
const displayName = (typeof name === "string" && name.trim().slice(0, 64)) ||
|
|
2341
|
+
mine.texts?.displayName ||
|
|
2342
|
+
mine.name.replace(/\.beagles\.eth$/, "");
|
|
2343
|
+
const desc = typeof description === "string" ? description.trim().slice(0, 200) : (mine.texts?.description ?? "");
|
|
2344
|
+
const w = await ensSignAndSet(opts.call, ensProfileRecord({ userid: identity.userid, address: identity.address }, mine.name, mine, displayName, desc));
|
|
2345
|
+
if (!w.ok)
|
|
2346
|
+
console.warn(`[ens] profile saved, but the ${mine.name} record was not updated: ${w.error}`);
|
|
2347
|
+
})().catch((e) => console.warn(`[ens] record update after profile save failed: ${e.message}`));
|
|
2348
|
+
}
|
|
2260
2349
|
return;
|
|
2261
2350
|
}
|
|
2262
2351
|
if (req.method === "GET" && url === "/api/routes") {
|
package/package.json
CHANGED