@decentnetwork/lan 0.1.140 → 0.1.142
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/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/daemon/message-store.d.ts +4 -0
- package/dist/daemon/server.js +4 -1
- package/dist/ui/desktop/app.js +84 -10
- package/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -30,6 +30,8 @@ export interface ChatMessage {
|
|
|
30
30
|
size: number;
|
|
31
31
|
status?: "queued" | "sending" | "sent" | "failed";
|
|
32
32
|
sent?: number;
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
avgKbps?: number;
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
export declare class MessageStore {
|
|
@@ -65,6 +67,8 @@ export declare class MessageStore {
|
|
|
65
67
|
patchFile(peer: string, id: string, patch: {
|
|
66
68
|
status?: "queued" | "sending" | "sent" | "failed";
|
|
67
69
|
sent?: number;
|
|
70
|
+
durationMs?: number;
|
|
71
|
+
avgKbps?: number;
|
|
68
72
|
}): boolean;
|
|
69
73
|
private push;
|
|
70
74
|
/**
|
package/dist/daemon/server.js
CHANGED
|
@@ -814,7 +814,10 @@ export class DaemonServer {
|
|
|
814
814
|
this.logger.info(`File "${p.name}" (${mb} MB) delivered to ${p.friendId.slice(0, 8)} in ${secs}s (${rate} MB/s)`);
|
|
815
815
|
const t = p.fileId ? this.activeSends.get(p.fileId) : undefined;
|
|
816
816
|
if (t) {
|
|
817
|
-
|
|
817
|
+
// Persist the delivery stats so total time + average speed survive
|
|
818
|
+
// (previously they were only logged and lost when the log scrolled).
|
|
819
|
+
const avgKbps = p.durationMs && p.durationMs > 0 ? p.size / 1024 / (p.durationMs / 1000) : undefined;
|
|
820
|
+
this.messageStore?.patchFile(t.peer, t.msgId, { status: "sent", sent: p.size, durationMs: p.durationMs, avgKbps });
|
|
818
821
|
this.activeSends.delete(p.fileId);
|
|
819
822
|
// If this was a queued (offline) send, its bytes lived in the
|
|
820
823
|
// outbox — now delivered, drop the copy.
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -52,6 +52,7 @@ 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"/>',
|
|
55
56
|
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
57
|
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
58
|
};
|
|
@@ -1030,9 +1031,9 @@ const inputStyle = {
|
|
|
1030
1031
|
outline: "none",
|
|
1031
1032
|
minWidth: 0
|
|
1032
1033
|
};
|
|
1033
|
-
function Msg({ m, peer, T }) {
|
|
1034
|
+
function Msg({ m, peer, T, onTheater }) {
|
|
1034
1035
|
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 ? (
|
|
1036
|
+
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
1037
|
// Inline preview/player. Received media: as soon as it's saved.
|
|
1037
1038
|
// Sent media: once it's fully delivered ('sent') — the daemon keeps a
|
|
1038
1039
|
// local copy so it plays on the sender's side too.
|
|
@@ -1042,27 +1043,42 @@ function Msg({ m, peer, T }) {
|
|
|
1042
1043
|
display: "flex",
|
|
1043
1044
|
flexDirection: "column",
|
|
1044
1045
|
gap: 6,
|
|
1045
|
-
maxWidth: 300,
|
|
1046
|
+
maxWidth: m.file.media === "image" || m.file.media === "video" ? "100%" : 300,
|
|
1047
|
+
width: m.file.media === "image" || m.file.media === "video" ? "100%" : void 0,
|
|
1046
1048
|
background: "var(--bub-them)",
|
|
1047
1049
|
border: "1px solid var(--line)",
|
|
1048
1050
|
borderRadius: 12,
|
|
1049
1051
|
padding: 6
|
|
1050
|
-
} }, m.file.media === "image" && /* @__PURE__ */ React.createElement("
|
|
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(
|
|
1051
1053
|
"img",
|
|
1052
1054
|
{
|
|
1053
1055
|
src: dkFileUrl(m.file.name),
|
|
1054
1056
|
alt: m.file.name,
|
|
1055
|
-
style: { display: "block",
|
|
1057
|
+
style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain" }
|
|
1056
1058
|
}
|
|
1057
|
-
)
|
|
1059
|
+
) : /* @__PURE__ */ React.createElement(
|
|
1058
1060
|
"video",
|
|
1059
1061
|
{
|
|
1060
1062
|
src: dkFileUrl(m.file.name),
|
|
1061
1063
|
controls: true,
|
|
1062
1064
|
preload: "metadata",
|
|
1063
|
-
style: {
|
|
1065
|
+
style: { display: "block", width: "100%", maxHeight: "72vh", borderRadius: 8, objectFit: "contain", background: "#000" }
|
|
1064
1066
|
}
|
|
1065
|
-
),
|
|
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)" }))))
|
|
1066
1082
|
) : (() => {
|
|
1067
1083
|
const cardStyle = {
|
|
1068
1084
|
display: "flex",
|
|
@@ -1129,9 +1145,21 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1129
1145
|
const [draft, setDraft] = React.useState("");
|
|
1130
1146
|
const [dragOver, setDragOver] = React.useState(false);
|
|
1131
1147
|
const [sending, setSending] = React.useState(null);
|
|
1148
|
+
const [theater, setTheater] = React.useState(null);
|
|
1132
1149
|
React.useEffect(() => {
|
|
1133
1150
|
setDraft("");
|
|
1134
1151
|
}, [peer.id]);
|
|
1152
|
+
React.useEffect(() => {
|
|
1153
|
+
setTheater(null);
|
|
1154
|
+
}, [peer.id]);
|
|
1155
|
+
React.useEffect(() => {
|
|
1156
|
+
if (!theater) return;
|
|
1157
|
+
const onKey = (e) => {
|
|
1158
|
+
if (e.key === "Escape") setTheater(null);
|
|
1159
|
+
};
|
|
1160
|
+
window.addEventListener("keydown", onKey);
|
|
1161
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1162
|
+
}, [theater]);
|
|
1135
1163
|
const sendDraft = () => {
|
|
1136
1164
|
if (draft.trim()) {
|
|
1137
1165
|
onSend(draft);
|
|
@@ -1172,7 +1200,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1172
1200
|
setMenu(false);
|
|
1173
1201
|
onRemove(peer);
|
|
1174
1202
|
} }))))),
|
|
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 })))),
|
|
1203
|
+
/* @__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
1204
|
/* @__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
1205
|
"input",
|
|
1178
1206
|
{
|
|
@@ -1198,7 +1226,53 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1198
1226
|
placeholder: `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
|
|
1199
1227
|
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
1228
|
}
|
|
1201
|
-
), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft })))
|
|
1229
|
+
), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft }))),
|
|
1230
|
+
theater && /* @__PURE__ */ React.createElement("div", { onClick: () => setTheater(null), style: {
|
|
1231
|
+
position: "absolute",
|
|
1232
|
+
inset: 0,
|
|
1233
|
+
zIndex: 60,
|
|
1234
|
+
background: "rgba(0,0,0,0.86)",
|
|
1235
|
+
display: "flex",
|
|
1236
|
+
flexDirection: "column"
|
|
1237
|
+
} }, /* @__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(
|
|
1238
|
+
"button",
|
|
1239
|
+
{
|
|
1240
|
+
title: lang === "zh" ? "\u5168\u5C4F" : "fullscreen",
|
|
1241
|
+
onClick: () => {
|
|
1242
|
+
const el = document.getElementById("dk-theater-media");
|
|
1243
|
+
if (el && el.requestFullscreen) el.requestFullscreen();
|
|
1244
|
+
},
|
|
1245
|
+
style: { width: 34, height: 34, borderRadius: 8, border: "none", background: "rgba(255,255,255,0.14)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }
|
|
1246
|
+
},
|
|
1247
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "maximize", size: 18, stroke: 2, color: "#fff" })
|
|
1248
|
+
), /* @__PURE__ */ React.createElement(
|
|
1249
|
+
"button",
|
|
1250
|
+
{
|
|
1251
|
+
title: (lang === "zh" ? "\u5173\u95ED" : "close") + " \xB7 Esc",
|
|
1252
|
+
onClick: () => setTheater(null),
|
|
1253
|
+
style: { width: 34, height: 34, borderRadius: 8, border: "none", background: "rgba(255,255,255,0.14)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }
|
|
1254
|
+
},
|
|
1255
|
+
/* @__PURE__ */ React.createElement(Icon, { name: "x", size: 20, stroke: 2.2, color: "#fff" })
|
|
1256
|
+
))), /* @__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(
|
|
1257
|
+
"video",
|
|
1258
|
+
{
|
|
1259
|
+
id: "dk-theater-media",
|
|
1260
|
+
src: theater.url,
|
|
1261
|
+
controls: true,
|
|
1262
|
+
autoPlay: true,
|
|
1263
|
+
onClick: (e) => e.stopPropagation(),
|
|
1264
|
+
style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6, background: "#000" }
|
|
1265
|
+
}
|
|
1266
|
+
) : /* @__PURE__ */ React.createElement(
|
|
1267
|
+
"img",
|
|
1268
|
+
{
|
|
1269
|
+
id: "dk-theater-media",
|
|
1270
|
+
src: theater.url,
|
|
1271
|
+
alt: theater.name,
|
|
1272
|
+
onClick: (e) => e.stopPropagation(),
|
|
1273
|
+
style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain", borderRadius: 6 }
|
|
1274
|
+
}
|
|
1275
|
+
)))
|
|
1202
1276
|
);
|
|
1203
1277
|
}
|
|
1204
1278
|
function MenuItem({ icon, label, onClick, danger }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.142",
|
|
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.75",
|
|
83
83
|
"ink": "^5.2.1",
|
|
84
84
|
"js-yaml": "^4.1.0",
|
|
85
85
|
"node-forge": "^1.4.0",
|