@decentnetwork/lan 0.1.155 → 0.1.156

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.
Binary file
Binary file
Binary file
Binary file
@@ -514,7 +514,7 @@ export class PeerManager extends EventEmitter {
514
514
  if (message.text.length === 0) {
515
515
  return;
516
516
  }
517
- this.emit("message", message.pubkey, message.text);
517
+ this.emit("message", message.pubkey, message.text, message.via);
518
518
  });
519
519
  // Toxcore-standard file transfer (native-compatible). Forward offers,
520
520
  // progress, and completions to the daemon, which auto-accepts and saves.
@@ -18,6 +18,11 @@ export interface ChatMessage {
18
18
  * user hit send, so the daemon stored it and will deliver it (in order) the
19
19
  * moment the friend reconnects. Cleared once actually sent. */
20
20
  status?: "queued" | "sent" | "failed";
21
+ /** How this message traversed the network, for the UI to distinguish at a
22
+ * glance (different colors). "online" = live net_crypto session (direct/relay);
23
+ * "offline" = express store-and-forward (the friend or we were offline). Lets
24
+ * a user SEE when online delivery is silently failing and only offline lands. */
25
+ via?: "online" | "offline";
21
26
  /** Present when this entry is a file transfer rather than a text message.
22
27
  * `name`/`size` describe the file; received files (dir:"in") are saved to
23
28
  * <configDir>/downloads/<name> and downloadable via the UI. For outgoing
@@ -47,7 +52,7 @@ export declare class MessageStore {
47
52
  /** Append a message and schedule a flush. Returns the stored message.
48
53
  * Pass `status: "queued"` for an outgoing text the peer wasn't online to
49
54
  * receive — the daemon flushes it on reconnect. */
50
- append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "queued" | "sent" | "failed"): ChatMessage;
55
+ append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage;
51
56
  /** Set (or, with undefined, clear) the delivery status on a text message.
52
57
  * Used to flip a "queued" message to delivered once it's actually sent.
53
58
  * No-op if the id isn't found or is a file entry. Returns true if patched. */
@@ -46,10 +46,12 @@ export class MessageStore {
46
46
  /** Append a message and schedule a flush. Returns the stored message.
47
47
  * Pass `status: "queued"` for an outgoing text the peer wasn't online to
48
48
  * receive — the daemon flushes it on reconnect. */
49
- append(peer, dir, text, ts = Date.now(), status) {
49
+ append(peer, dir, text, ts = Date.now(), status, via) {
50
50
  const msg = { dir, text, ts, id: `${ts}-${this.seq++}` };
51
51
  if (status)
52
52
  msg.status = status;
53
+ if (via)
54
+ msg.via = via;
53
55
  return this.push(peer, msg);
54
56
  }
55
57
  /** Set (or, with undefined, clear) the delivery status on a text message.
@@ -806,8 +806,8 @@ export class DaemonServer {
806
806
  return input;
807
807
  };
808
808
  // Chat: log incoming Carrier text messages (packet 64) for the UI.
809
- this.peerManager.on("message", (pubkey, text) => {
810
- this.logChat(pubkey, "in", text);
809
+ this.peerManager.on("message", (pubkey, text, via) => {
810
+ this.logChat(pubkey, "in", text, via);
811
811
  });
812
812
  // Presence: push to IPC subscribers so the TUI/UI reflects online/offline
813
813
  // transitions immediately instead of on the next poll tick.
@@ -1139,8 +1139,8 @@ export class DaemonServer {
1139
1139
  /** Append a chat message to the on-disk message store (persists across
1140
1140
  * restarts). Also ensures a friend-meta entry exists so the friend shows up
1141
1141
  * in the UI list even before it's been renamed. */
1142
- logChat(userid, dir, text) {
1143
- this.messageStore?.append(userid, dir, text);
1142
+ logChat(userid, dir, text, via) {
1143
+ this.messageStore?.append(userid, dir, text, Date.now(), undefined, via);
1144
1144
  this.friendMeta?.ensure(userid);
1145
1145
  this.ipcEvents.emit("event", { type: "chat", userid, dir });
1146
1146
  }
@@ -307,7 +307,10 @@ function useDaemonData() {
307
307
  pct: m.file.status === "sending" && m.file.size ? Math.min(100, (m.file.sent || 0) / m.file.size * 100) : void 0,
308
308
  kbps: m.file.kbps
309
309
  } : void 0,
310
- status: m.dir === "out" ? m.status === "queued" ? "queued" : "read" : void 0
310
+ status: m.dir === "out" ? m.status === "queued" ? "queued" : "read" : void 0,
311
+ // Delivery path: "online" = live session, "offline" = express relay.
312
+ // Incoming messages carry this; the bubble colors them differently.
313
+ via: m.via
311
314
  }));
312
315
  const withDay = msgs.length ? [{ day: dkDayLabel(arr[0].ts) }].concat(msgs) : [];
313
316
  setThreads((t) => Object.assign({}, t, { [peerId]: withDay }));
@@ -1206,15 +1209,25 @@ function Msg({ m, peer, T, onTheater, onDelete, onCancel, selMode, selected, onT
1206
1209
  borderRadius: 12,
1207
1210
  borderBottomRightRadius: mine ? 4 : 12,
1208
1211
  borderBottomLeftRadius: mine ? 12 : 4,
1209
- background: mine ? "var(--bub-me)" : "var(--bub-them)",
1212
+ // Incoming messages are tinted by delivery path so the split is
1213
+ // visible: amber = arrived via the express relay (offline), plain =
1214
+ // live online session. Outgoing keep the accent bubble.
1215
+ background: mine ? "var(--bub-me)" : m.via === "offline" ? "rgba(245,158,11,0.16)" : "var(--bub-them)",
1210
1216
  color: mine ? "#fff" : "var(--text)",
1211
- border: "1px solid " + (mine ? "transparent" : "var(--line)"),
1217
+ border: "1px solid " + (mine ? "transparent" : m.via === "offline" ? "rgba(245,158,11,0.55)" : "var(--line)"),
1212
1218
  fontFamily: "var(--ui)",
1213
1219
  fontSize: 13.5,
1214
1220
  lineHeight: 1.4,
1215
1221
  letterSpacing: -0.1,
1216
1222
  wordBreak: "break-word"
1217
- } }, 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), !selMode && m.id && /* @__PURE__ */ React.createElement(
1223
+ } }, 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(
1224
+ "span",
1225
+ {
1226
+ title: m.via === "offline" ? "delivered via express relay (offline)" : "delivered over a live session (online)",
1227
+ style: { fontFamily: "var(--mono)", fontSize: 9, color: m.via === "offline" ? "#f59e0b" : "var(--faint)" }
1228
+ },
1229
+ m.via === "offline" ? "\u79BB\u7EBF" : "\u5728\u7EBF"
1230
+ ), !selMode && m.id && /* @__PURE__ */ React.createElement(
1218
1231
  "button",
1219
1232
  {
1220
1233
  title: T.delete || "delete",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.155",
3
+ "version": "0.1.156",
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.13",
82
- "@decentnetwork/peer": "^0.1.79",
82
+ "@decentnetwork/peer": "^0.1.81",
83
83
  "ink": "^5.2.1",
84
84
  "js-yaml": "^4.1.0",
85
85
  "node-forge": "^1.4.0",