import { useSyncExternalStore } from "react"; import { ApiError, post } from "./api"; /** Messages written without a connection: kept on this device and sent when it is back. */ export interface OutboxItem { id: string; sessionId: string; text: string; effort?: string; createdAt: number; } const KEY = "codecraft.outbox"; const listeners = new Set<() => void>(); let cache: OutboxItem[] | undefined; function load(): OutboxItem[] { if (cache) return cache; try { const v = JSON.parse(localStorage.getItem(KEY) ?? "[]"); cache = Array.isArray(v) ? v : []; } catch { cache = []; } return cache!; } function save(items: OutboxItem[]) { cache = items; try { localStorage.setItem(KEY, JSON.stringify(items)); } catch { // Storage blocked: the list still works until the page is closed. } listeners.forEach((l) => l()); } export function addToOutbox(sessionId: string, text: string, effort?: string) { save([...load(), { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, sessionId, text, effort, createdAt: Date.now() }]); } export function removeFromOutbox(id: string) { save(load().filter((i) => i.id !== id)); } export function useOutbox(sessionId: string): OutboxItem[] { const all = useSyncExternalStore( (l) => { listeners.add(l); return () => listeners.delete(l); }, () => load(), ); return all.filter((i) => i.sessionId === sessionId); } /** True for failures of the connection itself (not answers of the server). */ export function isOffline(e: unknown) { return !(e instanceof ApiError) && (!navigator.onLine || e instanceof TypeError); } let flushing = false; /** Send the waiting messages of a chat in order; stops at the first connection failure. Returns how many were sent. */ export async function flushOutbox(sessionId: string): Promise { if (flushing) return 0; flushing = true; let sent = 0; try { for (const item of load().filter((i) => i.sessionId === sessionId)) { try { await post(`/sessions/${item.sessionId}/prompt`, { text: item.text, effort: item.effort }); removeFromOutbox(item.id); sent++; } catch (e) { if (isOffline(e)) break; // The server refused it (deleted chat, read-only …): it would never go through. removeFromOutbox(item.id); } } } finally { flushing = false; } return sent; }