import type { WorkspaceEventPayload, WorkspaceEventType } from "../../../server/src/api-types" import { serverApi } from "./api-client" import { getLogger } from "./logger" const RETRY_BASE_DELAY = 1000 const RETRY_MAX_DELAY = 10000 const log = getLogger("sse") function logSse(message: string, context?: Record) { if (context) { log.info(message, context) return } log.info(message) } class ServerEvents { private handlers = new Map void>>() private source: EventSource | null = null private retryDelay = RETRY_BASE_DELAY constructor() { this.connect() } private connect() { if (this.source) { this.source.close() } logSse("Connecting to backend events stream") this.source = serverApi.connectEvents((event) => this.dispatch(event), () => this.scheduleReconnect()) this.source.onopen = () => { logSse("Events stream connected") this.retryDelay = RETRY_BASE_DELAY } } private scheduleReconnect() { if (this.source) { this.source.close() this.source = null } logSse("Events stream disconnected, scheduling reconnect", { delayMs: this.retryDelay }) setTimeout(() => { this.retryDelay = Math.min(this.retryDelay * 2, RETRY_MAX_DELAY) this.connect() }, this.retryDelay) } private dispatch(event: WorkspaceEventPayload) { logSse(`event ${event.type}`) this.handlers.get("*")?.forEach((handler) => handler(event)) this.handlers.get(event.type)?.forEach((handler) => handler(event)) } on(type: WorkspaceEventType | "*", handler: (event: WorkspaceEventPayload) => void): () => void { if (!this.handlers.has(type)) { this.handlers.set(type, new Set()) } const bucket = this.handlers.get(type)! bucket.add(handler) return () => bucket.delete(handler) } } export const serverEvents = new ServerEvents()