// A single monotonic counter of JMAP TRANSPORT failures. // // WHY THIS EXISTS. The offline replica is a read-path FALLBACK, and to be one it // has to know that a read genuinely failed. `lib/jmap/client.ts` makes that // impossible to see from the outside: its read methods swallow their own errors // and return plausible-looking success. `getEmails()` returns // `{ emails: [], hasMore: false, total: 0 }`, so a dead network is // indistinguishable from an empty folder. `getEmail()` returns `null`. // `getMailboxes()` returns a SYNTHETIC single Inbox. Falling back on those shapes // alone would mean serving stale replica rows for a folder the user had genuinely // just emptied. // // So `authenticatedFetch` bumps this counter when, and only when, `fetch` itself // rejects - not on a 4xx, not on a 429 (that is a rate limit, and the server is // plainly reachable), not on a JMAP method error. The fallback layer samples the // counter before and after a call: a suspicious result PLUS an increment during // that exact call is a transport failure. Either signal alone is not enough. // // Module-level rather than per-client on purpose: it answers "is the network // working right now", which is a property of the machine, not of one account's // client instance. let failures = 0; let lastFailureAt = 0; let lastSuccessAt = 0; /** Called only when `fetch` itself rejects. Never for an HTTP status. */ export function noteTransportFailure(): void { failures++; lastFailureAt = Date.now(); } export function noteTransportSuccess(): void { lastSuccessAt = Date.now(); } /** Monotonic. Sample before and after a call to attribute a failure to it. */ export function transportFailureCount(): number { return failures; } export function transportHealth(): { failures: number; lastFailureAt: number; lastSuccessAt: number; /** Best-effort "probably offline": a failure more recent than any success. */ likelyOffline: boolean; } { return { failures, lastFailureAt, lastSuccessAt, likelyOffline: lastFailureAt > lastSuccessAt, }; } /** Test-only reset. */ export function resetTransportHealth(): void { failures = 0; lastFailureAt = 0; lastSuccessAt = 0; }