@decentnetwork/beagle 0.1.14 → 0.1.16

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/cli.js CHANGED
@@ -11,7 +11,7 @@ import { homedir } from "node:os";
11
11
  import { startBeagleServer } from "./server.js";
12
12
  import { openPeerHost, defaultConfigDir, decentlanCarrierDir, makeDaemonHost } from "./peer-host.js";
13
13
  import { SwitchablePeerHost } from "./lan-handoff.js";
14
- import { loadNodeConfig } from "./node-config.js";
14
+ import { loadNodeConfig, saveAutoAccept } from "./node-config.js";
15
15
  function parseArgs(argv) {
16
16
  const get = (flag) => {
17
17
  const i = argv.indexOf(flag);
@@ -66,6 +66,7 @@ async function main() {
66
66
  nickname,
67
67
  statusMessage,
68
68
  autoAcceptFriends: autoAccept,
69
+ onAutoAcceptChange: (enabled) => saveAutoAccept(args.configDir, enabled),
69
70
  force: args.backend,
70
71
  }));
71
72
  }
@@ -84,6 +85,7 @@ async function main() {
84
85
  nickname,
85
86
  statusMessage,
86
87
  autoAcceptFriends: autoAccept,
88
+ onAutoAcceptChange: (enabled) => saveAutoAccept(args.configDir, enabled),
87
89
  force: "embedded",
88
90
  })).host;
89
91
  const lanHost = new SwitchablePeerHost(peerHost, {
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.14";
1
+ window.__DK_UI_VERSION="0.1.15";
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"/>',
@@ -1582,7 +1582,7 @@ function dkGroupParse(text) {
1582
1582
  return null;
1583
1583
  return { sender: split.sender, senderId: null, body: split.body.trim(), ts, strong: !!ts };
1584
1584
  }
1585
- function DkGroupSender({ grp, T }) {
1585
+ function DkGroupSender({ grp, T, avatar }) {
1586
1586
  const [open, setOpen] = React.useState(false);
1587
1587
  const [busy, setBusy] = React.useState(false);
1588
1588
  const [done, setDone] = React.useState(null);
@@ -1591,18 +1591,18 @@ function DkGroupSender({ grp, T }) {
1591
1591
  setBusy(true);
1592
1592
  fetch("/api/add", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ address: grp.senderId }) }).then((r) => r.json()).then((r) => setDone(r.ok ? T && T.addSent || "request sent" : r.error || "failed")).catch((e) => setDone(String(e && e.message || e))).finally(() => setBusy(false));
1593
1593
  };
1594
- return /* @__PURE__ */ React.createElement("span", { style: { position: "relative", display: "inline-flex", alignItems: "center", gap: 5, margin: "0 3px 2px" } }, /* @__PURE__ */ React.createElement(
1594
+ return /* @__PURE__ */ React.createElement("span", { style: { position: "relative", display: "inline-flex", alignItems: "center", gap: 5, margin: avatar ? 0 : "0 3px 2px" } }, /* @__PURE__ */ React.createElement(
1595
1595
  "button",
1596
1596
  {
1597
1597
  onClick: () => setOpen((v) => !v),
1598
+ title: grp.sender,
1598
1599
  style: { display: "inline-flex", alignItems: "center", gap: 5, background: "none", border: "none", padding: 0, cursor: "pointer" }
1599
1600
  },
1600
- /* @__PURE__ */ React.createElement(DkIdenticon, { seed, size: 16, radius: 4 }),
1601
- /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, grp.sender)
1601
+ avatar ? /* @__PURE__ */ React.createElement(DkIdenticon, { seed, size: 24, radius: 6 }) : /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, grp.sender)
1602
1602
  ), open && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { onClick: () => setOpen(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("span", { style: {
1603
1603
  position: "absolute",
1604
1604
  left: 0,
1605
- top: 20,
1605
+ [avatar ? "bottom" : "top"]: avatar ? 28 : 20,
1606
1606
  zIndex: 50,
1607
1607
  width: 240,
1608
1608
  padding: 12,
@@ -1843,6 +1843,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
1843
1843
  const mine = m.from === "me";
1844
1844
  const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
1845
1845
  const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
1846
+ const grpSender = grp || (nativeMedia && nativeMedia.sender ? { sender: nativeMedia.sender, senderId: null } : null);
1846
1847
  const read = window.ChatComponents ? window.ChatComponents.readMessage((grp ? grp.body : m.text) || "") : { text: grp ? grp.body : m.text, form: null, interaction: null };
1847
1848
  const submitted = read.form && answered ? answered.get(read.form.custom_id) : null;
1848
1849
  const rtcFile = !mine && !m.file ? dkRtcFileFromText(peer.userId || peer.id, m.text) : null;
@@ -1875,7 +1876,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
1875
1876
  alignItems: "center",
1876
1877
  justifyContent: "center"
1877
1878
  } }, selected && /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 3, color: "#fff" })),
1878
- !mine && /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 24, radius: 6, dot: false }),
1879
+ !mine && (grpSender ? /* @__PURE__ */ React.createElement(DkGroupSender, { grp: grpSender, T, avatar: true }) : /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 24, radius: 6, dot: false })),
1879
1880
  /* @__PURE__ */ React.createElement("div", { style: { maxWidth: m.file && (m.file.media === "image" || m.file.media === "video") || rtcFile && (rtcFile.media === "image" || rtcFile.media === "video") ? "min(680px, 88%)" : "64%", display: "flex", flexDirection: "column", alignItems: mine ? "flex-end" : "flex-start" } }, callRec ? /* @__PURE__ */ React.createElement(
1880
1881
  "button",
1881
1882
  {
@@ -2795,7 +2796,19 @@ function DkEditModal({ T, me, onClose, onSave }) {
2795
2796
  function ProfileTab({ T, me, onEdit }) {
2796
2797
  const [qr, setQr] = React.useState(null);
2797
2798
  const [editing, setEditing] = React.useState(false);
2798
- return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__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(FieldRow, { T, label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}`, last: true })), /* @__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) => {
2799
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), me.isExit && /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, "exit", me.exitRegion ? ` \xB7 ${me.exitRegion.toUpperCase()}` : "")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle), me.description ? /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12.5, color: "var(--faint)", marginTop: 3 } }, me.description) : null), /* @__PURE__ */ React.createElement(Btn, { icon: "edit", onClick: () => setEditing(true) }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { T, label: T.netKey, value: me.netKey, copy: true, last: true })), typeof me.autoAccept === "boolean" && /* @__PURE__ */ React.createElement(Card, { label: T && T.friendsSettings || "Friends" }, /* @__PURE__ */ React.createElement("label", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", cursor: "pointer" } }, /* @__PURE__ */ React.createElement(
2800
+ "input",
2801
+ {
2802
+ type: "checkbox",
2803
+ checked: me.autoAccept,
2804
+ onChange: (e) => {
2805
+ const enabled = e.target.checked;
2806
+ fetch("/api/autoaccept", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ enabled }) }).catch(() => {
2807
+ });
2808
+ },
2809
+ style: { width: 16, height: 16, accentColor: "var(--accent)" }
2810
+ }
2811
+ ), /* @__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.")))), /* @__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(FieldRow, { T, label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}`, last: true })), /* @__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) => {
2799
2812
  if (onEdit)
2800
2813
  onEdit(name, description);
2801
2814
  setEditing(false);
@@ -3654,6 +3667,9 @@ const STR = {
3654
3667
  open: "open",
3655
3668
  retry: "Retry",
3656
3669
  cancel: "Cancel",
3670
+ friendsSettings: "Friends",
3671
+ autoAcceptLabel: "Auto-accept friend requests",
3672
+ autoAcceptSub: "Off: requests wait in the sidebar for your approval.",
3657
3673
  groupSenderUnknown: "The group relays only this display name. Register Beagle as a group agent (/agent add <beagle address> in the group) to receive full member identities you can add as friends.",
3658
3674
  pendingFriend: "Waiting for them to accept your friend request \u2014 messages will queue and deliver once they do.",
3659
3675
  cancelling: "Cancelling\u2026",
@@ -3752,6 +3768,9 @@ const STR = {
3752
3768
  open: "\u6253\u5F00",
3753
3769
  retry: "\u91CD\u8BD5",
3754
3770
  cancel: "\u53D6\u6D88",
3771
+ friendsSettings: "\u597D\u53CB",
3772
+ autoAcceptLabel: "\u81EA\u52A8\u63A5\u53D7\u597D\u53CB\u8BF7\u6C42",
3773
+ autoAcceptSub: "\u5173\u95ED\u540E\uFF0C\u597D\u53CB\u8BF7\u6C42\u4F1A\u7B49\u5F85\u4F60\u5728\u4FA7\u8FB9\u680F\u624B\u52A8\u786E\u8BA4\u3002",
3755
3774
  groupSenderUnknown: "\u7FA4\u670D\u52A1\u53EA\u8F6C\u53D1\u4E86\u663E\u793A\u540D\uFF0C\u62FF\u4E0D\u5230\u8BE5\u6210\u5458\u7684\u5B8C\u6574\u8EAB\u4EFD\u3002\u5728\u7FA4\u91CC\u6267\u884C /agent add <beagle \u5730\u5740> \u628A Beagle \u6CE8\u518C\u4E3A\u7FA4 agent \u540E\uFF0C\u5373\u53EF\u6536\u5230\u5B8C\u6574\u8EAB\u4EFD\u5E76\u4E00\u952E\u52A0\u597D\u53CB\u3002",
3756
3775
  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",
3757
3776
  cancelling: "\u6B63\u5728\u53D6\u6D88\u2026",
@@ -318,7 +318,7 @@ export class EmbeddedHost {
318
318
  const id = this.#node.identity();
319
319
  return {
320
320
  identity: id,
321
- node: { name: this.#opts.nickname ?? "", backend: "embedded" },
321
+ node: { name: this.#opts.nickname ?? "", backend: "embedded", autoAccept: this.#autoAccept },
322
322
  // No TUN in this backend — say so explicitly rather than omitting
323
323
  // the key, so the UI can distinguish "no LAN" from "unknown".
324
324
  tun: null,
@@ -380,8 +380,12 @@ export class EmbeddedHost {
380
380
  this.#pending.delete(uid);
381
381
  return {};
382
382
  case "friends-autoaccept": {
383
- this.#autoAccept = Boolean(req.enabled);
384
- this.#opts.onAutoAcceptChange?.(this.#autoAccept);
383
+ // Without an explicit boolean this is a READ — a bare call must never
384
+ // flip the switch off (Boolean(undefined) === false).
385
+ if (typeof req.enabled === "boolean") {
386
+ this.#autoAccept = req.enabled;
387
+ this.#opts.onAutoAcceptChange?.(this.#autoAccept);
388
+ }
385
389
  return { autoAccept: this.#autoAccept };
386
390
  }
387
391
  case "friend-remove":
@@ -14,3 +14,7 @@ export interface NodeConfig {
14
14
  autoAccept: boolean;
15
15
  }
16
16
  export declare function loadNodeConfig(configDir: string): NodeConfig;
17
+ /** Persist the auto-accept switch into config.yaml, preserving everything
18
+ * else in the file. Without this the runtime toggle lasted until the next
19
+ * restart and then silently reverted — worse than no switch. */
20
+ export declare function saveAutoAccept(configDir: string, enabled: boolean): void;
@@ -8,7 +8,7 @@
8
8
  // The bootstrap list itself is data, not code, so it lives in config/ and can
9
9
  // be refreshed without a release (decentlan's `agentnet bootstrap update`
10
10
  // pulls the published feed the same way).
11
- import { existsSync, readFileSync } from "node:fs";
11
+ 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";
@@ -51,3 +51,26 @@ export function loadNodeConfig(configDir) {
51
51
  }
52
52
  return { bootstrapNodes: shipped, autoAccept: true };
53
53
  }
54
+ /** Persist the auto-accept switch into config.yaml, preserving everything
55
+ * else in the file. Without this the runtime toggle lasted until the next
56
+ * restart and then silently reverted — worse than no switch. */
57
+ export function saveAutoAccept(configDir, enabled) {
58
+ const cfgPath = resolve(configDir, "config.yaml");
59
+ let cfg = {};
60
+ try {
61
+ if (existsSync(cfgPath))
62
+ cfg = (yaml.load(readFileSync(cfgPath, "utf-8")) ?? {});
63
+ }
64
+ catch {
65
+ // Malformed file: rather than destroy it with a rewrite, keep the toggle
66
+ // session-only.
67
+ return;
68
+ }
69
+ cfg.friends = { ...(cfg.friends ?? {}), autoAccept: enabled };
70
+ try {
71
+ writeFileSync(cfgPath, yaml.dump(cfg), "utf-8");
72
+ }
73
+ catch {
74
+ /* read-only config dir: session-only toggle */
75
+ }
76
+ }
@@ -37,6 +37,8 @@ export interface OpenPeerHostOptions {
37
37
  nickname?: string;
38
38
  statusMessage?: string;
39
39
  autoAcceptFriends?: boolean;
40
+ /** Persist a runtime auto-accept toggle (embedded backend only). */
41
+ onAutoAcceptChange?: (enabled: boolean) => void;
40
42
  /** Force a backend instead of auto-detecting. Mainly for testing the
41
43
  * embedded path on a machine that also runs a daemon. */
42
44
  force?: BackendKind;
package/dist/peer-host.js CHANGED
@@ -97,6 +97,7 @@ export async function openPeerHost(opts) {
97
97
  nickname: opts.nickname,
98
98
  statusMessage: opts.statusMessage,
99
99
  autoAcceptFriends: opts.autoAcceptFriends ?? true,
100
+ onAutoAcceptChange: opts.onAutoAcceptChange,
100
101
  });
101
102
  await host.start();
102
103
  return {
package/dist/server.js CHANGED
@@ -674,6 +674,9 @@ export function startBeagleServer(opts) {
674
674
  wire: opts.meExtra?.wire ?? "163",
675
675
  isExit: !!meExit,
676
676
  exitRegion: meExit?.region ?? null,
677
+ // null = backend didn't report it (daemon); the UI then hides the
678
+ // toggle rather than showing a state it cannot know.
679
+ autoAccept: typeof node.autoAccept === "boolean" ? node.autoAccept : null,
677
680
  };
678
681
  const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
679
682
  const peers = fl.map((f) => {
@@ -967,6 +970,12 @@ export function startBeagleServer(opts) {
967
970
  }
968
971
  return;
969
972
  }
973
+ if (req.method === "POST" && url === "/api/autoaccept") {
974
+ const { enabled } = await readBody(req);
975
+ const r = await opts.call({ op: "friends-autoaccept", enabled: !!enabled });
976
+ sendJson(res, r.ok ? 200 : 400, r);
977
+ return;
978
+ }
970
979
  if (req.method === "POST" && url === "/api/accept") {
971
980
  const { userid } = await readBody(req);
972
981
  const r = await opts.call({ op: "friends-accept", userid });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
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",