@decentnetwork/lan 0.1.228 → 0.1.230
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 +123 -5
- package/dist/ui/desktop/index.html +14 -0
- package/package.json +1 -1
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1210,6 +1210,98 @@ function dkCallFromText(text) {
|
|
|
1210
1210
|
const m = /^WebRTC (audio|video) call: (incoming|outgoing)$/.exec(String(text || ""));
|
|
1211
1211
|
return m ? { kind: m[1], direction: m[2] } : null;
|
|
1212
1212
|
}
|
|
1213
|
+
function dkHtmlEscape(s) {
|
|
1214
|
+
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1215
|
+
}
|
|
1216
|
+
function dkSafeHref(s) {
|
|
1217
|
+
const href = String(s || "").trim();
|
|
1218
|
+
if (!href) return "";
|
|
1219
|
+
try {
|
|
1220
|
+
const u = new URL(href, window.location.href);
|
|
1221
|
+
if (u.protocol === "http:" || u.protocol === "https:" || u.protocol === "mailto:") return u.href;
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
}
|
|
1224
|
+
return "";
|
|
1225
|
+
}
|
|
1226
|
+
function dkInlineMarkdown(s) {
|
|
1227
|
+
const text = String(s == null ? "" : s);
|
|
1228
|
+
const tokenRe = /(`[^`\n]+`|\[[^\]\n]+\]\([^) \n]+(?: [^)]+)?\)|\*\*[^*\n]+?\*\*|\*[^*\n]+?\*)/g;
|
|
1229
|
+
let out = "";
|
|
1230
|
+
let last = 0;
|
|
1231
|
+
let m;
|
|
1232
|
+
while (m = tokenRe.exec(text)) {
|
|
1233
|
+
out += dkHtmlEscape(text.slice(last, m.index));
|
|
1234
|
+
const tok = m[0];
|
|
1235
|
+
if (tok[0] === "`") {
|
|
1236
|
+
out += "<code>" + dkHtmlEscape(tok.slice(1, -1)) + "</code>";
|
|
1237
|
+
} else if (tok.startsWith("[")) {
|
|
1238
|
+
const lm = /^\[([^\]\n]+)\]\(([^) \n]+)(?: [^)]+)?\)$/.exec(tok);
|
|
1239
|
+
const href = lm && dkSafeHref(lm[2]);
|
|
1240
|
+
out += href ? '<a href="' + dkHtmlEscape(href) + '" target="_blank" rel="noopener">' + dkHtmlEscape(lm[1]) + "</a>" : dkHtmlEscape(tok);
|
|
1241
|
+
} else if (tok.startsWith("**")) {
|
|
1242
|
+
out += "<strong>" + dkHtmlEscape(tok.slice(2, -2)) + "</strong>";
|
|
1243
|
+
} else if (tok.startsWith("*")) {
|
|
1244
|
+
out += "<em>" + dkHtmlEscape(tok.slice(1, -1)) + "</em>";
|
|
1245
|
+
} else {
|
|
1246
|
+
out += dkHtmlEscape(tok);
|
|
1247
|
+
}
|
|
1248
|
+
last = tokenRe.lastIndex;
|
|
1249
|
+
}
|
|
1250
|
+
out += dkHtmlEscape(text.slice(last));
|
|
1251
|
+
return out;
|
|
1252
|
+
}
|
|
1253
|
+
function dkMarkdownHtml(src) {
|
|
1254
|
+
const lines = String(src == null ? "" : src).replace(/\r\n?/g, "\n").split("\n");
|
|
1255
|
+
let html = "";
|
|
1256
|
+
let i = 0;
|
|
1257
|
+
const isBlockStart = (line) => /^```/.test(line) || /^#{1,6}\s+/.test(line) || /^\s*[-*]\s+/.test(line) || /^\s*\d+\.\s+/.test(line);
|
|
1258
|
+
while (i < lines.length) {
|
|
1259
|
+
const line = lines[i];
|
|
1260
|
+
if (!line.trim()) {
|
|
1261
|
+
i += 1;
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (/^```/.test(line)) {
|
|
1265
|
+
i += 1;
|
|
1266
|
+
const code = [];
|
|
1267
|
+
while (i < lines.length && !/^```/.test(lines[i])) {
|
|
1268
|
+
code.push(lines[i]);
|
|
1269
|
+
i += 1;
|
|
1270
|
+
}
|
|
1271
|
+
if (i < lines.length) i += 1;
|
|
1272
|
+
html += "<pre><code>" + dkHtmlEscape(code.join("\n")) + "</code></pre>";
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
const hm = /^(#{1,6})\s+(.+)$/.exec(line);
|
|
1276
|
+
if (hm) {
|
|
1277
|
+
const level = Math.min(6, hm[1].length);
|
|
1278
|
+
html += "<h" + level + ">" + dkInlineMarkdown(hm[2]) + "</h" + level + ">";
|
|
1279
|
+
i += 1;
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (/^\s*[-*]\s+/.test(line) || /^\s*\d+\.\s+/.test(line)) {
|
|
1283
|
+
const ordered = /^\s*\d+\.\s+/.test(line);
|
|
1284
|
+
html += ordered ? "<ol>" : "<ul>";
|
|
1285
|
+
while (i < lines.length && (ordered ? /^\s*\d+\.\s+/.test(lines[i]) : /^\s*[-*]\s+/.test(lines[i]))) {
|
|
1286
|
+
html += "<li>" + dkInlineMarkdown(lines[i].replace(ordered ? /^\s*\d+\.\s+/ : /^\s*[-*]\s+/, "")) + "</li>";
|
|
1287
|
+
i += 1;
|
|
1288
|
+
}
|
|
1289
|
+
html += ordered ? "</ol>" : "</ul>";
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const para = [line];
|
|
1293
|
+
i += 1;
|
|
1294
|
+
while (i < lines.length && lines[i].trim() && !isBlockStart(lines[i])) {
|
|
1295
|
+
para.push(lines[i]);
|
|
1296
|
+
i += 1;
|
|
1297
|
+
}
|
|
1298
|
+
html += "<p>" + para.map(dkInlineMarkdown).join("<br>") + "</p>";
|
|
1299
|
+
}
|
|
1300
|
+
return html || dkHtmlEscape(src);
|
|
1301
|
+
}
|
|
1302
|
+
function MarkdownText({ text }) {
|
|
1303
|
+
return /* @__PURE__ */ React.createElement("div", { className: "dk-md", dangerouslySetInnerHTML: { __html: dkMarkdownHtml(text) } });
|
|
1304
|
+
}
|
|
1213
1305
|
function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onCall, selMode, selected, onToggleSel, busy }) {
|
|
1214
1306
|
const mine = m.from === "me";
|
|
1215
1307
|
const rtcFile = !mine && !m.file ? dkRtcFileFromText(peer.userId || peer.id, m.text) : null;
|
|
@@ -1430,7 +1522,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onCall, selMo
|
|
|
1430
1522
|
lineHeight: 1.4,
|
|
1431
1523
|
letterSpacing: -0.1,
|
|
1432
1524
|
wordBreak: "break-word"
|
|
1433
|
-
} }, m.text), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), !mine && m.via && /* @__PURE__ */ React.createElement(
|
|
1525
|
+
} }, /* @__PURE__ */ React.createElement(MarkdownText, { text: m.text })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), !mine && m.via && /* @__PURE__ */ React.createElement(
|
|
1434
1526
|
"span",
|
|
1435
1527
|
{
|
|
1436
1528
|
title: m.via === "offline" ? "delivered via express relay (offline)" : "delivered over a live session (online)",
|
|
@@ -1633,8 +1725,14 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1633
1725
|
if (!files || !files.length || !sender) return;
|
|
1634
1726
|
const file = files[0];
|
|
1635
1727
|
followScrollRef.current = true;
|
|
1636
|
-
setSending({ name: file.name, via: mode, pct: 0 });
|
|
1637
|
-
const onProgress = (p) => setSending((s) => s && s.name === file.name ? {
|
|
1728
|
+
setSending({ name: file.name, via: mode, pct: 0, sent: 0, size: file.size });
|
|
1729
|
+
const onProgress = (p) => setSending((s) => s && s.name === file.name ? {
|
|
1730
|
+
...s,
|
|
1731
|
+
pct: p && p.pct,
|
|
1732
|
+
sent: p && p.sent,
|
|
1733
|
+
size: p && p.size || s.size,
|
|
1734
|
+
kbps: p && p.kbps
|
|
1735
|
+
} : s);
|
|
1638
1736
|
Promise.resolve(sender(file, onProgress)).then((r) => {
|
|
1639
1737
|
if (r && r.ok === false) window.alert((lang === "zh" ? "\u53D1\u9001\u5931\u8D25: " : "Send failed: ") + (r.error || ""));
|
|
1640
1738
|
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");
|
|
@@ -1722,7 +1820,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
|
|
|
1722
1820
|
stroke: 2.2,
|
|
1723
1821
|
color: flash.kind === "err" ? "var(--danger)" : "var(--accent)"
|
|
1724
1822
|
}
|
|
1725
|
-
), 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(
|
|
1823
|
+
), 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.size ? " \xB7 " + dkFileSize(sending.sent || 0) + " / " + dkFileSize(sending.size) : "") + (sending.pct != null ? " \xB7 " + Math.min(100, Math.max(0, sending.pct)).toFixed(1) + "%" : "") + (sending.kbps != null ? " \xB7 " + dkSpeed(sending.kbps) : "")), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(
|
|
1726
1824
|
"input",
|
|
1727
1825
|
{
|
|
1728
1826
|
ref: fileRef,
|
|
@@ -2207,16 +2305,36 @@ function useRtcFileController(selfId, onReceivedFile) {
|
|
|
2207
2305
|
await dkWaitForOpen(dc, DK_FILE_RTC_OPEN_TIMEOUT_MS, () => dkRtcState(pc));
|
|
2208
2306
|
dc.send(JSON.stringify({ type: "meta", name: file.name, size: file.size, mime: file.type || "application/octet-stream" }));
|
|
2209
2307
|
let sent = 0;
|
|
2308
|
+
const startedAt = Date.now();
|
|
2309
|
+
let lastProgressAt = 0;
|
|
2210
2310
|
const highWater = 4 * 1024 * 1024;
|
|
2311
|
+
const reportProgress = (force) => {
|
|
2312
|
+
if (!onProgress) return;
|
|
2313
|
+
const now = Date.now();
|
|
2314
|
+
if (!force && now - lastProgressAt < 250 && sent < file.size) return;
|
|
2315
|
+
lastProgressAt = now;
|
|
2316
|
+
const elapsedMs = Math.max(1, now - startedAt);
|
|
2317
|
+
const kbps = sent / 1024 / (elapsedMs / 1e3);
|
|
2318
|
+
onProgress({
|
|
2319
|
+
sent,
|
|
2320
|
+
size: file.size,
|
|
2321
|
+
pct: file.size ? sent / file.size * 100 : 100,
|
|
2322
|
+
kbps,
|
|
2323
|
+
elapsedMs,
|
|
2324
|
+
via: "webrtc"
|
|
2325
|
+
});
|
|
2326
|
+
};
|
|
2327
|
+
reportProgress(true);
|
|
2211
2328
|
while (sent < file.size) {
|
|
2212
2329
|
if (dc.readyState !== "open") throw new Error("WebRTC data channel closed");
|
|
2213
2330
|
const end = Math.min(file.size, sent + DK_FILE_RTC_CHUNK);
|
|
2214
2331
|
const buf = await file.slice(sent, end).arrayBuffer();
|
|
2215
2332
|
dc.send(buf);
|
|
2216
2333
|
sent = end;
|
|
2217
|
-
|
|
2334
|
+
reportProgress(false);
|
|
2218
2335
|
await dkWaitBufferedLow(dc, highWater);
|
|
2219
2336
|
}
|
|
2337
|
+
reportProgress(true);
|
|
2220
2338
|
dc.send(JSON.stringify({ type: "done" }));
|
|
2221
2339
|
await sendSignal("bye", { reason: "done" }).catch(() => {
|
|
2222
2340
|
});
|
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
23
23
|
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
|
|
24
24
|
::-webkit-scrollbar-track { background: transparent; }
|
|
25
|
+
.dk-md { white-space: normal; }
|
|
26
|
+
.dk-md p { margin: 0; }
|
|
27
|
+
.dk-md p + p, .dk-md p + ul, .dk-md p + ol, .dk-md p + pre,
|
|
28
|
+
.dk-md ul + p, .dk-md ol + p, .dk-md pre + p,
|
|
29
|
+
.dk-md h1 + p, .dk-md h2 + p, .dk-md h3 + p { margin-top: 0.5em; }
|
|
30
|
+
.dk-md h1, .dk-md h2, .dk-md h3, .dk-md h4, .dk-md h5, .dk-md h6 {
|
|
31
|
+
margin: 0 0 0.35em; font-size: 1em; line-height: 1.25; font-weight: 800;
|
|
32
|
+
}
|
|
33
|
+
.dk-md ul, .dk-md ol { margin: 0.25em 0; padding-left: 1.35em; }
|
|
34
|
+
.dk-md li { margin: 0.15em 0; }
|
|
35
|
+
.dk-md code { font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; font-size: 0.92em; background: rgba(0,0,0,0.18); border-radius: 4px; padding: 0.08em 0.3em; }
|
|
36
|
+
.dk-md pre { margin: 0.45em 0 0; padding: 8px 10px; overflow: auto; border-radius: 8px; background: rgba(0,0,0,0.22); }
|
|
37
|
+
.dk-md pre code { display: block; background: transparent; padding: 0; white-space: pre; }
|
|
38
|
+
.dk-md a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
|
|
25
39
|
@keyframes dkpulse { 0% { transform: scale(0.9); opacity: 0.7; } 70% { transform: scale(1.5); opacity: 0; } 100% { opacity: 0; } }
|
|
26
40
|
</style>
|
|
27
41
|
</head>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.230",
|
|
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",
|