- Copy complete source code packages from original CodeNomad project - Add root package.json with npm workspace configuration - Include electron-app, server, ui, tauri-app, and opencode-config packages - Fix Launch-Windows.bat and Launch-Dev-Windows.bat to work with correct npm scripts - Fix Launch-Unix.sh to work with correct npm scripts - Launchers now correctly call npm run dev:electron which launches Electron app
67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
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<string, unknown>) {
|
|
if (context) {
|
|
log.info(message, context)
|
|
return
|
|
}
|
|
log.info(message)
|
|
}
|
|
|
|
class ServerEvents {
|
|
private handlers = new Map<WorkspaceEventType | "*", Set<(event: WorkspaceEventPayload) => 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()
|