import { backendErrorText, publicErrorDetails } from "./public-error"; export const NATIVE_EMAIL_WAIT_MS = 10_000; const pending = new Set(["pending", "syncing", "synced", "sending"]); type Receipt = Record; /** Observe one durable receipt. Never resubmit a send after an uncertain read. */ export async function waitForNativeEmail( initial: unknown, read: (draftId: string, signal: AbortSignal) => Promise, waitMs = NATIVE_EMAIL_WAIT_MS, ): Promise { if (!Number.isFinite(waitMs) || waitMs < 0 || waitMs > 60_000) throw new Error( "Email receipt wait must be between 0 and 60000 milliseconds", ); if (waitMs === 0 || !initial || typeof initial !== "object") return initial; let receipt = initial as Receipt; const draftId = receipt.draftId; if (typeof draftId !== "string" || !pending.has(String(receipt.syncStatus))) return initial; const controller = new AbortController(); let timer: ReturnType | undefined; const deadline = new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(new Error("Receipt observation timed out")); }, waitMs); }); try { return await Promise.race([ (async () => { while (!controller.signal.aborted) { const next = await read(draftId, controller.signal); if ( !next || typeof next !== "object" || (next as Receipt).draftId !== draftId || typeof (next as Receipt).syncStatus !== "string" ) { throw new Error("Invalid email receipt response"); } receipt = next as Receipt; if (!pending.has(String(receipt.syncStatus))) return receipt; await new Promise((resolve) => { const pollTimer = setTimeout(done, 500); function done() { clearTimeout(pollTimer); controller.signal.removeEventListener("abort", done); resolve(); } controller.signal.addEventListener("abort", done, { once: true }); }); } return receipt; })(), deadline, ]); } catch (error) { return { ...receipt, observation: { status: controller.signal.aborted ? "timed_out" : "unavailable", ...publicErrorDetails(error), message: controller.signal.aborted ? "Still awaiting the send result." : backendErrorText(error), nextAction: `dench email draft show ${draftId} --json`, }, }; } finally { if (timer !== undefined) clearTimeout(timer); controller.abort(); } }