import { useEffect, useState } from "react"; import { writeThroughViewer } from "../../../utils/writeThroughHold"; // A fax setup write that left for the server and never came back with an answer may have landed. It is held for the // person and practice that sent it, across a practice switch, reopening the page and a reload of the tab: none of // those says whether it landed. An account is settled only by a complete account saved and confirmed (no read says // which keys are stored); receiving only by a read that can prove what is set up, which the service list cannot. export interface ReceivingAttempt { serviceId: string; faxNumber: string; /** When it was handed to the network. */ sentAt: number; } export interface UnresolvedFaxSetup { /** An account save: unconfirmed, or being entered again in full to settle it. */ account?: "unconfirmed" | "recovering"; /** A receiving enable. */ receiving?: ReceivingAttempt; } const STORAGE_KEY = "akashic.faxSetupUnresolved"; const held = new Map(); const listeners = new Set<() => void>(); const persist = () => { try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify([...held.entries()])); } catch { // Storage refused: the hold still stands for as long as the app runs. } }; const changed = () => { persist(); listeners.forEach((listener) => listener()); }; /** Reads the holds this tab kept, as the app does when it starts. */ export const restoreUnresolvedFaxSetup = (): void => { held.clear(); try { const kept = JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? "[]") as [string, UnresolvedFaxSetup][]; if (Array.isArray(kept)) kept.forEach(([holder, setup]) => { if (typeof holder === "string" && setup) held.set(holder, setup); }); } catch { // Nothing readable was kept. } listeners.forEach((listener) => listener()); }; restoreUnresolvedFaxSetup(); /** Whose fax setup a write is: the person signed in and the practice, read when the write starts. */ export const faxSetupHolder = (practiceId: string | undefined): string => `${writeThroughViewer() ?? ""}|${practiceId ?? ""}`; export const unresolvedFaxSetup = (holder: string): UnresolvedFaxSetup => held.get(holder) ?? {}; export const holdFaxSetup = (holder: string, change: UnresolvedFaxSetup): void => { const next = { ...held.get(holder), ...change }; if (next.account || next.receiving) held.set(holder, next); else held.delete(holder); changed(); }; /** A fresh tab, for a test. */ export const forgetUnresolvedFaxSetup = (): void => { held.clear(); changed(); }; export const useUnresolvedFaxSetup = (holder: string): UnresolvedFaxSetup => { const [, rerender] = useState(0); useEffect(() => { const listener = () => rerender((n) => n + 1); listeners.add(listener); return () => { listeners.delete(listener); }; }, []); return unresolvedFaxSetup(holder); }; /** * One fax setup write, watched for whether it left and how it settled. Unconfirmed: it was handed to the network and * neither an answer nor a refusal (a 4xx, which writes nothing) came back - a 5xx, a lost answer, a cancel or a * deadline after it left all may have landed. */ export const watchFaxWrite = (write: (onSend: () => void) => Promise) => { let sentAt: number | undefined; let settled = false; return { run: () => write(() => { sentAt = Date.now(); }).then( (answer) => { settled = true; return answer; }, (error: unknown) => { const status = (error as { status?: unknown } | null)?.status; if (typeof status === "number" && status < 500) settled = true; throw error; }, ), unconfirmed: () => sentAt !== undefined && !settled, sentAt: () => sentAt ?? Date.now(), }; };