@decentnetwork/lan 0.1.223 → 0.1.224

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.
@@ -1547,9 +1547,11 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
1547
1547
  if (!files || !files.length || !sender) return;
1548
1548
  const file = files[0];
1549
1549
  followScrollRef.current = true;
1550
- setSending({ name: file.name, via: mode });
1551
- Promise.resolve(sender(file)).then((r) => {
1550
+ setSending({ name: file.name, via: mode, pct: 0 });
1551
+ const onProgress = (p) => setSending((s) => s && s.name === file.name ? { ...s, pct: p && p.pct } : s);
1552
+ Promise.resolve(sender(file, onProgress)).then((r) => {
1552
1553
  if (r && r.ok === false) window.alert((lang === "zh" ? "\u53D1\u9001\u5931\u8D25: " : "Send failed: ") + (r.error || ""));
1554
+ else showFlash("ok", mode === "webrtc" ? lang === "zh" ? "WebRTC \u6587\u4EF6\u53D1\u9001\u5B8C\u6210" : "WebRTC file sent" : lang === "zh" ? "\u6587\u4EF6\u5DF2\u63D0\u4EA4\u53D1\u9001" : "File send started");
1553
1555
  }).finally(() => {
1554
1556
  setSending(null);
1555
1557
  requestAnimationFrame(scrollToBottom);
@@ -1633,7 +1635,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
1633
1635
  stroke: 2.2,
1634
1636
  color: flash.kind === "err" ? "var(--danger)" : "var(--accent)"
1635
1637
  }
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(
1638
+ ), 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 + (sending.pct != null ? " \xB7 " + Math.min(100, Math.max(0, sending.pct)).toFixed(1) + "%" : "")), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(
1637
1639
  "input",
1638
1640
  {
1639
1641
  ref: fileRef,
@@ -1897,15 +1899,43 @@ function dkWaitForOpen(dc, timeoutMs, describeState) {
1897
1899
  }
1898
1900
  function dkWaitBufferedLow(dc, highWater) {
1899
1901
  if (dc.bufferedAmount < highWater) return Promise.resolve();
1900
- return new Promise((resolve) => {
1902
+ return new Promise((resolve, reject) => {
1901
1903
  const prev = dc.bufferedAmountLowThreshold;
1902
1904
  dc.bufferedAmountLowThreshold = Math.floor(highWater / 2);
1905
+ let poll = null;
1906
+ let timer = null;
1903
1907
  const done = () => {
1908
+ cleanup();
1909
+ resolve();
1910
+ };
1911
+ const cleanup = () => {
1912
+ if (poll) clearInterval(poll);
1913
+ if (timer) clearTimeout(timer);
1904
1914
  dc.removeEventListener("bufferedamountlow", done);
1915
+ dc.removeEventListener("close", onClose);
1916
+ dc.removeEventListener("error", onError);
1905
1917
  dc.bufferedAmountLowThreshold = prev;
1906
- resolve();
1918
+ };
1919
+ const check = () => {
1920
+ if (dc.readyState !== "open") return onClose();
1921
+ if (dc.bufferedAmount < highWater) done();
1922
+ };
1923
+ const onClose = () => {
1924
+ cleanup();
1925
+ reject(new Error("WebRTC data channel closed while draining"));
1926
+ };
1927
+ const onError = () => {
1928
+ cleanup();
1929
+ reject(new Error("WebRTC data channel failed while draining"));
1907
1930
  };
1908
1931
  dc.addEventListener("bufferedamountlow", done);
1932
+ dc.addEventListener("close", onClose);
1933
+ dc.addEventListener("error", onError);
1934
+ poll = setInterval(check, 250);
1935
+ timer = setTimeout(() => {
1936
+ cleanup();
1937
+ reject(new Error("WebRTC data channel drain timed out buffered=" + dc.bufferedAmount));
1938
+ }, 3e4);
1909
1939
  });
1910
1940
  }
1911
1941
  function dkFileDownload(blob, name) {
@@ -2113,10 +2143,12 @@ function useRtcFileController(selfId, onReceivedText) {
2113
2143
  function RtcFileInbox({ T, peers, ctl }) {
2114
2144
  const items = ctl && ctl.incoming || [];
2115
2145
  if (!items.length) return null;
2116
- return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", right: 18, bottom: 18, zIndex: 90, width: 360, display: "flex", flexDirection: "column", gap: 10 } }, items.map((it) => {
2146
+ const closeLabel = T && T.receivedClose || "Close";
2147
+ const downloadLabel = T && T.downloadFile || "Download";
2148
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", right: 72, bottom: 18, zIndex: 90, width: 360, maxWidth: "calc(100vw - 96px)", display: "flex", flexDirection: "column", gap: 10 } }, items.map((it) => {
2117
2149
  const peer = (peers || []).find((p) => p.id === it.peerId || p.userId === it.peerId) || {};
2118
2150
  const who = peer.alias || peer.name || shortKey(it.peerId, 8, 5);
2119
- return /* @__PURE__ */ React.createElement("div", { key: it.id, style: { border: "1px solid var(--line)", background: "var(--panel)", borderRadius: 12, padding: 12, boxShadow: "0 14px 40px rgba(0,0,0,0.35)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", marginBottom: 6 } }, T && T.receivedFile || "Received file", " \xB7 ", who), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 13, fontWeight: 700, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, it.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", marginTop: 3 } }, dkFileSize(it.size)), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 10 } }, /* @__PURE__ */ React.createElement("button", { onClick: () => ctl.dismissIncoming(it.id), style: { height: 30, borderRadius: 8, border: "1px solid var(--line)", background: "transparent", color: "var(--dim)", cursor: "pointer" } }, T && T.cancel || "Close"), /* @__PURE__ */ React.createElement("a", { href: it.url, download: it.name, onClick: () => setTimeout(() => ctl.dismissIncoming(it.id), 500), style: { height: 30, borderRadius: 8, padding: "6px 10px", background: "var(--accent)", color: "#fff", textDecoration: "none", fontFamily: "var(--mono)", fontSize: 12 } }, T && T.open || "Download")));
2151
+ return /* @__PURE__ */ React.createElement("div", { key: it.id, style: { border: "1px solid var(--line)", background: "var(--panel)", borderRadius: 12, padding: 12, boxShadow: "0 14px 40px rgba(0,0,0,0.35)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", marginBottom: 6 } }, T && T.receivedFile || "Received file", " \xB7 ", who), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 13, fontWeight: 700, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, it.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", marginTop: 3 } }, dkFileSize(it.size)), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 10, paddingRight: 2 } }, /* @__PURE__ */ React.createElement("button", { onClick: () => ctl.dismissIncoming(it.id), style: { height: 30, minWidth: 58, borderRadius: 8, border: "1px solid var(--line)", background: "transparent", color: "var(--dim)", cursor: "pointer" } }, closeLabel), /* @__PURE__ */ React.createElement("a", { href: it.url, download: it.name, onClick: () => setTimeout(() => ctl.dismissIncoming(it.id), 500), style: { height: 30, minWidth: 76, textAlign: "center", borderRadius: 8, padding: "6px 10px", background: "var(--accent)", color: "#fff", textDecoration: "none", fontFamily: "var(--mono)", fontSize: 12 } }, downloadLabel)));
2120
2152
  }));
2121
2153
  }
2122
2154
  Object.assign(window, { useRtcFileController, RtcFileInbox });
@@ -2451,6 +2483,8 @@ const STR = {
2451
2483
  block: "Block peer",
2452
2484
  remove: "Remove friend",
2453
2485
  receivedFile: "Received file",
2486
+ receivedClose: "Close",
2487
+ downloadFile: "Download",
2454
2488
  attach: "Attach file",
2455
2489
  message: "Message",
2456
2490
  send: "Send",
@@ -2543,6 +2577,8 @@ const STR = {
2543
2577
  block: "\u62C9\u9ED1",
2544
2578
  remove: "\u5220\u9664\u597D\u53CB",
2545
2579
  receivedFile: "\u6536\u5230\u6587\u4EF6",
2580
+ receivedClose: "\u5173\u95ED",
2581
+ downloadFile: "\u4E0B\u8F7D",
2546
2582
  attach: "\u9644\u4EF6",
2547
2583
  message: "\u6D88\u606F",
2548
2584
  send: "\u53D1\u9001",
@@ -2682,9 +2718,9 @@ function DkApp() {
2682
2718
  if (!activeId || !text || !text.trim()) return;
2683
2719
  dkApi.send(activeId, text.trim()).then(() => data.loadThread(activeId)).then(data.refresh);
2684
2720
  };
2685
- const onSendFile = (file) => {
2721
+ const onSendFile = (file, onProgress) => {
2686
2722
  if (!activeId || !file) return Promise.resolve({ ok: false });
2687
- return rtcFileCtl.sendFile(activeId, file).then((r) => {
2723
+ return rtcFileCtl.sendFile(activeId, file, onProgress).then((r) => {
2688
2724
  if (r && r.ok && r.via === "webrtc") {
2689
2725
  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" }));
2690
2726
  }
@@ -2695,9 +2731,9 @@ function DkApp() {
2695
2731
  return r;
2696
2732
  });
2697
2733
  };
2698
- const onSendRtcFile = (file) => {
2734
+ const onSendRtcFile = (file, onProgress) => {
2699
2735
  if (!activeId || !file) return Promise.resolve({ ok: false, error: "no active peer or file" });
2700
- return rtcFileCtl.sendFile(activeId, file).then((r) => {
2736
+ return rtcFileCtl.sendFile(activeId, file, onProgress).then((r) => {
2701
2737
  if (r && r.ok && r.via === "webrtc") {
2702
2738
  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" }));
2703
2739
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.223",
3
+ "version": "0.1.224",
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",