import { WebViewFailedToLoadErrorPhase, WebViewLoadAttemptFailure } from '../WebViewFailedToLoadError'; /** * Lifecycle status of the webview load engine. * * `waiting_to_retry` means the current attempt failed transiently and the * next attempt is waiting out its backoff delay. */ export type WebViewLoadStatus = 'idle' | 'loading' | 'waiting_to_retry' | 'ready' | 'failed'; /** * The in-flight step of the current load attempt while `status` is * `loading`: * - `html_load`: navigation started (or requested), response not committed. * - `html_finish`: response committed, page not finished loading. * - `sdk_bootstrap`: page finished, webview-side SDK not yet ready. */ export type WebViewLoadPhase = 'html_load' | 'html_finish' | 'sdk_bootstrap'; /** * External dependencies injected into the machine. The machine has NO * ambient access to `Date` or `Math` — every non-deterministic input goes * through this config so transitions stay pure and testable. */ export type WebViewLoadConfig = { /** Budget (ms) for the html_load and html_finish phases. */ pageLoadTimeoutMs: number; /** * Fail-fast cap (ms) on the FIRST attempt's html_load window only; the * effective window is min(this, pageLoadTimeoutMs). Falls back to the * 10s default when absent. */ firstAttemptPageLoadTimeoutMs?: number; /** Budget (ms) for the sdk_bootstrap phase. */ sdkReadyTimeoutMs: number; /** Wall-clock source, e.g. `Date.now`. */ now: () => number; /** * Source of randomness for the retry-backoff jitter. Non-cryptographic * backoff jitter only (it spreads reload storms across devices and is * never used for anything security-relevant), e.g. `Math.random`. */ random: () => number; }; export type WebViewLoadState = { status: WebViewLoadStatus; phase: WebViewLoadPhase | null; /** 1-based counter of load attempts within the current load cycle. */ attempt: number; maxAttempts: number; /** Wall-clock timestamp the pending retry is due at, else `null`. */ nextRetryAt: number | null; /** One record per failed attempt within the current load cycle. */ failures: WebViewLoadAttemptFailure[]; /** Wall-clock start of the current attempt, for per-attempt elapsedMs. */ attemptStartedAt: number | null; /** * Whether the current attempt already consumed its one manifest-driven * recovery extension. Bounds the extension to once per attempt so * message spam can't push the sdk_bootstrap deadline forever. */ hasExtendedRecoveryThisAttempt: boolean; }; /** * Native `onLoadError` details forwarded by the shell. `code`/`domain` * feed the transient/permanent classification; the rest is diagnostics * attached to the terminal error meta. */ export type WebViewLoadNativeErrorEvent = { type: 'native_error'; code: number; domain: string; description?: string; failedUrl?: string; isProvisional?: boolean; }; export type WebViewLoadEvent = { type: 'load_requested'; } | { type: 'retry_requested'; } | { type: 'load_started'; } | { type: 'response_committed'; } | { type: 'page_finished'; } | { type: 'manifest_received'; } | { type: 'sdk_ready'; } | { type: 'timer_expired'; phase: WebViewLoadPhase; } | { type: 'backoff_elapsed'; } | { type: 'recovery_requested'; } | WebViewLoadNativeErrorEvent; /** * Native error diagnostics carried through to the terminal * `WebViewFailedToLoadError` meta. */ export type WebViewLoadNativeErrorMeta = Partial<{ nativeErrorCode: number; nativeErrorDescription: string; nativeErrorDomain: string; nativeErrorFailedUrl: string; nativeErrorIsProvisional: boolean; }>; /** * Side effects requested by a transition, executed by a shell. The shell * keeps ONE timer per phase; arming a phase replaces that phase's timer. * `clear_timers` clears all phase timers plus the backoff timer. */ export type WebViewLoadEffect = { type: 'load_url'; retryCount: number; } | { type: 'arm_timer'; phase: WebViewLoadPhase; ms: number; } | { type: 'clear_timers'; } | { type: 'schedule_backoff'; delayMs: number; } | { type: 'block_transport'; } | { type: 'probe_origin'; attempt: number; phase: WebViewFailedToLoadErrorPhase; } | { type: 'emit_telemetry'; event: 'load_succeeded'; } | { type: 'emit_telemetry'; event: 'load_error'; attempt: number; phase: WebViewFailedToLoadErrorPhase; } | { type: 'emit_telemetry'; event: 'load_retry'; attempt: number; delayMs: number; maxAttempts: number; phase: WebViewFailedToLoadErrorPhase; } | { type: 'raise_terminal_error'; phase: WebViewFailedToLoadErrorPhase; failures: WebViewLoadAttemptFailure[]; extraMeta: WebViewLoadNativeErrorMeta; } | { type: 'clear_error'; }; export type WebViewLoadTransitionResult = { state: WebViewLoadState; effects: WebViewLoadEffect[]; };