@decentnetwork/beagle 0.1.117 → 0.1.118
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 +9 -3
- package/dist/discover-cache.d.ts +43 -0
- package/dist/discover-cache.js +90 -0
- package/dist/server.js +6 -23
- package/package.json +2 -2
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.118";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -1340,7 +1340,9 @@ window.__DK_UI_VERSION="0.1.117";
|
|
|
1340
1340
|
userId: ASSISTANT_ID,
|
|
1341
1341
|
alias: "Beagle Assistant",
|
|
1342
1342
|
address: "",
|
|
1343
|
-
online:
|
|
1343
|
+
// Not a peer, so not "online": there is no session, nothing to be present
|
|
1344
|
+
// on, and no dot is drawn for an `app` row anywhere in the UI.
|
|
1345
|
+
online: false,
|
|
1344
1346
|
app: true,
|
|
1345
1347
|
agent: false,
|
|
1346
1348
|
pending: false,
|
|
@@ -1782,7 +1784,7 @@ ${lines}`, { assistant: { kind: "adopt", rows: fresh } }));
|
|
|
1782
1784
|
textOverflow: "ellipsis",
|
|
1783
1785
|
flex: 1,
|
|
1784
1786
|
minWidth: 0
|
|
1785
|
-
} }, dkRowPreview(row.preview)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, row.lastTime), row.unread ? /* @__PURE__ */ React.createElement(Unread, { n: row.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
1787
|
+
} }, dkRowPreview(row.preview)))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, row.lastTime), peer.app ? null : row.unread ? /* @__PURE__ */ React.createElement(Unread, { n: row.unread }) : peer.pending ? /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--warn, #d29922)", title: "friend request pending" }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
1786
1788
|
}
|
|
1787
1789
|
function dkActivityTs(p, isProtocol, fix) {
|
|
1788
1790
|
const row = rosterRow(p, isProtocol || (() => false), fix);
|
|
@@ -4387,6 +4389,8 @@ ${peer.address}`
|
|
|
4387
4389
|
}
|
|
4388
4390
|
function DkAvatar({ peer, size = 30, radius = 7, dot = true }) {
|
|
4389
4391
|
const d = Math.max(8, size * 0.3);
|
|
4392
|
+
if (peer && peer.app)
|
|
4393
|
+
dot = false;
|
|
4390
4394
|
return /* @__PURE__ */ React.createElement("div", { style: { position: "relative", width: size, height: size, flexShrink: 0 } }, peer.agent ? /* @__PURE__ */ React.createElement(DkBot, { seed: peer.id, size, radius }) : peer.avatarUrl ? /* @__PURE__ */ React.createElement(DkImgAvatar, { url: peer.avatarUrl, seed: peer.userId || peer.id, size, radius }) : peer.punkId != null ? /* @__PURE__ */ React.createElement(DkPunkAvatar, { id: peer.punkId, size, radius, fallbackSeed: peer.userId || peer.id }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId || peer.id, size, radius }), dot && /* @__PURE__ */ React.createElement("span", { style: {
|
|
4391
4395
|
position: "absolute",
|
|
4392
4396
|
right: -2,
|
|
@@ -6132,6 +6136,8 @@ ${peer.address}`
|
|
|
6132
6136
|
return;
|
|
6133
6137
|
const byId = /* @__PURE__ */ new Map();
|
|
6134
6138
|
for (const p of peers || []) {
|
|
6139
|
+
if (p && p.app)
|
|
6140
|
+
continue;
|
|
6135
6141
|
const dir = p.lastDir || (String(p.lastMsg || "").startsWith("you: ") ? "out" : "in");
|
|
6136
6142
|
const quiet = typeof isProtocol === "function" && lastIsProtocol(p, isProtocol);
|
|
6137
6143
|
byId.set(p.id, { ts: p.lastTs || 0, dir, quiet });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached fetches for the discovery directories (the leaderboard and the
|
|
3
|
+
* beagles.eth /names listing).
|
|
4
|
+
*
|
|
5
|
+
* This used to cache successes only. When the gateway was down every UI poll —
|
|
6
|
+
* one every 2.5 s, per open tab, per machine — started a fresh request, so the
|
|
7
|
+
* upstream got hammered exactly when it was least able to answer. On
|
|
8
|
+
* 2026-09-28 the beagle servers on one home network were about 82% of the
|
|
9
|
+
* gateway's traffic and the day's total reached 139k requests against a 100k
|
|
10
|
+
* free limit. A failing upstream must cost LESS traffic than a working one,
|
|
11
|
+
* not more.
|
|
12
|
+
*
|
|
13
|
+
* Three rules, and they are the whole module:
|
|
14
|
+
* • a success is cached for `ttlMs` (5 minutes),
|
|
15
|
+
* • a failure is remembered for `failTtlMs` (60 s) and nothing is refetched
|
|
16
|
+
* inside that window — a stale body is served if we have one, because a
|
|
17
|
+
* five-minute-old directory listing is worth more than an error,
|
|
18
|
+
* • one in-flight request per URL, shared by every caller, so the poll that
|
|
19
|
+
* arrives while a fetch is running joins it instead of starting another.
|
|
20
|
+
*/
|
|
21
|
+
export interface DiscoverFetchOptions {
|
|
22
|
+
/** How long a good answer stays good. */
|
|
23
|
+
ttlMs?: number;
|
|
24
|
+
/** How long a failure suppresses refetching. */
|
|
25
|
+
failTtlMs?: number;
|
|
26
|
+
/** Abort a request that takes longer than this. */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** Injected for tests. */
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
now?: () => number;
|
|
31
|
+
}
|
|
32
|
+
export declare const DISCOVER_TTL_MS: number;
|
|
33
|
+
export declare const DISCOVER_FAIL_TTL_MS = 60000;
|
|
34
|
+
export interface DiscoverFetch {
|
|
35
|
+
(url: string): Promise<unknown>;
|
|
36
|
+
/** Forget everything about this URL: the next call refetches. Used when we
|
|
37
|
+
* KNOW the upstream changed (we just wrote a record), so the Names tab
|
|
38
|
+
* reflects it now instead of in five minutes. A request already in flight
|
|
39
|
+
* when this is called carries pre-change data, so its result is discarded
|
|
40
|
+
* rather than cached. */
|
|
41
|
+
invalidate(url: string): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createDiscoverFetch(opts?: DiscoverFetchOptions): DiscoverFetch;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached fetches for the discovery directories (the leaderboard and the
|
|
3
|
+
* beagles.eth /names listing).
|
|
4
|
+
*
|
|
5
|
+
* This used to cache successes only. When the gateway was down every UI poll —
|
|
6
|
+
* one every 2.5 s, per open tab, per machine — started a fresh request, so the
|
|
7
|
+
* upstream got hammered exactly when it was least able to answer. On
|
|
8
|
+
* 2026-09-28 the beagle servers on one home network were about 82% of the
|
|
9
|
+
* gateway's traffic and the day's total reached 139k requests against a 100k
|
|
10
|
+
* free limit. A failing upstream must cost LESS traffic than a working one,
|
|
11
|
+
* not more.
|
|
12
|
+
*
|
|
13
|
+
* Three rules, and they are the whole module:
|
|
14
|
+
* • a success is cached for `ttlMs` (5 minutes),
|
|
15
|
+
* • a failure is remembered for `failTtlMs` (60 s) and nothing is refetched
|
|
16
|
+
* inside that window — a stale body is served if we have one, because a
|
|
17
|
+
* five-minute-old directory listing is worth more than an error,
|
|
18
|
+
* • one in-flight request per URL, shared by every caller, so the poll that
|
|
19
|
+
* arrives while a fetch is running joins it instead of starting another.
|
|
20
|
+
*/
|
|
21
|
+
export const DISCOVER_TTL_MS = 5 * 60_000;
|
|
22
|
+
export const DISCOVER_FAIL_TTL_MS = 60_000;
|
|
23
|
+
export function createDiscoverFetch(opts = {}) {
|
|
24
|
+
const ttlMs = opts.ttlMs ?? DISCOVER_TTL_MS;
|
|
25
|
+
const failTtlMs = opts.failTtlMs ?? DISCOVER_FAIL_TTL_MS;
|
|
26
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
27
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
28
|
+
const now = opts.now ?? Date.now;
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
const inFlight = new Map();
|
|
31
|
+
const generation = new Map();
|
|
32
|
+
const fetchDiscoverJson = async function fetchDiscoverJson(url) {
|
|
33
|
+
const hit = cache.get(url);
|
|
34
|
+
if (hit && hit.at !== undefined && now() - hit.at < ttlMs)
|
|
35
|
+
return hit.body;
|
|
36
|
+
// Someone is already asking. Join them rather than making it worse — under
|
|
37
|
+
// a slow upstream this is the difference between one request and one per
|
|
38
|
+
// poll per tab.
|
|
39
|
+
const running = inFlight.get(url);
|
|
40
|
+
if (running)
|
|
41
|
+
return running;
|
|
42
|
+
if (hit && hit.failedAt !== undefined && now() - hit.failedAt < failTtlMs) {
|
|
43
|
+
// Inside the failure window nothing is fetched. Stale beats nothing;
|
|
44
|
+
// with nothing to serve, the caller gets the error it would have got.
|
|
45
|
+
if (hit.at !== undefined)
|
|
46
|
+
return hit.body;
|
|
47
|
+
throw hit.error ?? new Error("upstream unavailable");
|
|
48
|
+
}
|
|
49
|
+
const gen = generation.get(url) ?? 0;
|
|
50
|
+
const attempt = (async () => {
|
|
51
|
+
const ctl = new AbortController();
|
|
52
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
53
|
+
try {
|
|
54
|
+
const r = await doFetch(url, { signal: ctl.signal });
|
|
55
|
+
if (!r.ok)
|
|
56
|
+
throw new Error(`upstream ${r.status}`);
|
|
57
|
+
const body = await r.json();
|
|
58
|
+
if ((generation.get(url) ?? 0) === gen)
|
|
59
|
+
cache.set(url, { body, at: now() });
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
64
|
+
const prev = cache.get(url);
|
|
65
|
+
// Keep whatever good body we had: it is what the next 60 s will serve.
|
|
66
|
+
if ((generation.get(url) ?? 0) === gen) {
|
|
67
|
+
cache.set(url, { body: prev?.body, at: prev?.at, failedAt: now(), error: err });
|
|
68
|
+
}
|
|
69
|
+
if (prev && prev.at !== undefined)
|
|
70
|
+
return prev.body;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
// After an invalidate the entry is already gone and a newer request
|
|
76
|
+
// may own the slot; the generation says which of us this is.
|
|
77
|
+
if ((generation.get(url) ?? 0) === gen)
|
|
78
|
+
inFlight.delete(url);
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
inFlight.set(url, attempt);
|
|
82
|
+
return attempt;
|
|
83
|
+
};
|
|
84
|
+
fetchDiscoverJson.invalidate = (url) => {
|
|
85
|
+
cache.delete(url);
|
|
86
|
+
inFlight.delete(url);
|
|
87
|
+
generation.set(url, (generation.get(url) ?? 0) + 1);
|
|
88
|
+
};
|
|
89
|
+
return fetchDiscoverJson;
|
|
90
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -25,6 +25,7 @@ import * as peerAddr from "@decentnetwork/peer";
|
|
|
25
25
|
import { bridgeRoster } from "./bridge-roster.js";
|
|
26
26
|
import { isSupervised } from "./relaunch.js";
|
|
27
27
|
import { historyStats } from "./assistant-stats.js";
|
|
28
|
+
import { createDiscoverFetch } from "./discover-cache.js";
|
|
28
29
|
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
29
30
|
// scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
|
|
30
31
|
const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
@@ -33,28 +34,10 @@ const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
|
33
34
|
// (which probes all exits on each poll) stays responsive over a lossy mesh.
|
|
34
35
|
const EXIT_PROBE_PORT = 8888;
|
|
35
36
|
const EXIT_PROBE_TIMEOUT_MS = 2000;
|
|
36
|
-
/** Server-side fetch for the discovery directories (leaderboard / ENS names)
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
async function fetchDiscoverJson(url) {
|
|
41
|
-
const hit = discoverCache.get(url);
|
|
42
|
-
if (hit && Date.now() - hit.at < DISCOVER_CACHE_TTL_MS)
|
|
43
|
-
return hit.body;
|
|
44
|
-
const ctl = new AbortController();
|
|
45
|
-
const timer = setTimeout(() => ctl.abort(), 10_000);
|
|
46
|
-
try {
|
|
47
|
-
const r = await fetch(url, { signal: ctl.signal });
|
|
48
|
-
if (!r.ok)
|
|
49
|
-
throw new Error(`upstream ${r.status}`);
|
|
50
|
-
const body = await r.json();
|
|
51
|
-
discoverCache.set(url, { at: Date.now(), body });
|
|
52
|
-
return body;
|
|
53
|
-
}
|
|
54
|
-
finally {
|
|
55
|
-
clearTimeout(timer);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
37
|
+
/** Server-side fetch for the discovery directories (leaderboard / ENS names).
|
|
38
|
+
* Successes are cached, FAILURES ARE TOO, and concurrent callers share one
|
|
39
|
+
* request — see discover-cache.ts for the outage this comes from. */
|
|
40
|
+
const fetchDiscoverJson = createDiscoverFetch();
|
|
58
41
|
// ── beagles.eth names (ens-gateway worker) ──────────────────────────────────
|
|
59
42
|
// The same registry the mobile apps register into. Web-side registration
|
|
60
43
|
// signs the record with THIS node's Carrier identity (XEdDSA via the backend
|
|
@@ -190,7 +173,7 @@ async function ensSignAndSet(call, record) {
|
|
|
190
173
|
}
|
|
191
174
|
// The record changed — drop the cached /names listing so the Names tab
|
|
192
175
|
// reflects it immediately instead of after the 5-min TTL.
|
|
193
|
-
|
|
176
|
+
fetchDiscoverJson.invalidate(`${ENS_GATEWAY}/names`);
|
|
194
177
|
return { ok: true };
|
|
195
178
|
}
|
|
196
179
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.118",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"yargs": "^17.7.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
38
|
+
"@decentnetwork/beagle-ui": "^0.2.32",
|
|
39
39
|
"@types/js-yaml": "^4.0.9",
|
|
40
40
|
"@types/node": "^20.11.0",
|
|
41
41
|
"@types/yargs": "^17.0.32",
|