Added websocket reconnect with backoff
This commit is contained in:
parent
64f2fa012e
commit
d19c50cf77
3 changed files with 107 additions and 18 deletions
3
site/src/stores/agentBackoff.ts
Normal file
3
site/src/stores/agentBackoff.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const BACKOFF_BASE_MS = 1000
|
||||
export const BACKOFF_MAX_MS = 30000
|
||||
export const BACKOFF_MAX_ATTEMPTS = 6
|
||||
|
|
@ -6,6 +6,7 @@ import type {
|
|||
AgentSocketEventPayload,
|
||||
AgentStartPayload,
|
||||
} from '../types/agent'
|
||||
import { BACKOFF_BASE_MS, BACKOFF_MAX_MS, BACKOFF_MAX_ATTEMPTS } from './agentBackoff'
|
||||
|
||||
export const useAgentStore = defineStore('agent', () => {
|
||||
const isConnected = ref(false)
|
||||
|
|
@ -14,6 +15,11 @@ export const useAgentStore = defineStore('agent', () => {
|
|||
const lastExecutionId = ref<string | null>(null)
|
||||
const socket = ref<WebSocket | null>(null)
|
||||
|
||||
let currentUrl = ''
|
||||
let reconnectAttempts = 0
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let intentionalClose = false
|
||||
|
||||
const pushEvent = (evt: AgentSocketEventPayload) => {
|
||||
eventLog.value.unshift({
|
||||
type: evt.type,
|
||||
|
|
@ -23,17 +29,32 @@ export const useAgentStore = defineStore('agent', () => {
|
|||
})
|
||||
}
|
||||
|
||||
const connect = (id: string) => {
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
const clearReconnectTimer = () => {
|
||||
if (reconnectTimer !== null) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
const wsUrl = `${wsProtocol}://${window.location.host}/ws/onboarding/chat/${id}/`
|
||||
socket.value = new WebSocket(wsUrl)
|
||||
const scheduleReconnect = () => {
|
||||
if (reconnectAttempts >= BACKOFF_MAX_ATTEMPTS) {
|
||||
pushEvent({ type: 'error', message: 'Connection lost. Please refresh the page.' })
|
||||
executionStatus.value = 'idle'
|
||||
return
|
||||
}
|
||||
const delay = Math.min(BACKOFF_BASE_MS * 2 ** reconnectAttempts, BACKOFF_MAX_MS)
|
||||
reconnectAttempts++
|
||||
pushEvent({ type: 'status', message: `Reconnecting in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempts}/${BACKOFF_MAX_ATTEMPTS})...` })
|
||||
reconnectTimer = setTimeout(() => {
|
||||
if (!intentionalClose) openSocket(currentUrl)
|
||||
}, delay)
|
||||
}
|
||||
|
||||
const openSocket = (url: string) => {
|
||||
socket.value = new WebSocket(url)
|
||||
|
||||
socket.value.onopen = () => {
|
||||
reconnectAttempts = 0
|
||||
isConnected.value = true
|
||||
pushEvent({ type: 'status', message: 'Connected to Orchestrator' })
|
||||
}
|
||||
|
|
@ -81,13 +102,35 @@ export const useAgentStore = defineStore('agent', () => {
|
|||
}
|
||||
}
|
||||
|
||||
socket.value.onclose = () => {
|
||||
socket.value.onclose = (event) => {
|
||||
isConnected.value = false
|
||||
executionStatus.value = 'idle'
|
||||
// code 1000 = clean close (server finished normally); don't reconnect
|
||||
if (!intentionalClose && event.code !== 1000) {
|
||||
scheduleReconnect()
|
||||
} else {
|
||||
executionStatus.value = 'idle'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const connect = (id: string) => {
|
||||
intentionalClose = false
|
||||
clearReconnectTimer()
|
||||
reconnectAttempts = 0
|
||||
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
currentUrl = `${wsProtocol}://${window.location.host}/ws/onboarding/chat/${id}/`
|
||||
openSocket(currentUrl)
|
||||
}
|
||||
|
||||
const disconnect = () => {
|
||||
intentionalClose = true
|
||||
clearReconnectTimer()
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type {
|
|||
AgentSocketEventPayload,
|
||||
AgentStartPayload,
|
||||
} from '../types/agent'
|
||||
import { BACKOFF_BASE_MS, BACKOFF_MAX_MS, BACKOFF_MAX_ATTEMPTS } from './agentBackoff'
|
||||
|
||||
export const useOnboardingAgentStore = defineStore('onboarding-agent', () => {
|
||||
const isConnected = ref(false)
|
||||
|
|
@ -14,6 +15,11 @@ export const useOnboardingAgentStore = defineStore('onboarding-agent', () => {
|
|||
const lastExecutionId = ref<string | null>(null)
|
||||
const socket = ref<WebSocket | null>(null)
|
||||
|
||||
let currentUrl = ''
|
||||
let reconnectAttempts = 0
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let intentionalClose = false
|
||||
|
||||
const pushEvent = (evt: AgentSocketEventPayload) => {
|
||||
eventLog.value.unshift({
|
||||
type: evt.type,
|
||||
|
|
@ -23,17 +29,32 @@ export const useOnboardingAgentStore = defineStore('onboarding-agent', () => {
|
|||
})
|
||||
}
|
||||
|
||||
const connect = (id: string) => {
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
const clearReconnectTimer = () => {
|
||||
if (reconnectTimer !== null) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
const wsUrl = `${wsProtocol}://${window.location.host}/ws/onboarding/generate/${id}/`
|
||||
socket.value = new WebSocket(wsUrl)
|
||||
const scheduleReconnect = () => {
|
||||
if (reconnectAttempts >= BACKOFF_MAX_ATTEMPTS) {
|
||||
pushEvent({ type: 'error', message: 'Connection lost. Please refresh the page.' })
|
||||
executionStatus.value = 'idle'
|
||||
return
|
||||
}
|
||||
const delay = Math.min(BACKOFF_BASE_MS * 2 ** reconnectAttempts, BACKOFF_MAX_MS)
|
||||
reconnectAttempts++
|
||||
pushEvent({ type: 'status', message: `Reconnecting in ${Math.round(delay / 1000)}s (attempt ${reconnectAttempts}/${BACKOFF_MAX_ATTEMPTS})...` })
|
||||
reconnectTimer = setTimeout(() => {
|
||||
if (!intentionalClose) openSocket(currentUrl)
|
||||
}, delay)
|
||||
}
|
||||
|
||||
const openSocket = (url: string) => {
|
||||
socket.value = new WebSocket(url)
|
||||
|
||||
socket.value.onopen = () => {
|
||||
reconnectAttempts = 0
|
||||
isConnected.value = true
|
||||
pushEvent({ type: 'status', message: 'Connected to Orchestrator' })
|
||||
}
|
||||
|
|
@ -81,13 +102,35 @@ export const useOnboardingAgentStore = defineStore('onboarding-agent', () => {
|
|||
}
|
||||
}
|
||||
|
||||
socket.value.onclose = () => {
|
||||
socket.value.onclose = (event) => {
|
||||
isConnected.value = false
|
||||
executionStatus.value = 'idle'
|
||||
// code 1000 = clean close (server finished normally); don't reconnect
|
||||
if (!intentionalClose && event.code !== 1000) {
|
||||
scheduleReconnect()
|
||||
} else {
|
||||
executionStatus.value = 'idle'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const connect = (id: string) => {
|
||||
intentionalClose = false
|
||||
clearReconnectTimer()
|
||||
reconnectAttempts = 0
|
||||
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
}
|
||||
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
currentUrl = `${wsProtocol}://${window.location.host}/ws/onboarding/generate/${id}/`
|
||||
openSocket(currentUrl)
|
||||
}
|
||||
|
||||
const disconnect = () => {
|
||||
intentionalClose = true
|
||||
clearReconnectTimer()
|
||||
if (socket.value) {
|
||||
socket.value.close()
|
||||
socket.value = null
|
||||
|
|
|
|||
Loading…
Reference in a new issue