@decentnetwork/beagle 0.1.11 → 0.1.13

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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.11";
1
+ window.__DK_UI_VERSION="0.1.13";
2
2
  const ICON_PATHS = {
3
3
  // ---- tab bar (the four must feel like one set) ----
4
4
  users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
@@ -1528,6 +1528,72 @@ function dkMarkdownHtml(src) {
1528
1528
  }
1529
1529
  return html || dkHtmlEscape(src);
1530
1530
  }
1531
+ const DK_NATIVE_MEDIA_MIME = {
1532
+ ".jpg": "image/jpeg",
1533
+ ".jpeg": "image/jpeg",
1534
+ ".png": "image/png",
1535
+ ".gif": "image/gif",
1536
+ ".webp": "image/webp",
1537
+ ".heic": "image/heic",
1538
+ ".m4a": "audio/mp4",
1539
+ ".aac": "audio/aac",
1540
+ ".mp3": "audio/mpeg",
1541
+ ".wav": "audio/wav",
1542
+ ".caf": "audio/x-caf",
1543
+ ".mp4": "video/mp4",
1544
+ ".mov": "video/quicktime",
1545
+ ".m4v": "video/x-m4v"
1546
+ };
1547
+ const DK_GROUP_TS_RE = /\n?\[(20\d{2}-\d{1,2}-\d{1,2}\s+\d{1,2}:\d{2}:\d{2})\]\s*$/;
1548
+ function dkStripGroupTs(text) {
1549
+ const m = DK_GROUP_TS_RE.exec(text);
1550
+ return m ? { body: text.slice(0, m.index), ts: m[1] } : { body: text, ts: null };
1551
+ }
1552
+ function dkSplitGroupSender(text) {
1553
+ const m = /^([^:\n]{1,48}):\s([\s\S]+)$/.exec(text);
1554
+ return m ? { sender: m[1], body: m[2] } : null;
1555
+ }
1556
+ function dkGroupParse(text) {
1557
+ const t = String(text || "");
1558
+ const { body: noTs, ts } = dkStripGroupTs(t);
1559
+ const split = dkSplitGroupSender(noTs.trim());
1560
+ if (!split)
1561
+ return null;
1562
+ return { sender: split.sender, body: split.body.trim(), ts, strong: !!ts };
1563
+ }
1564
+ function dkThreadIsGroup(thread) {
1565
+ return (thread || []).some((m) => !m.day && m.dir !== "out" && !m.file && (dkGroupParse(m.text) || {}).strong);
1566
+ }
1567
+ function dkNativeMediaFromText(text) {
1568
+ let body = dkStripGroupTs(String(text || "")).body.trim();
1569
+ if (body.length < 32)
1570
+ return null;
1571
+ let sender = null;
1572
+ if (!body.startsWith("{")) {
1573
+ const split = dkSplitGroupSender(body);
1574
+ if (!split || !split.body.startsWith("{"))
1575
+ return null;
1576
+ sender = split.sender;
1577
+ body = split.body.trim();
1578
+ }
1579
+ if (!body.startsWith("{") || !body.endsWith("}"))
1580
+ return null;
1581
+ let j = null;
1582
+ try {
1583
+ j = JSON.parse(body);
1584
+ } catch (e) {
1585
+ return null;
1586
+ }
1587
+ if (!j || typeof j !== "object" || typeof j.data !== "string" || j.data.length < 16)
1588
+ return null;
1589
+ const kind = j.type === "image" ? "image" : j.type === "audio" || j.type === "voice" ? "audio" : j.type === "video" ? "video" : null;
1590
+ if (!kind)
1591
+ return null;
1592
+ const ext = String(j.fileExtension || "").toLowerCase();
1593
+ const mime = DK_NATIVE_MEDIA_MIME[ext] || kind + "/*";
1594
+ const name = String(j.fileName || "file") + ext;
1595
+ return { sender, kind, name, url: "data:" + mime + ";base64," + j.data.replace(/\s+/g, "") };
1596
+ }
1531
1597
  function MarkdownText({ text }) {
1532
1598
  return /* @__PURE__ */ React.createElement("div", { className: "dk-md", dangerouslySetInnerHTML: { __html: dkMarkdownHtml(text) } });
1533
1599
  }
@@ -1701,9 +1767,11 @@ function DkChatForm({ form, submitted, peer, onSubmit }) {
1701
1767
  return null;
1702
1768
  }), err && /* @__PURE__ */ React.createElement("div", { style: { color: "var(--danger, #ff6b6b)", fontSize: 11.5, fontFamily: "var(--mono)" } }, err));
1703
1769
  }
1704
- function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onCall, onFormSubmit, answered, selMode, selected, onToggleSel, busy }) {
1770
+ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onCall, onFormSubmit, answered, selMode, selected, onToggleSel, busy, isGroup }) {
1705
1771
  const mine = m.from === "me";
1706
- const read = window.ChatComponents ? window.ChatComponents.readMessage(m.text || "") : { text: m.text, form: null, interaction: null };
1772
+ const nativeMedia = !m.file ? dkNativeMediaFromText(m.text) : null;
1773
+ const grp = isGroup && m.dir !== "out" && !m.file && !nativeMedia ? dkGroupParse(m.text) : null;
1774
+ const read = window.ChatComponents ? window.ChatComponents.readMessage((grp ? grp.body : m.text) || "") : { text: grp ? grp.body : m.text, form: null, interaction: null };
1707
1775
  const submitted = read.form && answered ? answered.get(read.form.custom_id) : null;
1708
1776
  const rtcFile = !mine && !m.file ? dkRtcFileFromText(peer.userId || peer.id, m.text) : null;
1709
1777
  const callRec = !m.file ? dkCallFromText(m.text) : null;
@@ -1934,7 +2002,22 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
1934
2002
  )
1935
2003
  );
1936
2004
  })()
1937
- ) : /* @__PURE__ */ React.createElement("div", { style: {
2005
+ ) : nativeMedia ? /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxWidth: "min(420px, 88%)" } }, nativeMedia.sender && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, nativeMedia.sender), nativeMedia.kind === "image" ? /* @__PURE__ */ React.createElement(
2006
+ "img",
2007
+ {
2008
+ src: nativeMedia.url,
2009
+ alt: nativeMedia.name,
2010
+ style: { maxWidth: "100%", borderRadius: 12, border: "1px solid var(--line)", cursor: "pointer" },
2011
+ onClick: () => {
2012
+ try {
2013
+ const w = window.open();
2014
+ if (w)
2015
+ w.document.write('<img src="' + nativeMedia.url + '" style="max-width:100%">');
2016
+ } catch (e) {
2017
+ }
2018
+ }
2019
+ }
2020
+ ) : nativeMedia.kind === "audio" ? /* @__PURE__ */ React.createElement("audio", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%" } }) : /* @__PURE__ */ React.createElement("video", { controls: true, src: nativeMedia.url, style: { maxWidth: "100%", borderRadius: 12, border: "1px solid var(--line)" } })) : /* @__PURE__ */ React.createElement(React.Fragment, null, grp && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)", margin: "0 3px 2px" } }, grp.sender), /* @__PURE__ */ React.createElement("div", { style: {
1938
2021
  padding: "8px 12px",
1939
2022
  borderRadius: 12,
1940
2023
  borderBottomRightRadius: mine ? 4 : 12,
@@ -1950,7 +2033,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
1950
2033
  lineHeight: 1.4,
1951
2034
  letterSpacing: -0.1,
1952
2035
  wordBreak: "break-word"
1953
- } }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text })), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), /* @__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(
2036
+ } }, /* @__PURE__ */ React.createElement(MarkdownText, { text: read.text }))), read.form && /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6 } }, /* @__PURE__ */ React.createElement(DkChatForm, { form: read.form, submitted, peer, onSubmit: onFormSubmit })), /* @__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(
1954
2037
  "span",
1955
2038
  {
1956
2039
  title: m.via === "offline" ? "delivered via express relay (offline)" : "delivered over a live session (online)",
@@ -1981,6 +2064,7 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, onRetry, onReveal, onC
1981
2064
  }
1982
2065
  function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
1983
2066
  const scrollRef = React.useRef(null);
2067
+ const isGroup = React.useMemo(() => dkThreadIsGroup(threadProp), [threadProp]);
1984
2068
  const followScrollRef = React.useRef(true);
1985
2069
  const fileRef = React.useRef(null);
1986
2070
  const rtcFileRef = React.useRef(null);
@@ -2233,7 +2317,7 @@ function Conversation({ T, peer, lang, thread: threadProp, onSend, onSendFile, o
2233
2317
  onDrop
2234
2318
  },
2235
2319
  dragOver && /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", inset: 0, zIndex: 30, background: "rgba(91,140,255,0.10)", border: "2px dashed var(--accent)", borderRadius: 12, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "none", fontFamily: "var(--mono)", fontSize: 14, color: "var(--accent)" } }, lang === "zh" ? "\u677E\u5F00\u53D1\u9001\u6587\u4EF6" : "Drop to send file"),
2236
- /* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
2320
+ /* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), isGroup && /* @__PURE__ */ React.createElement(Tag, null, T && T.groupTag || "group"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), title: T && T.openNet || "Network view", style: { background: "none", border: "none", cursor: "pointer", padding: 0, color: "var(--accent)", display: "inline-flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.ip, title: peer.ip }, peer.ip), peer.address && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement(
2237
2321
  Mono,
2238
2322
  {
2239
2323
  size: 11.5,
@@ -2283,6 +2367,7 @@ ${peer.address}`
2283
2367
  m,
2284
2368
  peer,
2285
2369
  T,
2370
+ isGroup,
2286
2371
  onTheater: setTheater,
2287
2372
  onDelete: (id) => doDelete([id]),
2288
2373
  onCancel: doCancel,
@@ -3493,6 +3578,7 @@ const STR = {
3493
3578
  pickPeer: "Select a peer to open the conversation",
3494
3579
  queued: "queued",
3495
3580
  sendingMsg: "sending",
3581
+ groupTag: "group",
3496
3582
  open: "open",
3497
3583
  retry: "Retry",
3498
3584
  cancel: "Cancel",
@@ -3589,6 +3675,7 @@ const STR = {
3589
3675
  pickPeer: "\u9009\u62E9\u4E00\u4F4D\u597D\u53CB\u5F00\u59CB\u4F1A\u8BDD",
3590
3676
  queued: "\u5F85\u53D1\u9001",
3591
3677
  sendingMsg: "\u53D1\u9001\u4E2D",
3678
+ groupTag: "\u7FA4",
3592
3679
  open: "\u6253\u5F00",
3593
3680
  retry: "\u91CD\u8BD5",
3594
3681
  cancel: "\u53D6\u6D88",
@@ -303,6 +303,10 @@ var PeerWebRTC = (() => {
303
303
  const answer = await session.pc.createAnswer();
304
304
  await session.pc.setLocalDescription(answer);
305
305
  await __privateMethod(this, _send, send_fn).call(this, session.peerId, { type: "answer", sdp: answer.sdp, callId });
306
+ try {
307
+ await __privateMethod(this, _send, send_fn).call(this, session.peerId, { type: "action", action: "accept", callId });
308
+ } catch {
309
+ }
306
310
  __privateMethod(this, _setState, setState_fn).call(this, session, "connecting");
307
311
  await __privateMethod(this, _flushLocalCandidates, flushLocalCandidates_fn).call(this, session);
308
312
  __privateMethod(this, _log, log_fn).call(this, `accept callId=${callId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "@decentnetwork/chat-components": "^0.1.3",
29
29
  "@decentnetwork/lan": "^0.1.256",
30
30
  "@decentnetwork/peer": "^0.1.138",
31
- "@decentnetwork/peer-webrtc": "^0.2.10",
31
+ "@decentnetwork/peer-webrtc": "^0.2.11",
32
32
  "js-yaml": "^4.1.0",
33
33
  "yargs": "^17.7.2"
34
34
  },