@decentnetwork/beagle 0.1.88 → 0.1.89
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 +235 -10
- package/dist/embedded-host.js +22 -19
- package/dist/server.js +12 -4
- 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.89";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -521,6 +521,9 @@ window.__DK_UI_VERSION="0.1.87";
|
|
|
521
521
|
minWidth: 0
|
|
522
522
|
} }, dkContactPreview(peer.lastMsg) || peer.lastMsg))), /* @__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)" } }, peer.lastTime), peer.unread ? /* @__PURE__ */ React.createElement(Unread, { n: peer.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 })));
|
|
523
523
|
}
|
|
524
|
+
function dkActivityTs(p) {
|
|
525
|
+
return Math.max(p.lastTs || 0, p.acceptedAt || 0, p.requestedAt || 0);
|
|
526
|
+
}
|
|
524
527
|
function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed }) {
|
|
525
528
|
const [q, setQ] = React.useState("");
|
|
526
529
|
const [addr, setAddr] = React.useState("");
|
|
@@ -558,12 +561,9 @@ window.__DK_UI_VERSION="0.1.87";
|
|
|
558
561
|
const s = (p.alias || "") + " " + p.userId + " " + p.ip;
|
|
559
562
|
return s.toLowerCase().includes(q.toLowerCase());
|
|
560
563
|
}).slice().sort((a, b) => {
|
|
561
|
-
const ta = a
|
|
564
|
+
const ta = dkActivityTs(a), tb = dkActivityTs(b);
|
|
562
565
|
if (ta !== tb)
|
|
563
566
|
return tb - ta;
|
|
564
|
-
const aa = a.acceptedAt || 0, ba = b.acceptedAt || 0;
|
|
565
|
-
if (aa !== ba)
|
|
566
|
-
return ba - aa;
|
|
567
567
|
return String(a.alias || a.userId || "").localeCompare(String(b.alias || b.userId || ""));
|
|
568
568
|
});
|
|
569
569
|
const online = peers.filter((p) => p.online).length;
|
|
@@ -2720,6 +2720,35 @@ ${peer.address}`
|
|
|
2720
2720
|
)));
|
|
2721
2721
|
return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u663E\u793A" : "Display" }, /* @__PURE__ */ React.createElement("div", { style: { ...row, borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u8BED\u8A00" : "Language"), seg("lang", t.lang, (v) => setTweak("lang", v), [["en", "EN"], ["zh", "\u4E2D\u6587"]])), /* @__PURE__ */ React.createElement("div", { style: row }, /* @__PURE__ */ React.createElement("span", { style: name }, zh ? "\u4E3B\u9898" : "Theme"), seg("theme", t.theme, (v) => setTweak("theme", v), [["dark", zh ? "\u6DF1\u8272" : "Dark"], ["light", zh ? "\u6D45\u8272" : "Light"]])));
|
|
2722
2722
|
}
|
|
2723
|
+
function DkAlertSettings({ t, setTweak }) {
|
|
2724
|
+
const zh = t.lang === "zh";
|
|
2725
|
+
const toggle = (key, label, sub, last) => /* @__PURE__ */ React.createElement("label", { style: {
|
|
2726
|
+
display: "flex",
|
|
2727
|
+
alignItems: "center",
|
|
2728
|
+
gap: 13,
|
|
2729
|
+
padding: "13px 16px",
|
|
2730
|
+
cursor: "pointer",
|
|
2731
|
+
borderBottom: last ? "none" : "1px solid var(--line)"
|
|
2732
|
+
} }, /* @__PURE__ */ React.createElement(
|
|
2733
|
+
"input",
|
|
2734
|
+
{
|
|
2735
|
+
type: "checkbox",
|
|
2736
|
+
checked: t[key] !== false,
|
|
2737
|
+
onChange: (e) => setTweak(key, e.target.checked),
|
|
2738
|
+
style: { width: 16, height: 16, accentColor: "var(--accent)" }
|
|
2739
|
+
}
|
|
2740
|
+
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, label), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, sub)));
|
|
2741
|
+
return /* @__PURE__ */ React.createElement(Card, { label: zh ? "\u63D0\u9192" : "Alerts" }, toggle(
|
|
2742
|
+
"alertSound",
|
|
2743
|
+
zh ? "\u65B0\u6D88\u606F\u63D0\u793A\u97F3" : "Sound for new messages",
|
|
2744
|
+
zh ? "\u6536\u5230\u65B0\u6D88\u606F\u6216\u597D\u53CB\u8BF7\u6C42\u65F6\u64AD\u653E\u77ED\u4FC3\u63D0\u793A\u97F3\u3002\u6B63\u5728\u770B\u7684\u5F53\u524D\u4F1A\u8BDD\u4E0D\u4F1A\u54CD\u3002" : "A short chime when a message or friend request arrives. Silent for the conversation you are reading."
|
|
2745
|
+
), toggle(
|
|
2746
|
+
"alertFlash",
|
|
2747
|
+
zh ? "\u6807\u7B7E\u9875\u95EA\u70C1" : "Flash the tab",
|
|
2748
|
+
zh ? "\u7A97\u53E3\u4E0D\u5728\u524D\u53F0\u65F6\uFF0C\u6807\u7B7E\u6807\u9898\u548C\u56FE\u6807\u95EA\u70C1\u63D0\u793A\uFF0C\u56DE\u5230\u7A97\u53E3\u5373\u505C\u6B62\u3002" : "While this window is in the background, the tab title and icon blink until you come back.",
|
|
2749
|
+
true
|
|
2750
|
+
));
|
|
2751
|
+
}
|
|
2723
2752
|
function ProfileTab({ T, me, onEdit, t, setTweak }) {
|
|
2724
2753
|
var _a;
|
|
2725
2754
|
const [qr, setQr] = React.useState(null);
|
|
@@ -2741,7 +2770,7 @@ ${peer.address}`
|
|
|
2741
2770
|
},
|
|
2742
2771
|
style: { width: 16, height: 16, accentColor: "var(--accent)" }
|
|
2743
2772
|
}
|
|
2744
|
-
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
|
|
2773
|
+
), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, T && T.autoAcceptLabel || "Auto-accept friend requests"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 11.5, color: "var(--dim)", marginTop: 2 } }, T && T.autoAcceptSub || "Off: requests wait in the sidebar for your approval.")))), hostHasKeyBackup && /* @__PURE__ */ React.createElement(DkKeyBackup, { t: t || {} }), t && setTweak && /* @__PURE__ */ React.createElement(DkAlertSettings, { t, setTweak }), t && setTweak && /* @__PURE__ */ React.createElement(DkDisplaySettings, { t, setTweak }), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(DkVersionRow, { T, me }), /* @__PURE__ */ React.createElement(DkUpdateCheck, { T })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))), qr && /* @__PURE__ */ React.createElement(DkQrModal, { T, value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
|
|
2745
2774
|
if (onEdit)
|
|
2746
2775
|
onEdit(name, description);
|
|
2747
2776
|
setEditing(false);
|
|
@@ -4290,6 +4319,181 @@ ${peer.address}`
|
|
|
4290
4319
|
return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit }))))));
|
|
4291
4320
|
}
|
|
4292
4321
|
Object.assign(window, { NetworkTab });
|
|
4322
|
+
var dkAudioCtx = null;
|
|
4323
|
+
function dkAudio() {
|
|
4324
|
+
if (dkAudioCtx && dkAudioCtx.state !== "closed")
|
|
4325
|
+
return dkAudioCtx;
|
|
4326
|
+
try {
|
|
4327
|
+
dkAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
4328
|
+
} catch (e) {
|
|
4329
|
+
dkAudioCtx = null;
|
|
4330
|
+
}
|
|
4331
|
+
return dkAudioCtx;
|
|
4332
|
+
}
|
|
4333
|
+
function dkUnlockAudioOnGesture() {
|
|
4334
|
+
const unlock = () => {
|
|
4335
|
+
const c = dkAudio();
|
|
4336
|
+
if (c && c.state === "suspended")
|
|
4337
|
+
c.resume().catch(() => {
|
|
4338
|
+
});
|
|
4339
|
+
};
|
|
4340
|
+
window.addEventListener("pointerdown", unlock, { passive: true });
|
|
4341
|
+
window.addEventListener("keydown", unlock, { passive: true });
|
|
4342
|
+
return () => {
|
|
4343
|
+
window.removeEventListener("pointerdown", unlock);
|
|
4344
|
+
window.removeEventListener("keydown", unlock);
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
function dkChime(kind) {
|
|
4348
|
+
const c = dkAudio();
|
|
4349
|
+
if (!c)
|
|
4350
|
+
return;
|
|
4351
|
+
if (c.state === "suspended")
|
|
4352
|
+
c.resume().catch(() => {
|
|
4353
|
+
});
|
|
4354
|
+
const notes = kind === "request" ? [[660, 0], [880, 0.14]] : [[880, 0], [1175, 0.11]];
|
|
4355
|
+
const t0 = c.currentTime;
|
|
4356
|
+
for (const [freq, at] of notes) {
|
|
4357
|
+
const o = c.createOscillator(), g = c.createGain();
|
|
4358
|
+
o.type = "sine";
|
|
4359
|
+
o.frequency.value = freq;
|
|
4360
|
+
o.connect(g);
|
|
4361
|
+
g.connect(c.destination);
|
|
4362
|
+
const t = t0 + at;
|
|
4363
|
+
g.gain.setValueAtTime(1e-4, t);
|
|
4364
|
+
g.gain.exponentialRampToValueAtTime(0.18, t + 0.015);
|
|
4365
|
+
g.gain.exponentialRampToValueAtTime(1e-4, t + 0.17);
|
|
4366
|
+
o.start(t);
|
|
4367
|
+
o.stop(t + 0.2);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
var dkFlash = { timer: null, title: "", link: null, href: null, badged: null, on: false, n: 0, label: "" };
|
|
4371
|
+
function dkBadgedIcon(href) {
|
|
4372
|
+
return new Promise((res, rej) => {
|
|
4373
|
+
const img = new Image();
|
|
4374
|
+
img.onload = () => {
|
|
4375
|
+
try {
|
|
4376
|
+
if (!img.naturalWidth)
|
|
4377
|
+
return rej(new Error("icon has no intrinsic size"));
|
|
4378
|
+
const s = 64, cv = document.createElement("canvas");
|
|
4379
|
+
cv.width = s;
|
|
4380
|
+
cv.height = s;
|
|
4381
|
+
const cx = cv.getContext("2d");
|
|
4382
|
+
cx.drawImage(img, 0, 0, s, s);
|
|
4383
|
+
cx.beginPath();
|
|
4384
|
+
cx.arc(s - 14, 14, 12, 0, Math.PI * 2);
|
|
4385
|
+
cx.fillStyle = "#e5484d";
|
|
4386
|
+
cx.fill();
|
|
4387
|
+
cx.lineWidth = 3;
|
|
4388
|
+
cx.strokeStyle = "#fff";
|
|
4389
|
+
cx.stroke();
|
|
4390
|
+
res(cv.toDataURL("image/png"));
|
|
4391
|
+
} catch (e) {
|
|
4392
|
+
rej(e);
|
|
4393
|
+
}
|
|
4394
|
+
};
|
|
4395
|
+
img.onerror = () => rej(new Error("icon failed to load"));
|
|
4396
|
+
img.src = href;
|
|
4397
|
+
});
|
|
4398
|
+
}
|
|
4399
|
+
function dkFlashTick() {
|
|
4400
|
+
dkFlash.on = !dkFlash.on;
|
|
4401
|
+
document.title = `(${dkFlash.n}) ${dkFlash.on ? dkFlash.label : dkFlash.title}`;
|
|
4402
|
+
}
|
|
4403
|
+
function dkFlashStart(label, count) {
|
|
4404
|
+
dkFlash.n += count || 1;
|
|
4405
|
+
dkFlash.label = label;
|
|
4406
|
+
if (dkFlash.timer)
|
|
4407
|
+
return;
|
|
4408
|
+
dkFlash.title = document.title;
|
|
4409
|
+
dkFlash.link = document.querySelector('link[rel~="icon"]');
|
|
4410
|
+
dkFlash.href = dkFlash.link ? dkFlash.link.href : null;
|
|
4411
|
+
dkFlash.badged = null;
|
|
4412
|
+
dkFlash.on = false;
|
|
4413
|
+
if (dkFlash.href) {
|
|
4414
|
+
dkBadgedIcon(dkFlash.href).then((b) => {
|
|
4415
|
+
dkFlash.badged = b;
|
|
4416
|
+
if (dkFlash.timer && dkFlash.link)
|
|
4417
|
+
dkFlash.link.href = b;
|
|
4418
|
+
}).catch(() => {
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
dkFlashTick();
|
|
4422
|
+
dkFlash.timer = setInterval(dkFlashTick, 1e3);
|
|
4423
|
+
}
|
|
4424
|
+
function dkFlashStop() {
|
|
4425
|
+
if (!dkFlash.timer)
|
|
4426
|
+
return;
|
|
4427
|
+
clearInterval(dkFlash.timer);
|
|
4428
|
+
dkFlash.timer = null;
|
|
4429
|
+
dkFlash.n = 0;
|
|
4430
|
+
document.title = dkFlash.title;
|
|
4431
|
+
if (dkFlash.link && dkFlash.href)
|
|
4432
|
+
dkFlash.link.href = dkFlash.href;
|
|
4433
|
+
}
|
|
4434
|
+
function dkLooking() {
|
|
4435
|
+
return document.visibilityState === "visible" && document.hasFocus();
|
|
4436
|
+
}
|
|
4437
|
+
function useIncomingAlerts({ T, peers, requests, loaded, activeId, sound = true, flash = true }) {
|
|
4438
|
+
const prev = React.useRef(null);
|
|
4439
|
+
React.useEffect(() => dkUnlockAudioOnGesture(), []);
|
|
4440
|
+
React.useEffect(() => {
|
|
4441
|
+
if (!flash)
|
|
4442
|
+
dkFlashStop();
|
|
4443
|
+
}, [flash]);
|
|
4444
|
+
React.useEffect(() => {
|
|
4445
|
+
const seen = () => {
|
|
4446
|
+
if (dkLooking())
|
|
4447
|
+
dkFlashStop();
|
|
4448
|
+
};
|
|
4449
|
+
window.addEventListener("focus", seen);
|
|
4450
|
+
window.addEventListener("pointerdown", seen, { passive: true });
|
|
4451
|
+
document.addEventListener("visibilitychange", seen);
|
|
4452
|
+
return () => {
|
|
4453
|
+
window.removeEventListener("focus", seen);
|
|
4454
|
+
window.removeEventListener("pointerdown", seen);
|
|
4455
|
+
document.removeEventListener("visibilitychange", seen);
|
|
4456
|
+
dkFlashStop();
|
|
4457
|
+
};
|
|
4458
|
+
}, []);
|
|
4459
|
+
React.useEffect(() => {
|
|
4460
|
+
if (!loaded)
|
|
4461
|
+
return;
|
|
4462
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4463
|
+
for (const p of peers || []) {
|
|
4464
|
+
const dir = p.lastDir || (String(p.lastMsg || "").startsWith("you: ") ? "out" : "in");
|
|
4465
|
+
byId.set(p.id, { ts: p.lastTs || 0, dir });
|
|
4466
|
+
}
|
|
4467
|
+
const reqIds = new Set((requests || []).map((r) => r.id));
|
|
4468
|
+
const was = prev.current;
|
|
4469
|
+
prev.current = { byId, reqIds };
|
|
4470
|
+
if (!was)
|
|
4471
|
+
return;
|
|
4472
|
+
let msgs = 0, elsewhere = 0, reqs = 0;
|
|
4473
|
+
for (const [id, cur] of byId) {
|
|
4474
|
+
if (cur.dir !== "in" || !cur.ts)
|
|
4475
|
+
continue;
|
|
4476
|
+
const old = was.byId.get(id);
|
|
4477
|
+
if (old ? cur.ts <= old.ts : Date.now() - cur.ts > 3e4)
|
|
4478
|
+
continue;
|
|
4479
|
+
msgs += 1;
|
|
4480
|
+
if (id !== activeId)
|
|
4481
|
+
elsewhere += 1;
|
|
4482
|
+
}
|
|
4483
|
+
for (const id of reqIds)
|
|
4484
|
+
if (!was.reqIds.has(id))
|
|
4485
|
+
reqs += 1;
|
|
4486
|
+
if (!msgs && !reqs)
|
|
4487
|
+
return;
|
|
4488
|
+
const looking = dkLooking();
|
|
4489
|
+
if (sound && !(looking && !reqs && !elsewhere))
|
|
4490
|
+
dkChime(reqs && !msgs ? "request" : "message");
|
|
4491
|
+
if (flash && !looking) {
|
|
4492
|
+
const label = msgs ? T && T.alertMsg || "New message" : T && T.alertReq || "New friend request";
|
|
4493
|
+
dkFlashStart(label, msgs + reqs);
|
|
4494
|
+
}
|
|
4495
|
+
}, [peers, requests, loaded, activeId, T, sound, flash]);
|
|
4496
|
+
}
|
|
4293
4497
|
var TWEAKS_STORAGE_KEY = "decentlan.tweaks";
|
|
4294
4498
|
function useTweaks(defaults) {
|
|
4295
4499
|
const [values, setValues] = React.useState(() => {
|
|
@@ -4347,7 +4551,9 @@ ${peer.address}`
|
|
|
4347
4551
|
"accent": "#7B6CF6",
|
|
4348
4552
|
"density": "compact",
|
|
4349
4553
|
"lang": "en",
|
|
4350
|
-
"startTab": "chat"
|
|
4554
|
+
"startTab": "chat",
|
|
4555
|
+
"alertSound": true,
|
|
4556
|
+
"alertFlash": true
|
|
4351
4557
|
}
|
|
4352
4558
|
);
|
|
4353
4559
|
function dkDetectLang() {
|
|
@@ -4416,6 +4622,8 @@ ${peer.address}`
|
|
|
4416
4622
|
network: "Network",
|
|
4417
4623
|
apps: "Apps",
|
|
4418
4624
|
profile: "Profile",
|
|
4625
|
+
alertMsg: "New message",
|
|
4626
|
+
alertReq: "New friend request",
|
|
4419
4627
|
recommended: "Discover",
|
|
4420
4628
|
registered: "Names",
|
|
4421
4629
|
here: "Here",
|
|
@@ -4596,6 +4804,8 @@ ${peer.address}`
|
|
|
4596
4804
|
network: "\u7F51\u7EDC",
|
|
4597
4805
|
apps: "\u5E94\u7528",
|
|
4598
4806
|
profile: "\u6211\u7684",
|
|
4807
|
+
alertMsg: "\u65B0\u6D88\u606F",
|
|
4808
|
+
alertReq: "\u65B0\u7684\u597D\u53CB\u8BF7\u6C42",
|
|
4599
4809
|
recommended: "\u63A8\u8350",
|
|
4600
4810
|
registered: "\u540D\u5F55",
|
|
4601
4811
|
here: "\u5728\u573A",
|
|
@@ -4988,6 +5198,15 @@ ${peer.address}`
|
|
|
4988
5198
|
const T = STR[t.lang] || STR.en;
|
|
4989
5199
|
const vars = dkTheme(t.theme, t.accent);
|
|
4990
5200
|
const rowPad = t.density === "comfortable" ? "11px 12px" : "7px 10px";
|
|
5201
|
+
useIncomingAlerts({
|
|
5202
|
+
T,
|
|
5203
|
+
peers,
|
|
5204
|
+
requests,
|
|
5205
|
+
loaded: data.loaded,
|
|
5206
|
+
activeId,
|
|
5207
|
+
sound: t.alertSound !== false,
|
|
5208
|
+
flash: t.alertFlash !== false
|
|
5209
|
+
});
|
|
4991
5210
|
const callCtl = useCallController(me.userId, (peerId, video, direction) => {
|
|
4992
5211
|
const text = `WebRTC ${video ? "video" : "audio"} call: ${direction}`;
|
|
4993
5212
|
dkApi.logLocal(peerId, direction === "outgoing" ? "out" : "in", text).then(() => {
|
|
@@ -5129,10 +5348,16 @@ ${peer.address}`
|
|
|
5129
5348
|
{ id: "recommended", icon: "sparkles", label: T.recommended },
|
|
5130
5349
|
{ id: "registered", icon: "at", label: T.registered },
|
|
5131
5350
|
{ id: "network", icon: "network", label: T.network },
|
|
5132
|
-
{ id: "apps", icon: "grid", label: T.apps }
|
|
5133
|
-
{ id: "profile", icon: "userRound", label: T.profile }
|
|
5351
|
+
{ id: "apps", icon: "grid", label: T.apps }
|
|
5134
5352
|
];
|
|
5135
|
-
return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("
|
|
5353
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("button", { onClick: () => setTab("profile"), title: T.profile, style: {
|
|
5354
|
+
padding: 4,
|
|
5355
|
+
borderRadius: 12,
|
|
5356
|
+
cursor: "pointer",
|
|
5357
|
+
display: "flex",
|
|
5358
|
+
border: "1px solid " + (tab === "profile" ? "var(--line)" : "transparent"),
|
|
5359
|
+
background: tab === "profile" ? "var(--panel-2)" : "transparent"
|
|
5360
|
+
} }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement(DkBrowserNotice, { lang: t.lang }), /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("svg", { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "var(--accent)", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "block", flexShrink: 0 } }, /* @__PURE__ */ React.createElement("path", { d: "m4.5 17 6-6-6-6" }), /* @__PURE__ */ React.createElement("path", { d: "M12 18.5h7.5" })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", (nav.find((n) => n.id === tab) || { label: T.profile }).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId), prefillAddr: pendingAddr, onPrefillConsumed: () => setPendingAddr("") }), tab === "here" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "bridge", peers, meId: me.userId, onAdd, onOpenChat }), tab === "recommended" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "recommended", peers, meId: me.userId, onAdd, onOpenChat }), tab === "registered" && /* @__PURE__ */ React.createElement(DiscoverTab, { T, kind: "registered", peers, meId: me.userId, onAdd, onOpenChat }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat, backend, onArmLan: armLan, onCancelLan: cancelLan }), tab === "apps" && /* @__PURE__ */ React.createElement(AppsTab, { T, t }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit, t, setTweak })), data.locked && /* @__PURE__ */ React.createElement(
|
|
5136
5361
|
DkLockedOut,
|
|
5137
5362
|
{
|
|
5138
5363
|
compact: true,
|
package/dist/embedded-host.js
CHANGED
|
@@ -90,31 +90,30 @@ export class EmbeddedHost {
|
|
|
90
90
|
this.#messages = new MessageStore(resolve(opts.configDir, "messages.json"));
|
|
91
91
|
this.#meta = new FriendMetaStore(resolve(opts.configDir, "friends-meta.json"));
|
|
92
92
|
}
|
|
93
|
-
/** Friend-list order: pinned first, then most recent
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
93
|
+
/** Friend-list order: pinned first, then most recent ACTIVITY, then by
|
|
94
|
+
* name. Activity is the newest of the last message, the friendship being
|
|
95
|
+
* accepted, and our request going out — so a friend you added or accepted
|
|
96
|
+
* a moment ago ranks exactly as if they had just written to you. The
|
|
97
|
+
* earlier rule sorted by last message alone and ranked new friends only
|
|
98
|
+
* among the never-messaged group at the bottom, which on a long list still
|
|
99
|
+
* put them below the fold and made the accept look like a no-op. Returns
|
|
100
|
+
* a new array — `friends()` hands back the SDK's own list and sorting it
|
|
101
|
+
* in place would reorder the friend store as a side effect of merely
|
|
102
|
+
* rendering the sidebar. */
|
|
97
103
|
#sortForList(friends, last) {
|
|
98
104
|
const rank = (f) => [
|
|
99
105
|
this.#meta.get(f.carrierId)?.pinned ? 0 : 1,
|
|
100
|
-
// Negated so a LARGER timestamp sorts first.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
last.has(f.carrierId) ? -last.get(f.carrierId).ts : Number.MAX_SAFE_INTEGER,
|
|
106
|
-
// Among friends you have never messaged, NEWEST FRIENDSHIP first.
|
|
107
|
-
// Alphabetical put someone you accepted ten seconds ago wherever their
|
|
108
|
-
// name happened to land — usually far down a 50-name list, so accepting
|
|
109
|
-
// a request looked like nothing had happened. The one friend you are
|
|
110
|
-
// most likely to open is the one you just added.
|
|
111
|
-
-(f.acceptedAt ?? 0),
|
|
106
|
+
// Negated so a LARGER timestamp sorts first. Nothing at all → 0, which
|
|
107
|
+
// parks the entry after every real one; a plain number, so two such
|
|
108
|
+
// entries compare 0 - 0 = 0 and fall through to the name (Infinity
|
|
109
|
+
// would give NaN there).
|
|
110
|
+
-Math.max(last.get(f.carrierId)?.ts ?? 0, f.acceptedAt ?? 0, f.requestedAt ?? 0),
|
|
112
111
|
(f.name || f.carrierId).toLowerCase(),
|
|
113
112
|
];
|
|
114
113
|
return [...friends].sort((a, b) => {
|
|
115
|
-
const [ap, at,
|
|
116
|
-
const [bp, bt,
|
|
117
|
-
return ap - bp || at - bt ||
|
|
114
|
+
const [ap, at, an] = rank(a);
|
|
115
|
+
const [bp, bt, bn] = rank(b);
|
|
116
|
+
return ap - bp || at - bt || an.localeCompare(bn);
|
|
118
117
|
});
|
|
119
118
|
}
|
|
120
119
|
get downloadsDir() {
|
|
@@ -459,6 +458,10 @@ export class EmbeddedHost {
|
|
|
459
458
|
f.status === "online" ||
|
|
460
459
|
this.#messages.hasInbound(f.carrierId),
|
|
461
460
|
lastSeen: f.acceptedAt,
|
|
461
|
+
// When WE sent the request. Counts as activity for the sidebar
|
|
462
|
+
// order: someone you just added belongs at the top while you
|
|
463
|
+
// wait for them, not filed by name at the bottom.
|
|
464
|
+
requestedAt: f.requestedAt,
|
|
462
465
|
// The avatar they advertised over Carrier (userinfo `gender`).
|
|
463
466
|
// Peer-to-peer, so it works for a friend who never registered a
|
|
464
467
|
// beagles.eth name — until now that meant an identicon.
|
package/dist/server.js
CHANGED
|
@@ -1975,11 +1975,19 @@ export function startBeagleServer(opts) {
|
|
|
1975
1975
|
// be ordered, which is why the sidebar never reordered on new
|
|
1976
1976
|
// activity.
|
|
1977
1977
|
lastTs: lm?.ts ?? 0,
|
|
1978
|
-
//
|
|
1979
|
-
//
|
|
1980
|
-
//
|
|
1981
|
-
|
|
1978
|
+
// Which way the last message went. The UI's new-message alert
|
|
1979
|
+
// needs to know an inbound stamp moved, and inferring that from
|
|
1980
|
+
// the "you: " prefix on the preview is a convention, not a field.
|
|
1981
|
+
lastDir: lm?.dir ?? null,
|
|
1982
|
+
// When the friendship was established, and when we asked for it.
|
|
1983
|
+
// The sidebar ranks a friend by the newest of these and the last
|
|
1984
|
+
// message, so someone you just accepted — or just added — lands
|
|
1985
|
+
// at the top like a fresh message would, not in a no-history
|
|
1986
|
+
// group at the bottom. A daemon backend reports only lastSeen
|
|
1987
|
+
// (its acceptedAt); requestedAt is embedded-only and reads 0
|
|
1988
|
+
// elsewhere.
|
|
1982
1989
|
acceptedAt: f.lastSeen ?? 0,
|
|
1990
|
+
requestedAt: f.requestedAt ?? 0,
|
|
1983
1991
|
wire: "163",
|
|
1984
1992
|
avatarUrl: ensPub.get(uid)?.avatarUrl ?? null,
|
|
1985
1993
|
// What they advertised over Carrier wins over the directory: it is
|
package/package.json
CHANGED