@decentnetwork/beagle 0.1.67 → 0.1.69

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
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.67";
1
+ window.__DK_UI_VERSION="0.1.69";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -145,6 +145,43 @@ export class SwitchablePeerHost {
145
145
  // not silently lose chat.
146
146
  if (this.#state === "daemon" && !daemonUp) {
147
147
  await this.#backToEmbedded("the daemon is no longer running");
148
+ return;
149
+ }
150
+ // ...and the way back, which was missing. Every `agentnet restart` — an
151
+ // ordinary event, and exactly what an update tells you to run — took the
152
+ // daemon away for a second or two. This state machine fell back to the
153
+ // embedded peer and then had no transition to return, so beagle stayed
154
+ // demoted for the life of the process: the daemon it was meant to front
155
+ // was running the whole time, invisible to it. The version panel then
156
+ // reported the APP's peer as the daemon's, so an update that had actually
157
+ // landed looked like it had not.
158
+ if (this.#state === "embedded" && daemonUp) {
159
+ await this.#backToDaemon();
160
+ }
161
+ }
162
+ /** Hand the identity back to a daemon that has (re)appeared. */
163
+ async #backToDaemon() {
164
+ if (this.#busy)
165
+ return;
166
+ this.#busy = true;
167
+ try {
168
+ this.#log.info("the daemon is back — handing the identity over to it");
169
+ const old = this.#inner;
170
+ // Null first: reject calls rather than racing a peer that is stopping,
171
+ // the same order the manual release uses.
172
+ this.#inner = null;
173
+ await old?.stop();
174
+ this.#inner = this.#opts.makeDaemon();
175
+ this.#setState("daemon");
176
+ }
177
+ catch (err) {
178
+ // Could not stop cleanly — go back to embedded on the next tick rather
179
+ // than sitting with a null backend.
180
+ this.#log.error(`could not hand over to the daemon: ${err.message}`);
181
+ this.#setState("embedded");
182
+ }
183
+ finally {
184
+ this.#busy = false;
148
185
  }
149
186
  }
150
187
  async #backToEmbedded(why) {
@@ -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.67",
3
+ "version": "0.1.69",
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",