@decentnetwork/lan 0.1.226 → 0.1.227

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.
@@ -1181,8 +1181,18 @@ const mediaBtnStyle = {
1181
1181
  justifyContent: "center",
1182
1182
  lineHeight: 0
1183
1183
  };
1184
+ function dkRtcFileFromText(peerId, text) {
1185
+ const m = /^received file via WebRTC: (.+) \(([^)]+)\)$/.exec(String(text || ""));
1186
+ if (!m || !window.__dkRtcFiles) return null;
1187
+ const name = m[1];
1188
+ for (const item of window.__dkRtcFiles.values()) {
1189
+ if (item && item.peerId === peerId && item.name === name) return item;
1190
+ }
1191
+ return null;
1192
+ }
1184
1193
  function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, selMode, selected, onToggleSel, busy }) {
1185
1194
  const mine = m.from === "me";
1195
+ const rtcFile = !mine && !m.file ? dkRtcFileFromText(peer.userId || peer.id, m.text) : null;
1186
1196
  return /* @__PURE__ */ React.createElement(
1187
1197
  "div",
1188
1198
  {
@@ -1212,7 +1222,52 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, selMode, sele
1212
1222
  justifyContent: "center"
1213
1223
  } }, selected && /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 3, color: "#fff" })),
1214
1224
  !mine && /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 24, radius: 6, dot: false }),
1215
- /* @__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 ? (
1225
+ /* @__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" } }, rtcFile ? /* @__PURE__ */ React.createElement("div", { style: {
1226
+ display: "flex",
1227
+ flexDirection: "column",
1228
+ gap: 6,
1229
+ maxWidth: rtcFile.media === "image" || rtcFile.media === "video" ? "100%" : 300,
1230
+ width: rtcFile.media === "image" || rtcFile.media === "video" ? "100%" : void 0,
1231
+ background: "var(--bub-them)",
1232
+ border: "1px solid var(--line)",
1233
+ borderRadius: 12,
1234
+ padding: 6
1235
+ } }, (rtcFile.media === "image" || rtcFile.media === "video") && /* @__PURE__ */ React.createElement("div", { "data-media": true, style: { position: "relative", lineHeight: 0 } }, rtcFile.media === "image" ? /* @__PURE__ */ React.createElement(
1236
+ "img",
1237
+ {
1238
+ src: rtcFile.url,
1239
+ alt: rtcFile.name,
1240
+ style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain" }
1241
+ }
1242
+ ) : /* @__PURE__ */ React.createElement(
1243
+ "video",
1244
+ {
1245
+ src: rtcFile.url,
1246
+ controls: true,
1247
+ preload: "metadata",
1248
+ style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain", background: "#000" }
1249
+ }
1250
+ ), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", top: 8, right: 8, display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(
1251
+ "button",
1252
+ {
1253
+ onClick: () => onTheater({ kind: rtcFile.media, url: rtcFile.url, name: rtcFile.name }),
1254
+ title: "theater / \u7F51\u9875\u5168\u5C4F",
1255
+ style: mediaBtnStyle
1256
+ },
1257
+ /* @__PURE__ */ React.createElement(Icon, { name: "theater", size: 16, stroke: 2, color: "#fff" })
1258
+ ), /* @__PURE__ */ React.createElement(
1259
+ "button",
1260
+ {
1261
+ onClick: (e) => {
1262
+ const w = e.currentTarget.closest("[data-media]");
1263
+ const el = w && w.querySelector("video,img");
1264
+ if (el && el.requestFullscreen) el.requestFullscreen();
1265
+ },
1266
+ title: "fullscreen / \u5168\u5C4F",
1267
+ style: mediaBtnStyle
1268
+ },
1269
+ /* @__PURE__ */ React.createElement(Icon, { name: "maximize", size: 16, stroke: 2, color: "#fff" })
1270
+ ))), rtcFile.media === "audio" && /* @__PURE__ */ React.createElement("audio", { src: rtcFile.url, 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" } }, rtcFile.name), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)", flexShrink: 0 } }, dkFileSize(rtcFile.size)), /* @__PURE__ */ React.createElement("a", { href: rtcFile.url, download: rtcFile.name, title: "download", style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React.createElement(Icon, { name: "download", size: 15, stroke: 2, color: "var(--accent)" })))) : m.file ? (
1216
1271
  // Inline preview/player. Received media: as soon as it's saved.
1217
1272
  // Sent media: once it's fully delivered ('sent') — the daemon keeps a
1218
1273
  // local copy so it plays on the sender's side too.
@@ -1947,6 +2002,12 @@ function dkFileDownload(blob, name) {
1947
2002
  a.textContent = name || "agentnet-file";
1948
2003
  return { url, name: a.download };
1949
2004
  }
2005
+ function dkRememberRtcFile(peerId, fileId, name, size, url) {
2006
+ if (!window.__dkRtcFiles) window.__dkRtcFiles = /* @__PURE__ */ new Map();
2007
+ const item = { id: fileId, peerId, name, size, url, media: dkFileMediaKind(name) };
2008
+ window.__dkRtcFiles.set(peerId + "|" + name + "|" + size, item);
2009
+ return item;
2010
+ }
1950
2011
  function useRtcFileController(selfId, onReceivedText) {
1951
2012
  const [incoming, setIncoming] = React.useState([]);
1952
2013
  const peersRef = React.useRef(/* @__PURE__ */ new Map());
@@ -1992,6 +2053,7 @@ function useRtcFileController(selfId, onReceivedText) {
1992
2053
  const meta = sess.meta || {};
1993
2054
  const blob = new Blob(sess.chunks, { type: meta.mime || "application/octet-stream" });
1994
2055
  const dl = dkFileDownload(blob, meta.name || "agentnet-file");
2056
+ dkRememberRtcFile(peerId, fileId, dl.name, blob.size, dl.url);
1995
2057
  setIncoming((arr) => [{ id: fileId, peerId, name: dl.name, size: blob.size, url: dl.url }].concat(arr).slice(0, 8));
1996
2058
  if (onReceivedTextRef.current) onReceivedTextRef.current(peerId, "received file via WebRTC: " + dl.name + " (" + dkFileSize(blob.size) + ")");
1997
2059
  cleanup(fileId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.226",
3
+ "version": "0.1.227",
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",