@decentnetwork/lan 0.1.142 → 0.1.143

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.
@@ -53,6 +53,7 @@ const ICON_PATHS = {
53
53
  arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
54
54
  shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
55
55
  maximize: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>',
56
+ theater: '<rect x="2.5" y="6.5" width="19" height="11" rx="2"/>',
56
57
  speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
57
58
  micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
58
59
  };
@@ -1031,6 +1032,18 @@ const inputStyle = {
1031
1032
  outline: "none",
1032
1033
  minWidth: 0
1033
1034
  };
1035
+ const mediaBtnStyle = {
1036
+ width: 28,
1037
+ height: 28,
1038
+ borderRadius: 8,
1039
+ border: "none",
1040
+ background: "rgba(0,0,0,0.5)",
1041
+ cursor: "pointer",
1042
+ display: "flex",
1043
+ alignItems: "center",
1044
+ justifyContent: "center",
1045
+ lineHeight: 0
1046
+ };
1034
1047
  function Msg({ m, peer, T, onTheater }) {
1035
1048
  const mine = m.from === "me";
1036
1049
  return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: mine ? "flex-end" : "flex-start", alignItems: "flex-end", gap: 8, margin: "3px 0" } }, !mine && /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 24, radius: 6, dot: false }), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: m.file && (m.file.media === "image" || m.file.media === "video") ? "min(680px, 88%)" : "64%", display: "flex", flexDirection: "column", alignItems: mine ? "flex-end" : "flex-start" } }, m.file ? (
@@ -1049,7 +1062,7 @@ function Msg({ m, peer, T, onTheater }) {
1049
1062
  border: "1px solid var(--line)",
1050
1063
  borderRadius: 12,
1051
1064
  padding: 6
1052
- } }, (m.file.media === "image" || m.file.media === "video") && /* @__PURE__ */ React.createElement("div", { style: { position: "relative", lineHeight: 0 } }, m.file.media === "image" ? /* @__PURE__ */ React.createElement(
1065
+ } }, (m.file.media === "image" || m.file.media === "video") && /* @__PURE__ */ React.createElement("div", { "data-media": true, style: { position: "relative", lineHeight: 0 } }, m.file.media === "image" ? /* @__PURE__ */ React.createElement(
1053
1066
  "img",
1054
1067
  {
1055
1068
  src: dkFileUrl(m.file.name),
@@ -1064,21 +1077,27 @@ function Msg({ m, peer, T, onTheater }) {
1064
1077
  preload: "metadata",
1065
1078
  style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain", background: "#000" }
1066
1079
  }
1067
- ), /* @__PURE__ */ React.createElement("button", { onClick: () => onTheater({ kind: m.file.media, url: dkFileUrl(m.file.name), name: m.file.name }), title: "theater / \u7F51\u9875\u5168\u5C4F", style: {
1068
- position: "absolute",
1069
- top: 8,
1070
- right: 8,
1071
- width: 30,
1072
- height: 30,
1073
- borderRadius: 8,
1074
- border: "none",
1075
- background: "rgba(0,0,0,0.5)",
1076
- cursor: "pointer",
1077
- display: "flex",
1078
- alignItems: "center",
1079
- justifyContent: "center",
1080
- lineHeight: 0
1081
- } }, /* @__PURE__ */ React.createElement(Icon, { name: "maximize", size: 17, stroke: 2, color: "#fff" }))), m.file.media === "audio" && /* @__PURE__ */ React.createElement("audio", { src: dkFileUrl(m.file.name), controls: true, style: { width: "100%" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px" } }, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.file.name), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", flexShrink: 0 } }, m.file.size), /* @__PURE__ */ React.createElement("a", { href: dkFileDownloadUrl(m.file.name), download: m.file.name, title: "download", style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "download", size: 15, stroke: 2, color: "var(--accent)" }))))
1080
+ ), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", top: 8, right: 8, display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(
1081
+ "button",
1082
+ {
1083
+ onClick: () => onTheater({ kind: m.file.media, url: dkFileUrl(m.file.name), name: m.file.name }),
1084
+ title: "theater / \u7F51\u9875\u5168\u5C4F",
1085
+ style: mediaBtnStyle
1086
+ },
1087
+ /* @__PURE__ */ React.createElement(Icon, { name: "theater", size: 16, stroke: 2, color: "#fff" })
1088
+ ), /* @__PURE__ */ React.createElement(
1089
+ "button",
1090
+ {
1091
+ onClick: (e) => {
1092
+ const w = e.currentTarget.closest("[data-media]");
1093
+ const el = w && w.querySelector("video,img");
1094
+ if (el && el.requestFullscreen) el.requestFullscreen();
1095
+ },
1096
+ title: "fullscreen / \u5168\u5C4F",
1097
+ style: mediaBtnStyle
1098
+ },
1099
+ /* @__PURE__ */ React.createElement(Icon, { name: "maximize", size: 16, stroke: 2, color: "#fff" })
1100
+ ))), m.file.media === "audio" && /* @__PURE__ */ React.createElement("audio", { src: dkFileUrl(m.file.name), controls: true, style: { width: "100%" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px" } }, /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.file.name), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", flexShrink: 0 } }, m.file.size), /* @__PURE__ */ React.createElement("a", { href: dkFileDownloadUrl(m.file.name), download: m.file.name, title: "download", style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "download", size: 15, stroke: 2, color: "var(--accent)" }))))
1082
1101
  ) : (() => {
1083
1102
  const cardStyle = {
1084
1103
  display: "flex",
package/dist/ui/server.js CHANGED
@@ -176,7 +176,10 @@ export function startFriendUi(opts) {
176
176
  const desktopIndex = join(DESKTOP_DIR, "index.html");
177
177
  if (req.method === "GET" && (url === "/" || url === "/index.html")) {
178
178
  if (existsSync(desktopIndex)) {
179
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
179
+ // no-store: the UI bundle is rebuilt in place on every UI change, so the
180
+ // browser must never serve a stale cached index/app.js (that's why UI
181
+ // updates appeared to "not take" until a hard reload).
182
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
180
183
  res.end(readFileSync(desktopIndex));
181
184
  return;
182
185
  }
@@ -195,7 +198,12 @@ export function startFriendUi(opts) {
195
198
  const file = join(DESKTOP_DIR, rel);
196
199
  // Guard against path traversal: resolved file must stay under DESKTOP_DIR.
197
200
  if (existsSync(file) && file.startsWith(DESKTOP_DIR)) {
198
- res.writeHead(200, { "content-type": "application/javascript; charset=utf-8" });
201
+ // app.js changes on every UI rebuild → never cache it. Vendored React
202
+ // UMD is stable, so let it cache normally.
203
+ const headers = { "content-type": "application/javascript; charset=utf-8" };
204
+ if (rel === "app.js")
205
+ headers["cache-control"] = "no-store";
206
+ res.writeHead(200, headers);
199
207
  res.end(readFileSync(file));
200
208
  return;
201
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.142",
3
+ "version": "0.1.143",
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",
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "@decentnetwork/dora": "^0.1.11",
82
- "@decentnetwork/peer": "^0.1.75",
82
+ "@decentnetwork/peer": "^0.1.76",
83
83
  "ink": "^5.2.1",
84
84
  "js-yaml": "^4.1.0",
85
85
  "node-forge": "^1.4.0",