@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.
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 ? { ...s, pct: p && p.pct } : s);
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
- if (onProgress) onProgress({ sent, size: file.size, pct: file.size ? sent / file.size * 100 : 100 });
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.228",
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",