@decentnetwork/lan 0.1.200 → 0.1.202

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.
@@ -7,7 +7,7 @@ import { createConnection } from "net";
7
7
  import { createRequire } from "module";
8
8
  import { fileURLToPath } from "url";
9
9
  import { ConfigLoader, DEFAULT_DORAS, DEFAULT_EXITS } from "../config/loader.js";
10
- import { ipcSocketPath } from "../daemon/ipc.js";
10
+ import { ipcSocketPath, ipcSocketSupportsFsExistenceCheck, } from "../daemon/ipc.js";
11
11
  import { DaemonServer } from "../daemon/server.js";
12
12
  import { Ipam } from "../ipam/ipam.js";
13
13
  import { Policy } from "../acl/policy.js";
@@ -75,7 +75,9 @@ function assertDaemonNotRunning(config, commandName) {
75
75
  */
76
76
  async function ipcCall(config, req, timeoutMs = 30_000) {
77
77
  const sockPath = ipcSocketPath(config.carrier.dataDir);
78
- if (!existsSync(sockPath)) {
78
+ // fs.existsSync works for Unix-domain sockets, but always reports false for
79
+ // Windows named pipes. On Windows, connect directly and trust net's error.
80
+ if (ipcSocketSupportsFsExistenceCheck() && !existsSync(sockPath)) {
79
81
  throw new Error(`Daemon socket not found at ${sockPath} — is the daemon running?`);
80
82
  }
81
83
  return new Promise((resolve, reject) => {
@@ -154,3 +154,12 @@ export declare class IpcServer {
154
154
  /** Derive the socket path that pairs with a given carrier data dir.
155
155
  * Kept as a one-liner helper so daemon and client agree. */
156
156
  export declare function ipcSocketPath(dataDir: string, platform?: NodeJS.Platform): string;
157
+ /** Whether the IPC endpoint can be probed with fs.existsSync().
158
+ *
159
+ * Unix-domain sockets have filesystem entries. Windows named pipes live in
160
+ * the pipe namespace instead: Node can connect to them with net.connect(),
161
+ * but fs.existsSync("\\\\.\\pipe\\...") reports false even while the server is
162
+ * listening. Clients must therefore skip the filesystem preflight on Windows
163
+ * and let net.connect() report a real connection error instead.
164
+ */
165
+ export declare function ipcSocketSupportsFsExistenceCheck(platform?: NodeJS.Platform): boolean;
@@ -285,3 +285,14 @@ export function ipcSocketPath(dataDir, platform = process.platform) {
285
285
  // on macOS; <dataDir>/daemon.sock fits comfortably for sane homedirs.
286
286
  return `${dataDir.replace(/\/+$/, "")}/daemon.sock`;
287
287
  }
288
+ /** Whether the IPC endpoint can be probed with fs.existsSync().
289
+ *
290
+ * Unix-domain sockets have filesystem entries. Windows named pipes live in
291
+ * the pipe namespace instead: Node can connect to them with net.connect(),
292
+ * but fs.existsSync("\\\\.\\pipe\\...") reports false even while the server is
293
+ * listening. Clients must therefore skip the filesystem preflight on Windows
294
+ * and let net.connect() report a real connection error instead.
295
+ */
296
+ export function ipcSocketSupportsFsExistenceCheck(platform = process.platform) {
297
+ return platform !== "win32";
298
+ }
@@ -1494,10 +1494,41 @@ function NetworkTab({ T, me, peers, exits, activeExit, reqCount, onSetExit, onOp
1494
1494
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit }))))));
1495
1495
  }
1496
1496
  Object.assign(window, { NetworkTab });
1497
- function FieldRow({ label, value, mono = true, copy, qr, onQr, last }) {
1498
- return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, label), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: mono ? "var(--mono)" : "var(--ui)", fontSize: 13.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, value), copy && /* @__PURE__ */ React.createElement(Btn, { icon: "copy", size: "sm", onClick: () => dkCopy(value) }), qr && /* @__PURE__ */ React.createElement(Btn, { icon: "qr", size: "sm", onClick: () => onQr && onQr(value, label) }));
1497
+ function CopyBtn({ value, copiedText = "Copied", copyFailedText = "Copy failed", copyTitle = "Copy" }) {
1498
+ const [status, setStatus] = React.useState(null);
1499
+ const timer = React.useRef(null);
1500
+ React.useEffect(() => () => {
1501
+ if (timer.current) clearTimeout(timer.current);
1502
+ }, []);
1503
+ const onCopy = async () => {
1504
+ if (timer.current) clearTimeout(timer.current);
1505
+ setStatus("copied");
1506
+ timer.current = setTimeout(() => setStatus(null), 1400);
1507
+ const ok = await dkCopy(value);
1508
+ if (!ok) {
1509
+ setStatus("failed");
1510
+ if (timer.current) clearTimeout(timer.current);
1511
+ timer.current = setTimeout(() => setStatus(null), 1400);
1512
+ }
1513
+ };
1514
+ const message = status === "copied" ? copiedText : copyFailedText;
1515
+ return /* @__PURE__ */ React.createElement("span", { "aria-live": "polite", style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(
1516
+ Btn,
1517
+ {
1518
+ icon: status === "copied" ? "check" : "copy",
1519
+ tone: status === "copied" ? "ok" : status === "failed" ? "danger" : "ghost",
1520
+ size: "sm",
1521
+ title: status ? message : copyTitle,
1522
+ onClick: onCopy,
1523
+ style: { minWidth: 82 }
1524
+ },
1525
+ status ? message : copyTitle
1526
+ ));
1527
+ }
1528
+ function FieldRow({ T, label, value, mono = true, copy, qr, onQr, last }) {
1529
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, label), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: mono ? "var(--mono)" : "var(--ui)", fontSize: 13.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, value), copy && /* @__PURE__ */ React.createElement(CopyBtn, { value, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy }), qr && /* @__PURE__ */ React.createElement(Btn, { icon: "qr", size: "sm", onClick: () => onQr && onQr(value, label) }));
1499
1530
  }
1500
- function DkQrModal({ value, label, onClose }) {
1531
+ function DkQrModal({ T, value, label, onClose }) {
1501
1532
  const qr = React.useMemo(() => {
1502
1533
  if (typeof qrcode === "undefined") return null;
1503
1534
  try {
@@ -1517,7 +1548,7 @@ function DkQrModal({ value, label, onClose }) {
1517
1548
  (_, r) => Array.from({ length: count }).map(
1518
1549
  (__, c) => qr.isDark(r, c) ? /* @__PURE__ */ React.createElement("rect", { key: `${r}-${c}`, x: c + quiet, y: r + quiet, width: 1.04, height: 1.04, fill: "#000" }) : null
1519
1550
  )
1520
- )) : /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", padding: 48 } }, "QR unavailable"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", wordBreak: "break-all", textAlign: "center", maxWidth: 280, lineHeight: 1.5 } }, value), /* @__PURE__ */ React.createElement(Btn, { icon: "copy", size: "sm", onClick: () => dkCopy(value) }, "copy")));
1551
+ )) : /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", padding: 48 } }, "QR unavailable"), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", wordBreak: "break-all", textAlign: "center", maxWidth: 280, lineHeight: 1.5 } }, value), /* @__PURE__ */ React.createElement(CopyBtn, { value, copiedText: T.copied, copyFailedText: T.copyFailed, copyTitle: T.copy })));
1521
1552
  }
1522
1553
  function Card({ label, children, trailing }) {
1523
1554
  return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10 } }, /* @__PURE__ */ React.createElement(Section, { label, trailing }), /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, children));
@@ -1540,7 +1571,7 @@ function DkEditModal({ T, me, onClose, onSave }) {
1540
1571
  function ProfileTab({ T, me, onEdit }) {
1541
1572
  const [qr, setQr] = React.useState(null);
1542
1573
  const [editing, setEditing] = React.useState(false);
1543
- 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, { label: T.userId, value: me.userId, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.carrierAddr, value: me.carrier, copy: true, qr: true, onQr: (v, l) => setQr({ value: v, label: l }) }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(FieldRow, { 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, { value: qr.value, label: qr.label, onClose: () => setQr(null) }), editing && /* @__PURE__ */ React.createElement(DkEditModal, { T, me, onClose: () => setEditing(false), onSave: (name, description) => {
1574
+ 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) => {
1544
1575
  if (onEdit) onEdit(name, description);
1545
1576
  setEditing(false);
1546
1577
  } }));
@@ -1959,6 +1990,9 @@ const STR = {
1959
1990
  virtualIp: "virtual ip",
1960
1991
  version: "version",
1961
1992
  editProfile: "edit",
1993
+ copy: "Copy",
1994
+ copied: "Copied",
1995
+ copyFailed: "Copy failed",
1962
1996
  dangerZone: "Danger zone",
1963
1997
  deleteNode: "Delete this node",
1964
1998
  deleteSub: "Permanently remove identity & keys from this device",
@@ -2036,6 +2070,9 @@ const STR = {
2036
2070
  virtualIp: "\u865A\u62DF IP",
2037
2071
  version: "\u7248\u672C",
2038
2072
  editProfile: "\u7F16\u8F91",
2073
+ copy: "\u590D\u5236",
2074
+ copied: "\u5DF2\u590D\u5236",
2075
+ copyFailed: "\u590D\u5236\u5931\u8D25",
2039
2076
  dangerZone: "\u5371\u9669\u64CD\u4F5C",
2040
2077
  deleteNode: "\u5220\u9664\u6B64\u8282\u70B9",
2041
2078
  deleteSub: "\u4ECE\u672C\u8BBE\u5907\u6C38\u4E45\u79FB\u9664\u8EAB\u4EFD\u4E0E\u5BC6\u94A5",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
4
4
  "description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",