@decentnetwork/beagle 0.1.9 → 0.1.10
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 +6 -2
- package/dist/embedded-host.d.ts +2 -0
- package/dist/embedded-host.js +54 -26
- package/dist/server.js +10 -1
- 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.10";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
@@ -509,12 +509,16 @@ function dkRtcSignalBus() {
|
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
async function pollLoop() {
|
|
512
|
+
let cursor = null;
|
|
512
513
|
while (!stopped) {
|
|
513
514
|
let signals = [];
|
|
514
515
|
try {
|
|
515
|
-
const
|
|
516
|
+
const u = cursor == null ? "/api/call-poll" : "/api/call-poll?since=" + cursor;
|
|
517
|
+
const r = await fetch(u, { headers: { "cache-control": "no-cache" } });
|
|
516
518
|
const d = await r.json();
|
|
517
519
|
signals = d && d.signals || [];
|
|
520
|
+
if (d && typeof d.cursor === "number")
|
|
521
|
+
cursor = d.cursor;
|
|
518
522
|
} catch (e) {
|
|
519
523
|
await new Promise((res) => setTimeout(res, 1e3));
|
|
520
524
|
continue;
|
package/dist/embedded-host.d.ts
CHANGED
|
@@ -36,4 +36,6 @@ export declare class EmbeddedHost implements PeerHost {
|
|
|
36
36
|
subscribe(emit: (event: Record<string, unknown>) => void): () => void;
|
|
37
37
|
stop(): Promise<void>;
|
|
38
38
|
call(req: IpcRequest): Promise<IpcResponse>;
|
|
39
|
+
/** Signals older than this have no business ringing a phone. */
|
|
40
|
+
static readonly CALL_SIGNAL_TTL_MS = 90000;
|
|
39
41
|
}
|
package/dist/embedded-host.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// What is deliberately NOT here, versus the daemon: virtual IPs, exits, dora,
|
|
9
9
|
// ACL, packet routing. Those are decentlan's job and need privilege beagle
|
|
10
10
|
// refuses to ask for.
|
|
11
|
+
var _a;
|
|
11
12
|
import { EventEmitter } from "node:events";
|
|
12
13
|
import { existsSync } from "node:fs";
|
|
13
14
|
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises";
|
|
@@ -53,8 +54,14 @@ export class EmbeddedHost {
|
|
|
53
54
|
#sendChains = new Map();
|
|
54
55
|
/** Friend requests held for manual accept when auto-accept is off. */
|
|
55
56
|
#pending = new Map();
|
|
56
|
-
/** Inbound call signals
|
|
57
|
-
|
|
57
|
+
/** Inbound call signals as a broadcast ring: every poller reads at its own
|
|
58
|
+
* cursor, entries expire by AGE, never by being read. The previous shape —
|
|
59
|
+
* a queue drained by splice(0) — handed the whole batch to whichever
|
|
60
|
+
* poller arrived first, so with two tabs open an incoming call rang a
|
|
61
|
+
* random one and the tab the user was looking at stayed silent
|
|
62
|
+
* (INBOX 2026-08-05: "能打出去接不进来"). */
|
|
63
|
+
#callLog = [];
|
|
64
|
+
#callSeq = 0;
|
|
58
65
|
#callWaiters = [];
|
|
59
66
|
/** fileId -> which chat message it belongs to, so progress can patch it. */
|
|
60
67
|
#activeSends = new Map();
|
|
@@ -147,8 +154,14 @@ export class EmbeddedHost {
|
|
|
147
154
|
}
|
|
148
155
|
});
|
|
149
156
|
this.#node.on("call-signal", (evt) => {
|
|
150
|
-
this.#
|
|
151
|
-
|
|
157
|
+
this.#callLog.push({
|
|
158
|
+
seq: ++this.#callSeq,
|
|
159
|
+
ts: Date.now(),
|
|
160
|
+
userid: evt.pubkey,
|
|
161
|
+
data: Buffer.from(evt.data).toString("utf-8"),
|
|
162
|
+
});
|
|
163
|
+
this.#pruneCallLog();
|
|
164
|
+
// Wake every long-poll; each reads from its own cursor, nobody consumes.
|
|
152
165
|
const waiters = this.#callWaiters.splice(0);
|
|
153
166
|
for (const w of waiters)
|
|
154
167
|
w();
|
|
@@ -495,32 +508,46 @@ export class EmbeddedHost {
|
|
|
495
508
|
await this.#node.sendCallSignal(uid, String(req.text ?? req.data ?? ""));
|
|
496
509
|
return {};
|
|
497
510
|
case "call-poll":
|
|
498
|
-
return
|
|
511
|
+
return this.#pollCalls(typeof req.since === "number" ? req.since : undefined);
|
|
499
512
|
default:
|
|
500
513
|
throw new Error(`Unsupported op in embedded backend: ${req.op}`);
|
|
501
514
|
}
|
|
502
515
|
}
|
|
503
|
-
/**
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
516
|
+
/** Signals older than this have no business ringing a phone. */
|
|
517
|
+
static CALL_SIGNAL_TTL_MS = 90_000;
|
|
518
|
+
#pruneCallLog() {
|
|
519
|
+
const cutoff = Date.now() - _a.CALL_SIGNAL_TTL_MS;
|
|
520
|
+
while (this.#callLog.length && this.#callLog[0].ts < cutoff)
|
|
521
|
+
this.#callLog.shift();
|
|
522
|
+
}
|
|
523
|
+
/** Long-poll at a cursor: every subscriber sees every signal (broadcast),
|
|
524
|
+
* reading never consumes. `since` is the cursor from the previous
|
|
525
|
+
* response; a caller without one (older UI) starts at "now" and gets only
|
|
526
|
+
* future signals — exactly the old semantics minus the stealing. Returns
|
|
527
|
+
* immediately when the log has anything past the cursor, else holds ~20s. */
|
|
528
|
+
async #pollCalls(since) {
|
|
529
|
+
const from = since ?? this.#callSeq;
|
|
530
|
+
this.#pruneCallLog();
|
|
531
|
+
let avail = this.#callLog.filter((s) => s.seq > from);
|
|
532
|
+
if (!avail.length) {
|
|
533
|
+
await new Promise((res) => {
|
|
534
|
+
const done = () => {
|
|
535
|
+
clearTimeout(timer);
|
|
536
|
+
res();
|
|
537
|
+
};
|
|
538
|
+
const timer = setTimeout(() => {
|
|
539
|
+
const i = this.#callWaiters.indexOf(done);
|
|
540
|
+
if (i >= 0)
|
|
541
|
+
this.#callWaiters.splice(i, 1);
|
|
542
|
+
res();
|
|
543
|
+
}, 20_000);
|
|
544
|
+
timer.unref?.();
|
|
545
|
+
this.#callWaiters.push(done);
|
|
546
|
+
});
|
|
547
|
+
this.#pruneCallLog();
|
|
548
|
+
avail = this.#callLog.filter((s) => s.seq > from);
|
|
549
|
+
}
|
|
550
|
+
return { signals: avail.map(({ userid, data }) => ({ userid, data })), cursor: this.#callSeq };
|
|
524
551
|
}
|
|
525
552
|
async #fileSend(userid, path) {
|
|
526
553
|
const data = await readFile(path);
|
|
@@ -578,3 +605,4 @@ export class EmbeddedHost {
|
|
|
578
605
|
return { fileId, name: safe, size: data.length };
|
|
579
606
|
}
|
|
580
607
|
}
|
|
608
|
+
_a = EmbeddedHost;
|
package/dist/server.js
CHANGED
|
@@ -900,8 +900,17 @@ export function startBeagleServer(opts) {
|
|
|
900
900
|
// The daemon holds this up to ~20s (long-poll). opts.call's own IPC
|
|
901
901
|
// timeout is 30s, so it returns before that; on any error, resolve
|
|
902
902
|
// empty so the browser simply re-polls.
|
|
903
|
+
//
|
|
904
|
+
// `since` is the caller's broadcast cursor: signal delivery is
|
|
905
|
+
// per-subscriber, so two open tabs BOTH ring on an incoming call.
|
|
906
|
+
// Without it (older UI) the backend serves only future signals.
|
|
907
|
+
const sinceRaw = new URL(req.url || "/", "http://x").searchParams.get("since");
|
|
908
|
+
const since = sinceRaw !== null && sinceRaw !== "" ? Number(sinceRaw) : undefined;
|
|
903
909
|
try {
|
|
904
|
-
const r = await opts.call({
|
|
910
|
+
const r = await opts.call({
|
|
911
|
+
op: "call-poll",
|
|
912
|
+
...(Number.isFinite(since) ? { since } : {}),
|
|
913
|
+
});
|
|
905
914
|
sendJson(res, 200, r.ok ? r.data ?? { signals: [] } : { signals: [] });
|
|
906
915
|
}
|
|
907
916
|
catch {
|
package/package.json
CHANGED