import { useAppStore } from "../store/useAppStore"; import { markSessionRead } from "./api"; /** * Session read cursor (Android 协同 B3/D4) — the consumer side of the per-session * unread count. Two write paths, both landing on the same single cursor: * * - opening a session (this end) → POST /sessions/{id}/read * - the `session_read` frame (other ends) → clears the badge locally only * * The store's `clearUnread` zeroes the badge; the network cannot fail the badge, * because `GET /sessions` is the eventual truth (a failed POST simply means the * dot comes back on the next list load). */ /** Which session does a `session_read` frame address? The payload's own * `sessionId` wins when present (a frame may scope itself in the body, like the * streaming frames), else the envelope's. "" means unattributable: the caller * must then leave every badge alone rather than guess, since clearing the wrong * session's badge would hide messages the user has not seen. */ export function sessionReadTarget( envelopeSessionId: string | undefined, r: Record | null ): string { const payload = typeof r?.sessionId === "string" ? r.sessionId : ""; return payload || envelopeSessionId || ""; } /** Mark a session read because the user just opened it (called once its first * page of history has landed, so the cursor never passes messages that were * never rendered). Zeroes the badge immediately — the list must not show a stale * dot for the conversation on screen — and pushes the cursor so every other end * converges. A session this end already reads as read is skipped entirely: the * WS frame (or this very call) may have cleared it first, and switching back and * forth must not fire a request per render. */ export function markSessionOpenedRead(sessionId: string): void { if (!sessionId) return; const { sessions, clearUnread } = useAppStore.getState(); if (!(sessions[sessionId]?.unreadCount ?? 0)) return; clearUnread(sessionId); void markSessionRead(sessionId).catch(() => { // Best effort: the server cursor stays where it was until the next attempt, // and the next GET /sessions reports it as truth. }); }