/** * On-disk chat message store. Replaces the previous in-memory-only chatLog so * conversations survive a daemon restart (PRD-DESKTOP-UI §2.1). * * Format: a single JSON file at /messages.json mapping * userid -> ChatMessage[] (oldest first) * Loaded fully into memory on start; writes are debounced and atomic * (write tmp + rename). Text chat is low-volume, so a JSON file is plenty; * swap for SQLite later if threads get large (PRD §9). */ export interface ChatMessage { dir: "in" | "out"; text: string; ts: number; /** Stable per-message id (ts + per-process sequence) for UI keys / dedup. */ id: string; /** Outgoing-text delivery state. "sending" = the background delivery attempt * is still running (chat-send returns before the network is consulted, so * the UI never blocks on a slow peer). "queued" = that attempt gave up; the * daemon will deliver it (in order) the moment the friend reconnects. * Cleared once actually sent. */ status?: "sending" | "queued" | "sent" | "failed"; /** True when the far side confirmed receipt; false when "sent" is a guess. * Absent on old records and on inbound messages. */ confirmed?: boolean; /** How this message traversed the network, for the UI to distinguish at a * glance (different colors). "online" = live net_crypto session (direct/relay); * "offline" = express store-and-forward (the friend or we were offline). Lets * a user SEE when online delivery is silently failing and only offline lands. */ via?: "online" | "offline"; /** Present when this entry is a file transfer rather than a text message. * `name`/`size` describe the file; received files (dir:"in") are saved to * /downloads/ and downloadable via the UI. For outgoing * files, `status` tracks delivery and `sent` is the acked byte count (live * progress) — the receiver confirms every byte before status becomes "sent". * "queued" mirrors text: the peer was offline, so the bytes live in * /outbox/ until the friend reconnects. */ file?: { name: string; size: number; status?: "queued" | "sending" | "sent" | "failed" | "cancelled"; sent?: number; durationMs?: number; avgKbps?: number; kbps?: number; }; } export declare class MessageStore { private path; private byPeer; private logger; private saveTimer?; private dirty; private seq; constructor(path: string); private load; /** Append a message and schedule a flush. Returns the stored message. * Pass `status: "queued"` for an outgoing text the peer wasn't online to * receive — the daemon flushes it on reconnect. */ append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "sending" | "queued" | "sent" | "failed", via?: "online" | "offline"): ChatMessage; /** Set (or, with undefined, clear) the delivery status on a text message. * Used to flip a "queued" message to delivered once it's actually sent. * No-op if the id isn't found or is a file entry. Returns true if patched. */ /** * `confirmed` distinguishes a tick that is EVIDENCE from a tick that is a * guess. sendText reports "acked" only when the far side answered — an * app-level ACK from a JS peer, toxcore's own ACK from a phone. Anything * else went to a live session and was never confirmed. The status stays * "sent" either way, because the message is not queued and must not be * resent; the flag is what the UI reads to stop over-claiming. */ setStatus(peer: string, id: string, status?: "sending" | "queued" | "sent" | "failed", confirmed?: boolean): boolean; /** Record how an outgoing text travelled: "online" over a live session, * "offline" posted to the express relay for the peer to collect. */ setVia(peer: string, id: string, via: "online" | "offline"): boolean; /** Has this peer ever sent us anything? Used as proof-of-friendship for * records that predate the SDK's acceptedAt bookkeeping — a message can * only arrive over an established session. */ hasInbound(peer: string): boolean; /** Outgoing messages still awaiting delivery (peer was offline), oldest * first — both queued text and queued file chips. The daemon drains this * on a friend's reconnect. */ /** Every peer with outgoing mail still waiting, so the outbox sweep does * not have to ask per friend. */ queuedPeers(): string[]; queuedOutgoing(peer: string): ChatMessage[]; /** Append a file-transfer entry (shown as a file chip in the UI). */ appendFile(peer: string, dir: "in" | "out", file: { name: string; size: number; status?: "queued" | "sending" | "sent" | "failed" | "cancelled"; sent?: number; }, ts?: number): ChatMessage; /** Patch an existing file message's transfer fields (status / sent bytes). * No-op if the id isn't found. Returns true if it patched. */ patchFile(peer: string, id: string, patch: { status?: "queued" | "sending" | "sent" | "failed" | "cancelled"; sent?: number; durationMs?: number; avgKbps?: number; kbps?: number; }): boolean; /** Look up one persisted message by its stable UI id. */ get(peer: string, id: string): ChatMessage | undefined; /** Remove messages by id. Returns the removed ones so the caller can clean up * any on-disk file the chip pointed at. */ deleteMessages(peer: string, ids: string[]): ChatMessage[]; private push; /** * Return history. With no peer, returns every peer's full thread (the legacy * chat-history shape). With a peer, supports pagination: `limit` newest * messages, optionally those strictly older than `before` (ms) for "load * earlier" scrolling. */ history(peer?: string, opts?: { before?: number; limit?: number; }): Record; /** Most recent message per peer — for the friend-list preview/sort. */ lastMessages(): Map; /** Count messages newer than `sinceTs` for a peer (unread badge). */ unreadCount(peer: string, sinceTs: number): number; removePeer(peer: string): void; private scheduleSave; /** Force a synchronous flush (called on debounce + on daemon shutdown). */ flush(): void; }