From 6658c0b15a0e20465e41e39e0bf6b0650098aa78 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Sun, 16 Nov 2025 10:56:09 +0000 Subject: [PATCH] add retry loop for local sse reconnection --- src/lib/sse-manager.ts | 52 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/lib/sse-manager.ts b/src/lib/sse-manager.ts index 8de1ed1d..e5e92a63 100644 --- a/src/lib/sse-manager.ts +++ b/src/lib/sse-manager.ts @@ -16,8 +16,11 @@ import type { interface SSEConnection { instanceId: string + port: number eventSource: EventSource status: "connecting" | "connected" | "disconnected" | "error" + reconnectAttempts: number + reconnectTimer?: ReturnType } interface TuiToastEvent { @@ -50,10 +53,13 @@ const [connectionStatus, setConnectionStatus] = createSignal< class SSEManager { private connections = new Map() + private static readonly MAX_RECONNECT_ATTEMPTS = 3 - connect(instanceId: string, port: number): void { - if (this.connections.has(instanceId)) { - this.disconnect(instanceId) + connect(instanceId: string, port: number, reconnectAttempts = 0): void { + const existing = this.connections.get(instanceId) + if (existing) { + this.clearReconnectTimer(existing) + existing.eventSource.close() } const url = `http://localhost:${port}/event` @@ -61,8 +67,10 @@ class SSEManager { const connection: SSEConnection = { instanceId, + port, eventSource, status: "connecting", + reconnectAttempts, } this.connections.set(instanceId, connection) @@ -70,6 +78,7 @@ class SSEManager { eventSource.onopen = () => { connection.status = "connected" + connection.reconnectAttempts = 0 this.updateConnectionStatus(instanceId, "connected") console.log(`[SSE] Connected to instance ${instanceId}`) } @@ -87,13 +96,14 @@ class SSEManager { connection.status = "error" this.updateConnectionStatus(instanceId, "error") console.error(`[SSE] Connection error for instance ${instanceId}`) - this.handleConnectionLost(instanceId, "Connection to instance lost") + this.handleConnectionError(instanceId, "Connection to instance lost") } } disconnect(instanceId: string): void { const connection = this.connections.get(instanceId) if (connection) { + this.clearReconnectTimer(connection) connection.eventSource.close() this.connections.delete(instanceId) this.updateConnectionStatus(instanceId, "disconnected") @@ -143,10 +153,37 @@ class SSEManager { } } + private handleConnectionError(instanceId: string, reason: string): void { + const connection = this.connections.get(instanceId) + if (!connection) return + + connection.eventSource.close() + + if (connection.reconnectAttempts >= SSEManager.MAX_RECONNECT_ATTEMPTS) { + this.handleConnectionLost(instanceId, reason) + return + } + + const nextAttempt = connection.reconnectAttempts + 1 + const delay = Math.min(nextAttempt * 1000, 5000) + + connection.reconnectAttempts = nextAttempt + connection.status = "connecting" + this.updateConnectionStatus(instanceId, "connecting") + + console.warn(`[SSE] Attempting reconnect ${nextAttempt} for instance ${instanceId}`) + + connection.reconnectTimer = setTimeout(() => { + connection.reconnectTimer = undefined + this.connect(instanceId, connection.port, nextAttempt) + }, delay) + } + private handleConnectionLost(instanceId: string, reason: string): void { const connection = this.connections.get(instanceId) if (!connection) return + this.clearReconnectTimer(connection) connection.eventSource.close() this.connections.delete(instanceId) connection.status = "disconnected" @@ -154,6 +191,13 @@ class SSEManager { this.onConnectionLost?.(instanceId, reason) } + private clearReconnectTimer(connection: SSEConnection): void { + if (connection.reconnectTimer) { + clearTimeout(connection.reconnectTimer) + connection.reconnectTimer = undefined + } + } + private updateConnectionStatus(instanceId: string, status: SSEConnection["status"]): void { setConnectionStatus((prev) => { const next = new Map(prev)