@decentnetwork/lan 0.1.141 → 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.
- package/dist/ui/desktop/app.js +103 -10
- package/dist/ui/server.js +10 -2
- package/package.json +2 -2
package/dist/ui/desktop/app.js
CHANGED
|
@@ -52,6 +52,8 @@ const ICON_PATHS = {
|
|
|
52
52
|
trash: '<path d="M3 6h18"/><path d="M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6"/><path d="M18.5 6l-1 13.5a2 2 0 0 1-2 1.5h-7a2 2 0 0 1-2-1.5L5.5 6"/><path d="M10 10.5v6M14 10.5v6"/>',
|
|
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
|
+
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"/>',
|
|
55
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"/>',
|
|
56
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"/>'
|
|
57
59
|
};
|
|
@@ -1030,9 +1032,21 @@ const inputStyle = {
|
|
|
1030
1032
|
outline: "none",
|
|
1031
1033
|
minWidth: 0
|
|
1032
1034
|
};
|
|
1033
|
-
|
|
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
|
+
};
|
|
1047
|
+
function Msg({ m, peer, T, onTheater }) {
|
|
1034
1048
|
const mine = m.from === "me";
|
|
1035
|
-
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: "64%", display: "flex", flexDirection: "column", alignItems: mine ? "flex-end" : "flex-start" } }, m.file ? (
|
|
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 ? (
|
|
1036
1050
|
// Inline preview/player. Received media: as soon as it's saved.
|
|
1037
1051
|
// Sent media: once it's fully delivered ('sent') — the daemon keeps a
|
|
1038
1052
|
// local copy so it plays on the sender's side too.
|
|
@@ -1042,27 +1056,48 @@ function Msg({ m, peer, T }) {
|
|
|
1042
1056
|
display: "flex",
|
|
1043
1057
|
flexDirection: "column",
|
|
1044
1058
|
gap: 6,
|
|
1045
|
-
maxWidth: 300,
|
|
1059
|
+
maxWidth: m.file.media === "image" || m.file.media === "video" ? "100%" : 300,
|
|
1060
|
+
width: m.file.media === "image" || m.file.media === "video" ? "100%" : void 0,
|
|
1046
1061
|
background: "var(--bub-them)",
|
|
1047
1062
|
border: "1px solid var(--line)",
|
|
1048
1063
|
borderRadius: 12,
|
|
1049
1064
|
padding: 6
|
|
1050
|
-
} }, 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(
|
|
1051
1066
|
"img",
|
|
1052
1067
|
{
|
|
1053
1068
|
src: dkFileUrl(m.file.name),
|
|
1054
1069
|
alt: m.file.name,
|
|
1055
|
-
style: { display: "block",
|
|
1070
|
+
style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain" }
|
|
1056
1071
|
}
|
|
1057
|
-
)
|
|
1072
|
+
) : /* @__PURE__ */ React.createElement(
|
|
1058
1073
|
"video",
|
|
1059
1074
|
{
|
|
1060
1075
|
src: dkFileUrl(m.file.name),
|
|
1061
1076
|
controls: true,
|
|
1062
1077
|
preload: "metadata",
|
|
1063
|
-
style: {
|
|
1078
|
+
style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain", background: "#000" }
|
|
1064
1079
|
}
|
|
1065
|
-
),
|
|
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)" }))))
|
|
1066
1101
|
) : (() => {
|
|
1067
1102
|
const cardStyle = {
|
|
1068
1103
|
display: "flex",
|
|
@@ -1129,9 +1164,21 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1129
1164
|
const [draft, setDraft] = React.useState("");
|
|
1130
1165
|
const [dragOver, setDragOver] = React.useState(false);
|
|
1131
1166
|
const [sending, setSending] = React.useState(null);
|
|
1167
|
+
const [theater, setTheater] = React.useState(null);
|
|
1132
1168
|
React.useEffect(() => {
|
|
1133
1169
|
setDraft("");
|
|
1134
1170
|
}, [peer.id]);
|
|
1171
|
+
React.useEffect(() => {
|
|
1172
|
+
setTheater(null);
|
|
1173
|
+
}, [peer.id]);
|
|
1174
|
+
React.useEffect(() => {
|
|
1175
|
+
if (!theater) return;
|
|
1176
|
+
const onKey = (e) => {
|
|
1177
|
+
if (e.key === "Escape") setTheater(null);
|
|
1178
|
+
};
|
|
1179
|
+
window.addEventListener("keydown", onKey);
|
|
1180
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1181
|
+
}, [theater]);
|
|
1135
1182
|
const sendDraft = () => {
|
|
1136
1183
|
if (draft.trim()) {
|
|
1137
1184
|
onSend(draft);
|
|
@@ -1172,7 +1219,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1172
1219
|
setMenu(false);
|
|
1173
1220
|
onRemove(peer);
|
|
1174
1221
|
} }))))),
|
|
1175
|
-
/* @__PURE__ */ React.createElement("div", { ref: scrollRef, style: { flex: 1, overflow: "auto", padding: "18px 22px" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto" } }, thread.map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(Msg, { key: i, m, peer, T })))),
|
|
1222
|
+
/* @__PURE__ */ React.createElement("div", { ref: scrollRef, style: { flex: 1, overflow: "auto", padding: "18px 22px" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto" } }, thread.map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(Msg, { key: i, m, peer, T, onTheater: setTheater })))),
|
|
1176
1223
|
/* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, borderTop: "1px solid var(--line)", padding: "12px 16px", background: "var(--panel)" } }, 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(
|
|
1177
1224
|
"input",
|
|
1178
1225
|
{
|
|
@@ -1198,7 +1245,53 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1198
1245
|
placeholder: `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
|
|
1199
1246
|
style: { flex: 1, height: 38, borderRadius: 9, border: "1px solid var(--line)", background: "var(--panel-2)", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13.5, padding: "0 14px", outline: "none", minWidth: 0 }
|
|
1200
1247
|
}
|
|
1201
|
-
), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft })))
|
|
1248
|
+
), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft }))),
|
|
1249
|
+
theater && /* @__PURE__ */ React.createElement("div", { onClick: () => setTheater(null), style: {
|
|
1250
|
+
position: "absolute",
|
|
1251
|
+
inset: 0,
|
|
1252
|
+
zIndex: 60,
|
|
1253
|
+
background: "rgba(0,0,0,0.86)",
|
|
1254
|
+
display: "flex",
|
|
1255
|
+
flexDirection: "column"
|
|
1256
|
+
} }, /* @__PURE__ */ React.createElement("div", { onClick: (e) => e.stopPropagation(), style: { flexShrink: 0, height: 46, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "0 14px" } }, /* @__PURE__ */ React.createElement("span", { style: { minWidth: 0, fontFamily: "var(--mono)", fontSize: 12, color: "rgba(255,255,255,0.72)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, theater.name), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, flexShrink: 0 } }, /* @__PURE__ */ React.createElement(
|
|
1257
|
+
"button",
|
|
1258
|
+
{
|
|
1259
|
+
title: lang === "zh" ? "\u5168\u5C4F" : "fullscreen",
|
|
1260
|
+
onClick: () => {
|
|
1261
|
+
const el = document.getElementById("dk-theater-media");
|
|
1262
|
+
if (el && el.requestFullscreen) el.requestFullscreen();
|
|
1263
|
+
},
|
|
1264
|
+
style: { width: 34, height: 34, borderRadius: 8, border: "none", background: "rgba(255,255,255,0.14)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }
|
|
1265
|
+
},
|
|
1266
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "maximize", size: 18, stroke: 2, color: "#fff" })
|
|
1267
|
+
), /* @__PURE__ */ React.createElement(
|
|
1268
|
+
"button",
|
|
1269
|
+
{
|
|
1270
|
+
title: (lang === "zh" ? "\u5173\u95ED" : "close") + " \xB7 Esc",
|
|
1271
|
+
onClick: () => setTheater(null),
|
|
1272
|
+
style: { width: 34, height: 34, borderRadius: 8, border: "none", background: "rgba(255,255,255,0.14)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }
|
|
1273
|
+
},
|
|
1274
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "x", size: 20, stroke: 2.2, color: "#fff" })
|
|
1275
|
+
))), /* @__PURE__ */ React.createElement("div", { onClick: () => setTheater(null), style: { flex: 1, minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center", padding: "0 16px 16px" } }, theater.kind === "video" ? /* @__PURE__ */ React.createElement(
|
|
1276
|
+
"video",
|
|
1277
|
+
{
|
|
1278
|
+
id: "dk-theater-media",
|
|
1279
|
+
src: theater.url,
|
|
1280
|
+
controls: true,
|
|
1281
|
+
autoPlay: true,
|
|
1282
|
+
onClick: (e) => e.stopPropagation(),
|
|
1283
|
+
style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6, background: "#000" }
|
|
1284
|
+
}
|
|
1285
|
+
) : /* @__PURE__ */ React.createElement(
|
|
1286
|
+
"img",
|
|
1287
|
+
{
|
|
1288
|
+
id: "dk-theater-media",
|
|
1289
|
+
src: theater.url,
|
|
1290
|
+
alt: theater.name,
|
|
1291
|
+
onClick: (e) => e.stopPropagation(),
|
|
1292
|
+
style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6 }
|
|
1293
|
+
}
|
|
1294
|
+
)))
|
|
1202
1295
|
);
|
|
1203
1296
|
}
|
|
1204
1297
|
function MenuItem({ icon, label, onClick, danger }) {
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|