@decentnetwork/beagle 0.1.6 → 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/call-ice.js +10 -31
- package/dist/desktop/app.js +26 -3
- package/dist/embedded-host.js +60 -21
- package/dist/message-store.d.ts +12 -6
- package/dist/message-store.js +11 -0
- package/dist/server.js +18 -1
- package/package.json +3 -3
package/dist/call-ice.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// Carrier bootstrap TURN credentials for browser WebRTC.
|
|
2
2
|
//
|
|
3
3
|
// Android online calls use CarrierExtension.getTurnServerInfo() →
|
|
4
|
-
// carrier_get_turn_server().
|
|
5
|
-
//
|
|
6
|
-
// deriveCarrierTurnCreds via the package dist path (older peer builds).
|
|
4
|
+
// carrier_get_turn_server(). We call @decentnetwork/peer getIceServers()
|
|
5
|
+
// (0.1.136+) with the same credential scheme.
|
|
7
6
|
import { readFileSync, existsSync } from "node:fs";
|
|
8
|
-
import {
|
|
9
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
-
import { base58ToBytes, carrierIdFromPublicKey } from "@decentnetwork/peer";
|
|
7
|
+
import { carrierIdFromPublicKey, getIceServers } from "@decentnetwork/peer";
|
|
11
8
|
function hexToBytes(hex) {
|
|
12
9
|
const clean = hex.trim().replace(/^0x/i, "");
|
|
13
10
|
if (clean.length % 2 !== 0)
|
|
@@ -38,29 +35,11 @@ export async function buildCarrierCallIceServers(opts) {
|
|
|
38
35
|
if (!nodes.length)
|
|
39
36
|
throw new Error("no bootstrap nodes for TURN");
|
|
40
37
|
const { publicKey, secretKey } = loadKeyPair(opts.keyFile);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
ourSecretKey: secretKey,
|
|
49
|
-
limit: opts.limit ?? 3,
|
|
50
|
-
includeTcpTurn: true,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const { deriveCarrierTurnCreds } = (await import(pathToFileURL(join(peerRoot, "turn-creds.js")).href));
|
|
54
|
-
const iceServers = [];
|
|
55
|
-
for (const n of nodes) {
|
|
56
|
-
const creds = deriveCarrierTurnCreds({
|
|
57
|
-
bootnodeHost: n.host,
|
|
58
|
-
bootnodePublicKey: base58ToBytes(n.pk),
|
|
59
|
-
ourUserid: userid,
|
|
60
|
-
ourSecretKey: secretKey,
|
|
61
|
-
});
|
|
62
|
-
const base = `${creds.host}:${creds.port}`;
|
|
63
|
-
iceServers.push({ urls: `stun:${base}`, username: creds.username, credential: creds.password }, { urls: `turn:${base}`, username: creds.username, credential: creds.password }, { urls: `turn:${base}?transport=tcp`, username: creds.username, credential: creds.password });
|
|
64
|
-
}
|
|
65
|
-
return iceServers;
|
|
38
|
+
return getIceServers({
|
|
39
|
+
bootstrapNodes: nodes,
|
|
40
|
+
ourUserid: carrierIdFromPublicKey(publicKey),
|
|
41
|
+
ourSecretKey: secretKey,
|
|
42
|
+
limit: opts.limit ?? 3,
|
|
43
|
+
includeTcpTurn: true,
|
|
44
|
+
});
|
|
66
45
|
}
|
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.7";
|
|
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"/>',
|
|
@@ -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("");
|
|
@@ -1964,7 +1964,15 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
|
|
|
1964
1964
|
style: { background: "none", border: "none", cursor: "pointer", padding: 0, marginLeft: 2, display: "inline-flex", opacity: 0.55 }
|
|
1965
1965
|
},
|
|
1966
1966
|
/* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 11, stroke: 2, color: "var(--faint)" })
|
|
1967
|
-
), mine && m.status === "queued"
|
|
1967
|
+
), mine && (m.status === "queued" || m.status === "sending") ? /* @__PURE__ */ React.createElement(
|
|
1968
|
+
"span",
|
|
1969
|
+
{
|
|
1970
|
+
style: { display: "inline-flex", alignItems: "center", gap: 2 },
|
|
1971
|
+
title: m.status === "sending" ? "delivering\u2026" : "waiting for peer \u2014 will send when they're online"
|
|
1972
|
+
},
|
|
1973
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--faint)" }),
|
|
1974
|
+
/* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.status === "sending" ? T.sendingMsg || "sending" : T.queued || "queued")
|
|
1975
|
+
) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
1968
1976
|
);
|
|
1969
1977
|
}
|
|
1970
1978
|
function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
|
|
@@ -2253,6 +2261,17 @@ ${peer.address}`
|
|
|
2253
2261
|
setMenu(false);
|
|
2254
2262
|
onRemove(peer);
|
|
2255
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.")),
|
|
2256
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(
|
|
2257
2276
|
Msg,
|
|
2258
2277
|
{
|
|
@@ -3466,9 +3485,11 @@ const STR = {
|
|
|
3466
3485
|
send: "Send",
|
|
3467
3486
|
pickPeer: "Select a peer to open the conversation",
|
|
3468
3487
|
queued: "queued",
|
|
3488
|
+
sendingMsg: "sending",
|
|
3469
3489
|
open: "open",
|
|
3470
3490
|
retry: "Retry",
|
|
3471
3491
|
cancel: "Cancel",
|
|
3492
|
+
pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
|
|
3472
3493
|
cancelling: "Cancelling\u2026",
|
|
3473
3494
|
cancelled: "Transfer cancelled",
|
|
3474
3495
|
retrying: "Retrying\u2026",
|
|
@@ -3560,9 +3581,11 @@ const STR = {
|
|
|
3560
3581
|
send: "\u53D1\u9001",
|
|
3561
3582
|
pickPeer: "\u9009\u62E9\u4E00\u4F4D\u597D\u53CB\u5F00\u59CB\u4F1A\u8BDD",
|
|
3562
3583
|
queued: "\u5F85\u53D1\u9001",
|
|
3584
|
+
sendingMsg: "\u53D1\u9001\u4E2D",
|
|
3563
3585
|
open: "\u6253\u5F00",
|
|
3564
3586
|
retry: "\u91CD\u8BD5",
|
|
3565
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",
|
|
3566
3589
|
cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
|
|
3567
3590
|
cancelled: "\u5DF2\u53D6\u6D88\u4F20\u8F93",
|
|
3568
3591
|
retrying: "\u6B63\u5728\u91CD\u8BD5\u2026",
|
package/dist/embedded-host.js
CHANGED
|
@@ -47,6 +47,10 @@ export class EmbeddedHost {
|
|
|
47
47
|
#logger = new Logger({ prefix: "Beagle" });
|
|
48
48
|
#opts;
|
|
49
49
|
#events = new EventEmitter();
|
|
50
|
+
/** Per-peer delivery chains: background sends to one peer run strictly in
|
|
51
|
+
* order, so a new message can never overtake an older one whose ACK is
|
|
52
|
+
* still pending. Entry removed when the chain drains. */
|
|
53
|
+
#sendChains = new Map();
|
|
50
54
|
/** Friend requests held for manual accept when auto-accept is off. */
|
|
51
55
|
#pending = new Map();
|
|
52
56
|
/** Inbound call signals waiting for the UI's long-poll to collect them. */
|
|
@@ -126,8 +130,21 @@ export class EmbeddedHost {
|
|
|
126
130
|
});
|
|
127
131
|
this.#node.on("friend-connection", (e) => {
|
|
128
132
|
this.#events.emit("event", { type: "presence", userid: e.pubkey, status: e.status });
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|
|
131
148
|
});
|
|
132
149
|
this.#node.on("call-signal", (evt) => {
|
|
133
150
|
this.#callQueue.push({ userid: evt.pubkey, data: Buffer.from(evt.data).toString("utf-8") });
|
|
@@ -308,6 +325,18 @@ export class EmbeddedHost {
|
|
|
308
325
|
alias: meta?.alias,
|
|
309
326
|
name: meta?.alias || f.name || f.carrierId,
|
|
310
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),
|
|
311
340
|
lastSeen: f.acceptedAt,
|
|
312
341
|
pinned: meta?.pinned ?? false,
|
|
313
342
|
lastMessage: lastMsg ? { dir: lastMsg.dir, text: lastMsg.text, ts: lastMsg.ts } : undefined,
|
|
@@ -368,27 +397,37 @@ export class EmbeddedHost {
|
|
|
368
397
|
// user's own words vanish from their own thread — they watched the
|
|
369
398
|
// peer answer a question that wasn't on screen (INBOX 2026-08-02).
|
|
370
399
|
// A send failure is a delivery state ON the message, never its absence.
|
|
371
|
-
const msg = this.#messages.append(uid, "out", text, Date.now());
|
|
400
|
+
const msg = this.#messages.append(uid, "out", text, Date.now(), "sending");
|
|
372
401
|
this.#meta.ensure(uid);
|
|
373
402
|
this.#events.emit("event", { type: "chat", userid: uid, dir: "out" });
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
403
|
+
// Return NOW. The delivery attempt runs in the background — an ACK
|
|
404
|
+
// that takes 15s per hop must cost the network 15s, never the user
|
|
405
|
+
// (INBOX 2026-08-05: a peer that never ACKed made every send block
|
|
406
|
+
// the UI for 30s, which reads as "stuck" and provokes re-sends).
|
|
407
|
+
// Per-peer chaining keeps FIFO: anything queued flushes first, and a
|
|
408
|
+
// second send waits for this one's attempt before its own.
|
|
409
|
+
const prev = this.#sendChains.get(uid) ?? Promise.resolve();
|
|
410
|
+
const job = prev.then(async () => {
|
|
411
|
+
await this.#flushOutbox(uid).catch(() => { });
|
|
412
|
+
try {
|
|
413
|
+
await this.#node.sendText(uid, text);
|
|
414
|
+
this.#messages.setStatus(uid, msg.id, "sent");
|
|
415
|
+
}
|
|
416
|
+
catch (e) {
|
|
417
|
+
// Offline peer or failed delivery: same recovery either way —
|
|
418
|
+
// "queued" (the UI shows the clock) and #flushOutbox delivers it
|
|
419
|
+
// on the friend's reconnect. A status update, not a failure.
|
|
420
|
+
this.#logger.info(`send to ${uid.slice(0, 8)} queued: ${e.message}`);
|
|
421
|
+
this.#messages.setStatus(uid, msg.id, "queued");
|
|
422
|
+
}
|
|
423
|
+
this.#events.emit("event", { type: "chat", userid: uid, dir: "out" });
|
|
424
|
+
});
|
|
425
|
+
this.#sendChains.set(uid, job);
|
|
426
|
+
void job.finally(() => {
|
|
427
|
+
if (this.#sendChains.get(uid) === job)
|
|
428
|
+
this.#sendChains.delete(uid);
|
|
429
|
+
});
|
|
430
|
+
return {};
|
|
392
431
|
}
|
|
393
432
|
case "chat-log-local":
|
|
394
433
|
this.#messages.append(uid, req.dir ?? "in", String(req.text ?? ""), Date.now());
|
package/dist/message-store.d.ts
CHANGED
|
@@ -14,10 +14,12 @@ export interface ChatMessage {
|
|
|
14
14
|
ts: number;
|
|
15
15
|
/** Stable per-message id (ts + per-process sequence) for UI keys / dedup. */
|
|
16
16
|
id: string;
|
|
17
|
-
/** Outgoing-text delivery state. "
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
17
|
+
/** Outgoing-text delivery state. "sending" = the background delivery attempt
|
|
18
|
+
* is still running (chat-send returns before the network is consulted, so
|
|
19
|
+
* the UI never blocks on a slow peer). "queued" = that attempt gave up; the
|
|
20
|
+
* daemon will deliver it (in order) the moment the friend reconnects.
|
|
21
|
+
* Cleared once actually sent. */
|
|
22
|
+
status?: "sending" | "queued" | "sent" | "failed";
|
|
21
23
|
/** How this message traversed the network, for the UI to distinguish at a
|
|
22
24
|
* glance (different colors). "online" = live net_crypto session (direct/relay);
|
|
23
25
|
* "offline" = express store-and-forward (the friend or we were offline). Lets
|
|
@@ -52,11 +54,15 @@ export declare class MessageStore {
|
|
|
52
54
|
/** Append a message and schedule a flush. Returns the stored message.
|
|
53
55
|
* Pass `status: "queued"` for an outgoing text the peer wasn't online to
|
|
54
56
|
* receive — the daemon flushes it on reconnect. */
|
|
55
|
-
append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage;
|
|
57
|
+
append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "sending" | "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage;
|
|
56
58
|
/** Set (or, with undefined, clear) the delivery status on a text message.
|
|
57
59
|
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
58
60
|
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
59
|
-
setStatus(peer: string, id: string, status?: "queued" | "sent" | "failed"): boolean;
|
|
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;
|
|
60
66
|
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
61
67
|
* first — both queued text and queued file chips. The daemon drains this
|
|
62
68
|
* on a friend's reconnect. */
|
package/dist/message-store.js
CHANGED
|
@@ -33,6 +33,11 @@ export class MessageStore {
|
|
|
33
33
|
let total = 0;
|
|
34
34
|
for (const [peer, msgs] of Object.entries(raw)) {
|
|
35
35
|
if (Array.isArray(msgs)) {
|
|
36
|
+
// A "sending" state cannot survive a restart — the attempt it
|
|
37
|
+
// described died with the process. Requeue so #flushOutbox owns it.
|
|
38
|
+
for (const m of msgs)
|
|
39
|
+
if (m.status === "sending")
|
|
40
|
+
m.status = "queued";
|
|
36
41
|
this.byPeer.set(peer, msgs);
|
|
37
42
|
total += msgs.length;
|
|
38
43
|
}
|
|
@@ -69,6 +74,12 @@ export class MessageStore {
|
|
|
69
74
|
this.scheduleSave();
|
|
70
75
|
return true;
|
|
71
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
|
+
}
|
|
72
83
|
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
73
84
|
* first — both queued text and queued file chips. The daemon drains this
|
|
74
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Beagle
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -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"
|