@decentnetwork/beagle 0.1.7 → 0.1.8
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 +14 -1
- package/dist/embedded-host.js +27 -2
- package/dist/message-store.d.ts +4 -0
- package/dist/message-store.js +6 -0
- package/dist/server.js +18 -1
- package/package.json +2 -2
package/dist/desktop/app.js
CHANGED
|
@@ -1297,7 +1297,7 @@ function PeerRow({ peer, T, active, onClick }) {
|
|
|
1297
1297
|
textOverflow: "ellipsis",
|
|
1298
1298
|
flex: 1,
|
|
1299
1299
|
minWidth: 0
|
|
1300
|
-
} }, 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 }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
|
|
1300
|
+
} }, 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 })));
|
|
1301
1301
|
}
|
|
1302
1302
|
function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd, prefillAddr, onPrefillConsumed }) {
|
|
1303
1303
|
const [q, setQ] = React.useState("");
|
|
@@ -2261,6 +2261,17 @@ ${peer.address}`
|
|
|
2261
2261
|
setMenu(false);
|
|
2262
2262
|
onRemove(peer);
|
|
2263
2263
|
} }))))),
|
|
2264
|
+
peer.pending && /* @__PURE__ */ React.createElement("div", { style: {
|
|
2265
|
+
flexShrink: 0,
|
|
2266
|
+
padding: "7px 16px",
|
|
2267
|
+
display: "flex",
|
|
2268
|
+
alignItems: "center",
|
|
2269
|
+
gap: 8,
|
|
2270
|
+
background: "color-mix(in srgb, #d29922 12%, var(--panel))",
|
|
2271
|
+
borderBottom: "1px solid var(--line)",
|
|
2272
|
+
fontSize: 12.5,
|
|
2273
|
+
color: "var(--text)"
|
|
2274
|
+
} }, /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 13, stroke: 2.2, color: "#d29922" }), /* @__PURE__ */ React.createElement("span", null, T.pendingFriend || "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.")),
|
|
2264
2275
|
/* @__PURE__ */ React.createElement("div", { ref: scrollRef, onScroll: updateFollowScroll, style: { flex: 1, overflow: "auto", padding: "18px 22px" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto" } }, thread.filter((m) => m.day || !hidden.has(m.id)).map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(
|
|
2265
2276
|
Msg,
|
|
2266
2277
|
{
|
|
@@ -3478,6 +3489,7 @@ const STR = {
|
|
|
3478
3489
|
open: "open",
|
|
3479
3490
|
retry: "Retry",
|
|
3480
3491
|
cancel: "Cancel",
|
|
3492
|
+
pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
|
|
3481
3493
|
cancelling: "Cancelling\u2026",
|
|
3482
3494
|
cancelled: "Transfer cancelled",
|
|
3483
3495
|
retrying: "Retrying\u2026",
|
|
@@ -3573,6 +3585,7 @@ const STR = {
|
|
|
3573
3585
|
open: "\u6253\u5F00",
|
|
3574
3586
|
retry: "\u91CD\u8BD5",
|
|
3575
3587
|
cancel: "\u53D6\u6D88",
|
|
3588
|
+
pendingFriend: "\u7B49\u5F85\u5BF9\u65B9\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42 \u2014 \u6D88\u606F\u4F1A\u5148\u6392\u961F\uFF0C\u5BF9\u65B9\u63A5\u53D7\u540E\u81EA\u52A8\u9001\u8FBE\u3002",
|
|
3576
3589
|
cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
|
|
3577
3590
|
cancelled: "\u5DF2\u53D6\u6D88\u4F20\u8F93",
|
|
3578
3591
|
retrying: "\u6B63\u5728\u91CD\u8BD5\u2026",
|
package/dist/embedded-host.js
CHANGED
|
@@ -130,8 +130,21 @@ export class EmbeddedHost {
|
|
|
130
130
|
});
|
|
131
131
|
this.#node.on("friend-connection", (e) => {
|
|
132
132
|
this.#events.emit("event", { type: "presence", userid: e.pubkey, status: e.status });
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// The connected event can arrive a beat before the session accepts
|
|
134
|
+
// traffic — observed live: flush at connect said "friend is offline",
|
|
135
|
+
// and nothing retried until the user's next send, so a queued message
|
|
136
|
+
// sat visibly stuck despite the peer being online. One delayed retry
|
|
137
|
+
// covers that gap; the per-send chain and later reconnects cover the
|
|
138
|
+
// rest.
|
|
139
|
+
if (e.status === "connected") {
|
|
140
|
+
void this.#flushOutbox(e.pubkey)
|
|
141
|
+
.catch(() => { })
|
|
142
|
+
.then(() => {
|
|
143
|
+
if (this.#messages.queuedOutgoing(e.pubkey).length === 0)
|
|
144
|
+
return;
|
|
145
|
+
return new Promise((r) => setTimeout(r, 4000)).then(() => this.#flushOutbox(e.pubkey).catch(() => { }));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
135
148
|
});
|
|
136
149
|
this.#node.on("call-signal", (evt) => {
|
|
137
150
|
this.#callQueue.push({ userid: evt.pubkey, data: Buffer.from(evt.data).toString("utf-8") });
|
|
@@ -312,6 +325,18 @@ export class EmbeddedHost {
|
|
|
312
325
|
alias: meta?.alias,
|
|
313
326
|
name: meta?.alias || f.name || f.carrierId,
|
|
314
327
|
status: f.status,
|
|
328
|
+
// Shareable Carrier address — the SDK derives it when the
|
|
329
|
+
// record lacks one, so this is always populated (peer ≥0.1.138).
|
|
330
|
+
address: f.address,
|
|
331
|
+
// Friendship established vs merely requested. acceptedAt is the
|
|
332
|
+
// SDK's cryptographic proof (session established, or their
|
|
333
|
+
// DHT-PK announce); an inbound message is equally conclusive
|
|
334
|
+
// and covers records that predate acceptedAt. Without this the
|
|
335
|
+
// UI shows a live-looking chat while every send quietly queues
|
|
336
|
+
// (INBOX 2026-08-05: 13 messages, zero delivered, no clue why).
|
|
337
|
+
accepted: !!f.acceptedAt ||
|
|
338
|
+
f.status === "online" ||
|
|
339
|
+
this.#messages.hasInbound(f.carrierId),
|
|
315
340
|
lastSeen: f.acceptedAt,
|
|
316
341
|
pinned: meta?.pinned ?? false,
|
|
317
342
|
lastMessage: lastMsg ? { dir: lastMsg.dir, text: lastMsg.text, ts: lastMsg.ts } : undefined,
|
package/dist/message-store.d.ts
CHANGED
|
@@ -59,6 +59,10 @@ export declare class MessageStore {
|
|
|
59
59
|
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
60
60
|
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
61
61
|
setStatus(peer: string, id: string, status?: "sending" | "queued" | "sent" | "failed"): boolean;
|
|
62
|
+
/** Has this peer ever sent us anything? Used as proof-of-friendship for
|
|
63
|
+
* records that predate the SDK's acceptedAt bookkeeping — a message can
|
|
64
|
+
* only arrive over an established session. */
|
|
65
|
+
hasInbound(peer: string): boolean;
|
|
62
66
|
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
63
67
|
* first — both queued text and queued file chips. The daemon drains this
|
|
64
68
|
* on a friend's reconnect. */
|
package/dist/message-store.js
CHANGED
|
@@ -74,6 +74,12 @@ export class MessageStore {
|
|
|
74
74
|
this.scheduleSave();
|
|
75
75
|
return true;
|
|
76
76
|
}
|
|
77
|
+
/** Has this peer ever sent us anything? Used as proof-of-friendship for
|
|
78
|
+
* records that predate the SDK's acceptedAt bookkeeping — a message can
|
|
79
|
+
* only arrive over an established session. */
|
|
80
|
+
hasInbound(peer) {
|
|
81
|
+
return (this.byPeer.get(peer) ?? []).some((m) => m.dir === "in");
|
|
82
|
+
}
|
|
77
83
|
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
78
84
|
* first — both queued text and queued file chips. The daemon drains this
|
|
79
85
|
* on a friend's reconnect. */
|
package/dist/server.js
CHANGED
|
@@ -20,6 +20,7 @@ import { dirname, join } from "node:path";
|
|
|
20
20
|
import { INSTALL_COMMAND as LAN_INSTALL_COMMAND } from "./lan-handoff.js";
|
|
21
21
|
import yaml from "js-yaml";
|
|
22
22
|
import { DEFAULT_EXITS } from "./exits.js";
|
|
23
|
+
import * as peerAddr from "@decentnetwork/peer";
|
|
23
24
|
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
24
25
|
// scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
|
|
25
26
|
const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
@@ -690,6 +691,11 @@ export function startBeagleServer(opts) {
|
|
|
690
691
|
// another friend (userid only works once already friends).
|
|
691
692
|
address: f.address || "",
|
|
692
693
|
online,
|
|
694
|
+
// Friendship not yet accepted by the other side. Only trusted when
|
|
695
|
+
// the backend states it explicitly (embedded does); a daemon
|
|
696
|
+
// backend that doesn't report it defaults to established, so the
|
|
697
|
+
// banner can never wrongly appear on an old backend.
|
|
698
|
+
pending: f.accepted === false,
|
|
693
699
|
via: online ? viaFromTransport(sess?.transport) : null,
|
|
694
700
|
ping: null,
|
|
695
701
|
ip: ipByUserid.get(uid) ?? "",
|
|
@@ -965,7 +971,18 @@ export function startBeagleServer(opts) {
|
|
|
965
971
|
return;
|
|
966
972
|
}
|
|
967
973
|
if (req.method === "POST" && url === "/api/add") {
|
|
968
|
-
|
|
974
|
+
let { address } = await readBody(req);
|
|
975
|
+
// Accept a 44-char userid too: the address is userid's pubkey plus a
|
|
976
|
+
// derivable suffix (nospam=0 network-wide + checksum), and users
|
|
977
|
+
// frequently have only the userid — it's what every other surface
|
|
978
|
+
// shows. Deriving here beats rejecting with "not an address".
|
|
979
|
+
try {
|
|
980
|
+
const raw = String(address ?? "").trim();
|
|
981
|
+
const bytes = peerAddr.base58ToBytes(raw);
|
|
982
|
+
if (bytes.length === 32)
|
|
983
|
+
address = peerAddr.carrierAddressFromPublicKey(bytes, 0);
|
|
984
|
+
}
|
|
985
|
+
catch { /* not base58 — let the backend judge it as-is */ }
|
|
969
986
|
// A friend-request is fire-and-forget: it's delivered when the peer is
|
|
970
987
|
// online and only becomes a friend once they accept. Don't let the UI
|
|
971
988
|
// hang on the network round-trip (DHT lookup / unreachable peer) — kick
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
29
29
|
"@decentnetwork/lan": "^0.1.256",
|
|
30
|
-
"@decentnetwork/peer": "^0.1.
|
|
30
|
+
"@decentnetwork/peer": "^0.1.138",
|
|
31
31
|
"@decentnetwork/peer-webrtc": "^0.2.10",
|
|
32
32
|
"js-yaml": "^4.1.0",
|
|
33
33
|
"yargs": "^17.7.2"
|