@decentnetwork/beagle 0.1.68 → 0.1.70

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.
@@ -48,3 +48,16 @@ bootstrapNodes:
48
48
  - host: 52.83.191.228
49
49
  port: 33445
50
50
  pk: 3khtxZo89SBScAMaHhTvD68pPHiKxgZT6hTCSZZVgNEm
51
+
52
+ # Store-and-forward relays. Shipped for the same reason the bootstraps are:
53
+ # without them a fresh install has no express client at all, so it never
54
+ # collects offline mail and a friend request sent while it was away is never
55
+ # seen. Previously these came only from config.yaml, which a new install does
56
+ # not have — measured 2026-08-30: a browser posted a friend request to express
57
+ # (recipient inbox grew 320 -> 672 bytes) and the recipient, a fresh desktop
58
+ # install, never pulled it.
59
+ expressNodes:
60
+ - host: lens.beagle.chat
61
+ port: 443
62
+ pk: ECbs4GxwGzxGerNkmqDJFibEmevu8jAXqAZtikccvD95
63
+ tls: true
@@ -91,9 +91,13 @@ export declare class CarrierNode extends EventEmitter {
91
91
  sessionStatus(pubkey: string): unknown;
92
92
  dhtHealth(): unknown;
93
93
  sendText(userid: string, text: string): Promise<void>;
94
- sendFriendRequest(address: string, hello?: string): Promise<void>;
94
+ sendFriendRequest(address: string, hello?: string, opts?: {
95
+ force?: boolean;
96
+ }): Promise<void>;
95
97
  acceptFriendRequest(pubkey: string): Promise<void>;
96
98
  removeFriend(userid: string): boolean;
99
+ /** Decline an inbound request and clear what it created in the SDK. */
100
+ rejectFriendRequest(pubkey: string): void;
97
101
  setUserInfo(info: {
98
102
  name?: string;
99
103
  description?: string;
@@ -180,8 +180,8 @@ export class CarrierNode extends EventEmitter {
180
180
  sendText(userid, text) {
181
181
  return this.#require().sendText(userid, text);
182
182
  }
183
- sendFriendRequest(address, hello) {
184
- return this.#require().sendFriendRequest(address, hello);
183
+ sendFriendRequest(address, hello, opts) {
184
+ return this.#require().sendFriendRequest(address, hello, opts);
185
185
  }
186
186
  acceptFriendRequest(pubkey) {
187
187
  return this.#require().acceptFriendRequest(pubkey);
@@ -189,6 +189,10 @@ export class CarrierNode extends EventEmitter {
189
189
  removeFriend(userid) {
190
190
  return this.#require().removeFriend(userid);
191
191
  }
192
+ /** Decline an inbound request and clear what it created in the SDK. */
193
+ rejectFriendRequest(pubkey) {
194
+ this.#require().rejectFriendRequest(pubkey);
195
+ }
192
196
  setUserInfo(info) {
193
197
  this.#require().setUserInfo(info);
194
198
  }
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.68";
1
+ window.__DK_UI_VERSION="0.1.70";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -474,7 +474,12 @@ export class EmbeddedHost {
474
474
  if (!address)
475
475
  throw new Error("friend-request requires an address");
476
476
  void this.#node
477
- .sendFriendRequest(address, req.hello)
477
+ // force: this op only runs because a person pressed Add. The SDK's
478
+ // idempotency guard is for automatic retries; here it would silently
479
+ // swallow the one case that needs to get through — re-adding someone
480
+ // whose earlier request was rejected, where the two sides' notions
481
+ // of "friend" have come apart and only a real send can fix it.
482
+ .sendFriendRequest(address, req.hello, { force: true })
478
483
  .catch((e) => this.#logger.info(`friend-request to ${address.slice(0, 8)} failed: ${e.message}`));
479
484
  return {};
480
485
  }
@@ -499,9 +504,23 @@ export class EmbeddedHost {
499
504
  }
500
505
  return {};
501
506
  }
502
- case "friends-reject":
507
+ case "friends-reject": {
508
+ // Read BEFORE deleting — the pubkey is what the SDK is keyed on.
509
+ const entry = this.#pending.get(uid);
503
510
  this.#pending.delete(uid);
511
+ // Tell the PEER too. Dropping only this map left the SDK holding the
512
+ // friend record the request created, so the next request from that
513
+ // person hit its "already a friend — treat as reconnection" path and
514
+ // was swallowed before the app ever saw it. Reject once and they could
515
+ // never reach you again, with nothing on either side saying why.
516
+ try {
517
+ this.#node.rejectFriendRequest(entry?.pubkey ?? uid);
518
+ }
519
+ catch (e) {
520
+ this.#logger.info(`reject cleanup failed for ${uid}: ${e.message}`);
521
+ }
504
522
  return {};
523
+ }
505
524
  case "friends-autoaccept": {
506
525
  // Without an explicit boolean this is a READ — a bare call must never
507
526
  // flip the switch off (Boolean(undefined) === false).
@@ -12,16 +12,26 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { dirname, resolve } from "node:path";
14
14
  import yaml from "js-yaml";
15
- function shippedBootstraps() {
15
+ function shippedConfig() {
16
16
  try {
17
17
  const file = resolve(dirname(fileURLToPath(import.meta.url)), "..", "config", "bootstrap-nodes.yaml");
18
- const parsed = yaml.load(readFileSync(file, "utf-8"));
19
- return (parsed?.bootstrapNodes ?? []).filter((n) => n?.host && n?.pk);
18
+ return (yaml.load(readFileSync(file, "utf-8")) ?? {});
20
19
  }
21
20
  catch {
22
- return [];
21
+ return {};
23
22
  }
24
23
  }
24
+ function shippedBootstraps() {
25
+ return (shippedConfig().bootstrapNodes ?? []).filter((n) => n?.host && n?.pk);
26
+ }
27
+ /** Store-and-forward relays, shipped like the bootstraps.
28
+ *
29
+ * These used to come ONLY from config.yaml, which a fresh install does not
30
+ * have — so a new desktop had no express client, never pulled offline mail,
31
+ * and silently missed every friend request sent while it was away. */
32
+ function shippedExpress() {
33
+ return (shippedConfig().expressNodes ?? []).filter((n) => n?.host && n?.pk);
34
+ }
25
35
  function valid(nodes) {
26
36
  return Array.isArray(nodes) && nodes.length > 0 && nodes.every((n) => n && typeof n.host === "string" && typeof n.pk === "string");
27
37
  }
@@ -39,7 +49,8 @@ export function loadNodeConfig(configDir) {
39
49
  const merged = valid(fromFile) ? [...fromFile, ...extra] : shipped;
40
50
  return {
41
51
  bootstrapNodes: merged.length ? merged : shipped,
42
- expressNodes: cfg.carrier?.expressNodes,
52
+ // Config first, shipped defaults when it says nothing.
53
+ expressNodes: cfg.carrier?.expressNodes?.length ? cfg.carrier.expressNodes : shippedExpress(),
43
54
  nickname: cfg.node?.name,
44
55
  statusMessage: cfg.node?.statusMessage,
45
56
  autoAccept: cfg.friends?.autoAccept ?? true,
@@ -49,7 +60,9 @@ export function loadNodeConfig(configDir) {
49
60
  // Malformed config must not stop the app — fall through to defaults.
50
61
  }
51
62
  }
52
- return { bootstrapNodes: shipped, autoAccept: true };
63
+ // No config.yaml at all a fresh install. It still needs the express
64
+ // relays, or it will never collect a friend request sent while it was away.
65
+ return { bootstrapNodes: shipped, expressNodes: shippedExpress(), autoAccept: true };
53
66
  }
54
67
  /** Persist the auto-accept switch into config.yaml, preserving everything
55
68
  * else in the file. Without this the runtime toggle lasted until the next
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
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.283",
30
- "@decentnetwork/peer": "0.1.151",
30
+ "@decentnetwork/peer": "0.1.152",
31
31
  "@decentnetwork/peer-webrtc": "^0.2.16",
32
32
  "js-yaml": "^4.1.0",
33
33
  "yargs": "^17.7.2"