@decentnetwork/lan 0.1.298 → 0.1.299

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.
@@ -14,10 +14,19 @@ export interface ChatMessage {
14
14
  ts: number;
15
15
  /** Stable per-message id (ts + per-process sequence) for UI keys / dedup. */
16
16
  id: string;
17
- /** Outgoing-text delivery state. "queued" means the peer was offline when the
18
- * user hit send, so the daemon stored it and will deliver it (in order) the
19
- * moment the friend reconnects. Cleared once actually sent. */
20
- status?: "queued" | "sent" | "failed";
17
+ /** Outgoing-text delivery state. "sending" = the background delivery attempt
18
+ * is still running (chat-send returns before the network is consulted).
19
+ * "queued" = that attempt gave up, or the peer was offline when the user hit
20
+ * send; the daemon delivers it (in order) the moment the friend reconnects.
21
+ * Cleared once actually sent.
22
+ *
23
+ * The two states must stay distinct: chat-send used to store the message as
24
+ * "queued" while its own send was in flight, and the reconnect flush sends
25
+ * everything "queued". sendText brings the session up first when it has to,
26
+ * and the session coming up IS the reconnect event — so the flush sent the
27
+ * message, then chat-send sent it again, 10-20 ms apart. Every friend of a
28
+ * daemon saw those messages twice. */
29
+ status?: "sending" | "queued" | "sent" | "failed";
21
30
  /** How this message traversed the network, for the UI to distinguish at a
22
31
  * glance (different colors). "online" = live net_crypto session (direct/relay);
23
32
  * "offline" = express store-and-forward (the friend or we were offline). Lets
@@ -52,11 +61,11 @@ export declare class MessageStore {
52
61
  /** Append a message and schedule a flush. Returns the stored message.
53
62
  * Pass `status: "queued"` for an outgoing text the peer wasn't online to
54
63
  * receive — the daemon flushes it on reconnect. */
55
- append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage;
64
+ append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "sending" | "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage;
56
65
  /** Set (or, with undefined, clear) the delivery status on a text message.
57
66
  * Used to flip a "queued" message to delivered once it's actually sent.
58
67
  * No-op if the id isn't found or is a file entry. Returns true if patched. */
59
- setStatus(peer: string, id: string, status?: "queued" | "sent" | "failed"): boolean;
68
+ setStatus(peer: string, id: string, status?: "sending" | "queued" | "sent" | "failed"): boolean;
60
69
  /** Outgoing messages still awaiting delivery (peer was offline), oldest
61
70
  * first — both queued text and queued file chips. The daemon drains this
62
71
  * on a friend's reconnect. */
@@ -33,6 +33,11 @@ export class MessageStore {
33
33
  let total = 0;
34
34
  for (const [peer, msgs] of Object.entries(raw)) {
35
35
  if (Array.isArray(msgs)) {
36
+ // A "sending" state cannot survive a restart — the attempt it
37
+ // described died with the process. Requeue so the flush owns it.
38
+ for (const m of msgs)
39
+ if (m.status === "sending")
40
+ m.status = "queued";
36
41
  this.byPeer.set(peer, msgs);
37
42
  total += msgs.length;
38
43
  }
@@ -458,14 +458,23 @@ export class DaemonServer {
458
458
  //
459
459
  // Same rule the embedded host and the browser client already follow:
460
460
  // a delivery failure is a STATE on the message, never its absence.
461
- const msg = this.messageStore?.append(userid, "out", text, Date.now(), "queued");
461
+ //
462
+ // Stored as "sending", NOT "queued", while our own attempt runs. The
463
+ // reconnect flush re-sends everything "queued", and sendText brings
464
+ // the session up itself when it has to — which fires the very
465
+ // friend-connection event that triggers the flush. Stored as "queued"
466
+ // the message went out twice, 10-20 ms apart (seen on every daemon
467
+ // sender in ~/.agentnet/messages.json). "queued" now means exactly
468
+ // one thing: nobody is sending this, the flush must.
469
+ const online = !!this.peerManager?.isFriendOnline(userid);
470
+ const msg = this.messageStore?.append(userid, "out", text, Date.now(), online ? "sending" : "queued");
462
471
  this.friendMeta?.ensure(userid);
463
472
  this.ipcEvents.emit("event", { type: "chat", userid, dir: "out" });
464
- if (!msg || !this.peerManager?.isFriendOnline(userid)) {
473
+ if (!msg || !online) {
465
474
  this.logger.info(`Queued text for ${userid.slice(0, 8)} (delivers on reconnect)`);
466
475
  return;
467
476
  }
468
- // Deliver in the background; the reconnect flush covers a failure.
477
+ // Deliver in the background; on failure hand it to the reconnect flush.
469
478
  void (async () => {
470
479
  try {
471
480
  await this.peerManager.sendText(userid, text);
@@ -473,7 +482,8 @@ export class DaemonServer {
473
482
  this.messageStore?.setStatus(userid, msg.id, undefined); // delivered
474
483
  }
475
484
  catch (e) {
476
- this.logger.warn(`sendText to ${userid.slice(0, 8)} failed, stays queued: ${e.message}`);
485
+ this.logger.warn(`sendText to ${userid.slice(0, 8)} failed, queued for reconnect: ${e.message}`);
486
+ this.messageStore?.setStatus(userid, msg.id, "queued");
477
487
  }
478
488
  this.ipcEvents.emit("event", { type: "chat", userid, dir: "out" });
479
489
  })();
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.298";
1
+ window.__DK_UI_VERSION="0.1.299";
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"/>',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.298",
3
+ "version": "0.1.299",
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",