/** * Fetch-based SSE subscription primitive. * * Why not `EventSource`: cross-origin embedders authenticate via the * `embedAuthedFetch` adapter's HEADERS (OpenFrame bearer), which * `EventSource` cannot carry. This reads `response.body` from a normal * authed fetch and parses standard `event:`/`data:` frames. * * Lifecycle (never-terminating by design): * - Infinite reconnects with capped exponential backoff (~30s max + * jitter), reset on a successful open. Do NOT copy the terminating * `maxRetries → exhausted` model of the hub's server-side * `RealtimeRetryManager` — a long outage must self-heal when the * backend returns, because there is NO polling fallback behind this. * - Liveness by silence: the server keepalives every ~15s; any bytes * (INCLUDING `: keepalive` comment lines — they reset the timer * before frame parsing drops them) count as life. Silence beyond * `silenceTimeoutMs` (default 45s = 3× keepalive) aborts + reconnects. * - Terminal responses (NO retry): any 4xx except 408/429 — the * `x-block-layer` header, when readable, is logged for attribution * only, never used as a retry gate (proxies may strip it); and * 204 → a distinct `no-stream` status (the caller decides when to * try again). Backoff-retry is for transport errors, 408/429, and 5xx. * * THE common SSE client for lib + hosts — exported from the `utils` * barrel. Consumers: `TicketLiveProvider` (lib) and the hub's * workflow/invocation stream hooks (which replaced the hub's old * EventSource-based `sse-client.ts`). Finite streams (workflows, * invocations) end with a terminal event and a server-side close — * their handlers MUST call `close()` on the terminal event, or the * never-terminating reconnect loop re-opens the finished stream. */ /** Transport-level status. `suspended` = paused by `pauseWhenHidden` * after the hidden grace elapsed (resumes automatically on visible). */ export type SseTransportStatus = 'connecting' | 'open' | 'reconnecting' | 'no-stream' | 'terminal' | 'suspended' | 'closed'; export interface SseSubscriptionOptions { url: string; /** Defaults to `embedAuthedFetch` (adapter headers + credentials). */ fetchImpl?: (url: string, init?: RequestInit) => Promise; /** Called per parsed frame: `eventName` from `event:` (default * 'message'), `data` JSON-parsed when possible (raw string otherwise). * Server `status` frames are ALSO forwarded here (after the client's * own lifecycle handling) for consumers that render health detail. */ onEvent: (eventName: string, data: unknown) => void; onStatusChange?: (status: SseTransportStatus) => void; /** * Consolidated liveness signal — true only when the SERVER confirmed * its Realtime subscription (`status: subscribed` frame), never merely * "HTTP open". THE CLIENT owns the whole lifecycle policy: * - transport open without `subscribed` within 15s → hard reconnect; * - server `retrying` → connected=false, give the server 90s to * recover before a client hard reconnect; * - server `reconnect_failed` → connected=false, hard reconnect at * the capped 30s delay (no stampedes); * - any transport drop → connected=false (reconnect via backoff). * Fired only on transitions. */ onConnectedChange?: (connected: boolean) => void; /** * Pause the subscription while the tab is hidden (45s grace so * Cmd-Tab thrash doesn't churn connections), resume + reconnect on * visible, and reconnect on `online`. OPT-IN — short-lived streams * (workflow/invocation monitors) must keep running in background * tabs. Long-lived per-user streams should enable it (scale relief: * a hidden tab holds no server invocation). */ pauseWhenHidden?: boolean; /** Silence threshold before the connection is presumed dead. MUST be * ≥2.5× the server keepalive cadence or jitter causes false-disconnect * churn (each one costs a full server invocation). */ silenceTimeoutMs?: number; maxBackoffMs?: number; initialBackoffMs?: number; } export interface SseSubscription { /** Permanently stop — no further reconnects, no callbacks. */ close: () => void; /** Drop the current connection (if any) and reconnect immediately, * resetting backoff. No-op after `close()`. */ reconnectNow: () => void; } export declare function createSseSubscription(options: SseSubscriptionOptions): SseSubscription; //# sourceMappingURL=sse-subscription.d.ts.map