@decentnetwork/lan 0.1.220 → 0.1.222

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.
@@ -1365,10 +1365,11 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, selMode, sele
1365
1365
  ), mine && m.status === "queued" ? /* @__PURE__ */ React.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 2 }, title: "waiting for peer \u2014 will send when they're online" }, /* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--faint)" }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, T.queued || "queued")) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
1366
1366
  );
1367
1367
  }
1368
- function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
1368
+ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
1369
1369
  const scrollRef = React.useRef(null);
1370
1370
  const followScrollRef = React.useRef(true);
1371
1371
  const fileRef = React.useRef(null);
1372
+ const rtcFileRef = React.useRef(null);
1372
1373
  const [menu, setMenu] = React.useState(false);
1373
1374
  const [selMode, setSelMode] = React.useState(false);
1374
1375
  const [sel, setSel] = React.useState(() => /* @__PURE__ */ new Set());
@@ -1541,12 +1542,13 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
1541
1542
  requestAnimationFrame(scrollToBottom);
1542
1543
  }
1543
1544
  };
1544
- const sendFiles = (files) => {
1545
- if (!files || !files.length || !onSendFile) return;
1545
+ const sendFiles = (files, mode) => {
1546
+ const sender = mode === "webrtc" ? onSendRtcFile : onSendFile;
1547
+ if (!files || !files.length || !sender) return;
1546
1548
  const file = files[0];
1547
1549
  followScrollRef.current = true;
1548
- setSending({ name: file.name });
1549
- Promise.resolve(onSendFile(file)).then((r) => {
1550
+ setSending({ name: file.name, via: mode });
1551
+ Promise.resolve(sender(file)).then((r) => {
1550
1552
  if (r && r.ok === false) window.alert((lang === "zh" ? "\u53D1\u9001\u5931\u8D25: " : "Send failed: ") + (r.error || ""));
1551
1553
  }).finally(() => {
1552
1554
  setSending(null);
@@ -1572,7 +1574,18 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
1572
1574
  onDrop
1573
1575
  },
1574
1576
  dragOver && /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", inset: 0, zIndex: 30, background: "rgba(91,140,255,0.10)", border: "2px dashed var(--accent)", borderRadius: 12, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none", fontFamily: "var(--mono)", fontSize: 14, color: "var(--accent)" } }, lang === "zh" ? "\u677E\u5F00\u53D1\u9001\u6587\u4EF6" : "Drop to send file"),
1575
- /* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--accent)", display: "inline-flex", alignItems: "center", gap: 4 } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 }), " ", peer.ip))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
1577
+ /* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--accent)", display: "inline-flex", alignItems: "center", gap: 4 } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 }), " ", peer.ip))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T && T.audioCall || "Audio call", onClick: () => onCall(peer.userId, false) }), onCall && /* @__PURE__ */ React.createElement(Btn, { icon: "video", title: T && T.videoCall || "Video call", onClick: () => onCall(peer.userId, true) }), onSendRtcFile && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1578
+ "input",
1579
+ {
1580
+ ref: rtcFileRef,
1581
+ type: "file",
1582
+ style: { display: "none" },
1583
+ onChange: (e) => {
1584
+ sendFiles(e.target.files, "webrtc");
1585
+ e.target.value = "";
1586
+ }
1587
+ }
1588
+ ), /* @__PURE__ */ React.createElement(Btn, { icon: "file", title: T && T.sendWebrtcFile || "Send file via WebRTC", onClick: () => rtcFileRef.current && rtcFileRef.current.click() })), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
1576
1589
  setMenu(false);
1577
1590
  onAlias(peer);
1578
1591
  } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "check", label: T.selectDelete || "Select & delete", onClick: () => {
@@ -1620,7 +1633,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
1620
1633
  stroke: 2.2,
1621
1634
  color: flash.kind === "err" ? "var(--danger)" : "var(--accent)"
1622
1635
  }
1623
- ), flash.msg), sending && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto 8px", display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "paperclip", size: 13, stroke: 2, color: "var(--accent)" }), (lang === "zh" ? "\u53D1\u9001\u4E2D: " : "Sending: ") + sending.name), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(
1636
+ ), flash.msg), sending && /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto 8px", display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "paperclip", size: 13, stroke: 2, color: "var(--accent)" }), (lang === "zh" ? "\u53D1\u9001\u4E2D: " : "Sending: ") + (sending.via === "webrtc" ? "WebRTC \xB7 " : "") + sending.name), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(
1624
1637
  "input",
1625
1638
  {
1626
1639
  ref: fileRef,
@@ -1715,9 +1728,9 @@ function MenuItem({ icon, label, onClick, danger }) {
1715
1728
  function ChatEmpty({ T }) {
1716
1729
  return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
1717
1730
  }
1718
- function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
1731
+ function ChatTab({ T, lang, peers, requests, activeId, thread, onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
1719
1732
  const peer = peers.find((p) => p.id === activeId);
1720
- return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, thread, onSend, onSendFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
1733
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0, minHeight: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct, onAdd }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, thread, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
1721
1734
  }
1722
1735
  Object.assign(window, { ChatTab });
1723
1736
  function StatTile({ label, value, sub, tone }) {
@@ -1835,8 +1848,8 @@ function ProfileTab({ T, me, onEdit }) {
1835
1848
  }
1836
1849
  Object.assign(window, { ProfileTab });
1837
1850
  const DK_FILE_RTC_KIND = "file";
1838
- const DK_FILE_RTC_CHUNK = 64 * 1024;
1839
- const DK_FILE_RTC_OPEN_TIMEOUT_MS = 8e3;
1851
+ const DK_FILE_RTC_CHUNK = 16 * 1024;
1852
+ const DK_FILE_RTC_OPEN_TIMEOUT_MS = 2e4;
1840
1853
  const DK_FILE_RTC_SIGNAL_TTL_MS = 10 * 60 * 1e3;
1841
1854
  function dkFileRtcId() {
1842
1855
  if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
@@ -1845,12 +1858,17 @@ function dkFileRtcId() {
1845
1858
  function dkFileRtcEnvelope(fileId, type, extra) {
1846
1859
  return Object.assign({ dkRtc: 1, kind: DK_FILE_RTC_KIND, fileId, type, ts: Date.now() }, extra || {});
1847
1860
  }
1848
- function dkWaitForOpen(dc, timeoutMs) {
1861
+ function dkRtcState(pc) {
1862
+ if (!pc) return "";
1863
+ return "ice=" + pc.iceConnectionState + " conn=" + pc.connectionState + " gather=" + pc.iceGatheringState;
1864
+ }
1865
+ function dkWaitForOpen(dc, timeoutMs, describeState) {
1849
1866
  if (dc.readyState === "open") return Promise.resolve();
1850
1867
  return new Promise((resolve, reject) => {
1851
1868
  const timer = setTimeout(() => {
1852
1869
  cleanup();
1853
- reject(new Error("WebRTC data channel open timed out"));
1870
+ const s = describeState ? describeState() : "";
1871
+ reject(new Error("WebRTC data channel open timed out" + (s ? " (" + s + ")" : "")));
1854
1872
  }, timeoutMs);
1855
1873
  const cleanup = () => {
1856
1874
  clearTimeout(timer);
@@ -1864,11 +1882,13 @@ function dkWaitForOpen(dc, timeoutMs) {
1864
1882
  };
1865
1883
  const onError = () => {
1866
1884
  cleanup();
1867
- reject(new Error("WebRTC data channel failed"));
1885
+ const s = describeState ? describeState() : "";
1886
+ reject(new Error("WebRTC data channel failed" + (s ? " (" + s + ")" : "")));
1868
1887
  };
1869
1888
  const onClose = () => {
1870
1889
  cleanup();
1871
- reject(new Error("WebRTC data channel closed"));
1890
+ const s = describeState ? describeState() : "";
1891
+ reject(new Error("WebRTC data channel closed before open" + (s ? " (" + s + ")" : "")));
1872
1892
  };
1873
1893
  dc.addEventListener("open", onOpen);
1874
1894
  dc.addEventListener("error", onError);
@@ -1960,6 +1980,8 @@ function useRtcFileController(selfId, onReceivedText) {
1960
1980
  if (env.type === "offer") {
1961
1981
  if (sess) cleanup(fileId);
1962
1982
  const pc = new RTCPeerConnection({ iceServers: CALL_ICE_SERVERS });
1983
+ pc.oniceconnectionstatechange = () => console.warn("[file-rtc] receiver ice=" + pc.iceConnectionState + " conn=" + pc.connectionState);
1984
+ pc.onconnectionstatechange = () => console.warn("[file-rtc] receiver conn=" + pc.connectionState + " ice=" + pc.iceConnectionState);
1963
1985
  pc.onicecandidate = (ev) => {
1964
1986
  if (ev.candidate) sendSignal(peerId, fileId, "candidate", { candidate: ev.candidate.toJSON() }).catch(() => {
1965
1987
  });
@@ -2035,6 +2057,8 @@ function useRtcFileController(selfId, onReceivedText) {
2035
2057
  };
2036
2058
  const sendSignal = (type, extra) => bus.send(peerId, dkFileRtcEnvelope(fileId, type, extra));
2037
2059
  try {
2060
+ pc.oniceconnectionstatechange = () => console.warn("[file-rtc] sender ice=" + pc.iceConnectionState + " conn=" + pc.connectionState);
2061
+ pc.onconnectionstatechange = () => console.warn("[file-rtc] sender conn=" + pc.connectionState + " ice=" + pc.iceConnectionState);
2038
2062
  pc.onicecandidate = (ev) => {
2039
2063
  if (ev.candidate) sendSignal("candidate", { candidate: ev.candidate.toJSON() }).catch(() => {
2040
2064
  });
@@ -2045,7 +2069,7 @@ function useRtcFileController(selfId, onReceivedText) {
2045
2069
  sdp: offer.sdp,
2046
2070
  meta: { name: file.name, size: file.size, mime: file.type || "application/octet-stream" }
2047
2071
  });
2048
- await dkWaitForOpen(dc, DK_FILE_RTC_OPEN_TIMEOUT_MS);
2072
+ await dkWaitForOpen(dc, DK_FILE_RTC_OPEN_TIMEOUT_MS, () => dkRtcState(pc));
2049
2073
  dc.send(JSON.stringify({ type: "meta", name: file.name, size: file.size, mime: file.type || "application/octet-stream" }));
2050
2074
  let sent = 0;
2051
2075
  const highWater = 4 * 1024 * 1024;
@@ -2091,7 +2115,8 @@ function RtcFileInbox({ T, peers, ctl }) {
2091
2115
  Object.assign(window, { useRtcFileController, RtcFileInbox });
2092
2116
  const CALL_ICE_SERVERS = [
2093
2117
  { urls: "stun:stun.l.google.com:19302" },
2094
- { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" }
2118
+ { urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
2119
+ { urls: "turn:tokyo.fi.chat:3478?transport=tcp", username: "allcom", credential: "allcompass" }
2095
2120
  ];
2096
2121
  function useCallController(selfId) {
2097
2122
  const [incoming, setIncoming] = React.useState(null);
@@ -2415,6 +2440,7 @@ const STR = {
2415
2440
  call: "Voice call",
2416
2441
  alias: "Set alias",
2417
2442
  sendFile: "Send file",
2443
+ sendWebrtcFile: "Send file via WebRTC",
2418
2444
  block: "Block peer",
2419
2445
  remove: "Remove friend",
2420
2446
  receivedFile: "Received file",
@@ -2506,6 +2532,7 @@ const STR = {
2506
2532
  call: "\u8BED\u97F3\u901A\u8BDD",
2507
2533
  alias: "\u8BBE\u7F6E\u5907\u6CE8",
2508
2534
  sendFile: "\u53D1\u9001\u6587\u4EF6",
2535
+ sendWebrtcFile: "\u901A\u8FC7 WebRTC \u53D1\u9001\u6587\u4EF6",
2509
2536
  block: "\u62C9\u9ED1",
2510
2537
  remove: "\u5220\u9664\u597D\u53CB",
2511
2538
  receivedFile: "\u6536\u5230\u6587\u4EF6",
@@ -2661,6 +2688,19 @@ function DkApp() {
2661
2688
  return r;
2662
2689
  });
2663
2690
  };
2691
+ const onSendRtcFile = (file) => {
2692
+ if (!activeId || !file) return Promise.resolve({ ok: false, error: "no active peer or file" });
2693
+ return rtcFileCtl.sendFile(activeId, file).then((r) => {
2694
+ if (r && r.ok && r.via === "webrtc") {
2695
+ return dkApi.send(activeId, (t.lang === "zh" ? "\u5DF2\u901A\u8FC7 WebRTC \u53D1\u9001\u6587\u4EF6: " : "sent file via WebRTC: ") + file.name).then(() => ({ ok: true, via: "webrtc" }));
2696
+ }
2697
+ return { ok: false, error: r && r.error || "WebRTC file send failed" };
2698
+ }).then((r) => {
2699
+ data.loadThread(activeId);
2700
+ data.refresh();
2701
+ return r;
2702
+ });
2703
+ };
2664
2704
  const onAlias = (peer) => {
2665
2705
  const a = window.prompt("Set alias for this peer (empty to clear):", peer.alias || "");
2666
2706
  if (a !== null) dkApi.alias(peer.id, a).then(data.refresh);
@@ -2678,7 +2718,7 @@ function DkApp() {
2678
2718
  { id: "network", icon: "network", label: T.network },
2679
2719
  { id: "profile", icon: "userRound", label: T.profile }
2680
2720
  ];
2681
- return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId) }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit })), /* @__PURE__ */ React.createElement(TweaksPanel, null, /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5916\u89C2" : "Appearance" }), /* @__PURE__ */ React.createElement(
2721
+ return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, minHeight: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "beagle"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread: () => activeId && data.loadThread(activeId) }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me, onEdit })), /* @__PURE__ */ React.createElement(TweaksPanel, null, /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5916\u89C2" : "Appearance" }), /* @__PURE__ */ React.createElement(
2682
2722
  TweakRadio,
2683
2723
  {
2684
2724
  label: t.lang === "zh" ? "\u4E3B\u9898" : "Theme",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.220",
3
+ "version": "0.1.222",
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",