Skip to content

Commit

Permalink
fix(services): fix running status on first deployment (#1381)
Browse files Browse the repository at this point in the history
During the first deployment, the websocket can timeout because the
websocket isn't completely setup.
We must auto-reconnect till connection is setup.
  • Loading branch information
ctjhoa authored May 29, 2024
1 parent 0f481a3 commit b334f88
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function useStatusWebSockets({
},
// NOTE: projectId is not required by the API but it limits WS messages when cluster handles my environments / services
enabled: Boolean(organizationId) && Boolean(clusterId) && Boolean(projectId),
shouldReconnect: true,
onMessage(queryClient, message: ServiceStatusDto) {
for (const env of message.environments) {
queryClient.setQueryData(queries.environments.runningStatus(env.id).queryKey, () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuth0 } from '@auth0/auth0-react'
import { type QueryClient, useQueryClient } from '@tanstack/react-query'
import { useEffect } from 'react'
import { useEffect, useRef } from 'react'

// Inspired by:
// https://tkdodo.eu/blog/using-web-sockets-with-react-query
Expand All @@ -17,6 +17,7 @@ export interface UseReactQueryWsSubscriptionProps {
onError?: (queryClient: QueryClient, event: Event) => void
onClose?: (QueryClient: QueryClient, event: CloseEvent) => void
enabled?: boolean
shouldReconnect?: boolean
}

interface InvalidateOperation {
Expand All @@ -38,6 +39,7 @@ export function useReactQueryWsSubscription({
onError,
onClose,
enabled = true,
shouldReconnect = false,
}: UseReactQueryWsSubscriptionProps) {
const queryClient = useQueryClient()
const { getAccessTokenSilently } = useAuth0()
Expand All @@ -57,14 +59,16 @@ export function useReactQueryWsSubscription({
}

const searchParams = new URLSearchParams(_urlSearchParams)
const reconnectCount = useRef<number>(0)

useEffect(() => {
if (!enabled) {
return
}
let websocket: WebSocket | undefined
let timeout: ReturnType<typeof setTimeout> | undefined

async function initWebsocket() {
async function connect() {
const token = await getAccessTokenSilently()
websocket = new WebSocket(`${url}?${searchParams.toString()}`, ['v1', 'auth.bearer.' + token])

Expand All @@ -86,15 +90,31 @@ export function useReactQueryWsSubscription({
onError?.(queryClient, event)
}
websocket.onclose = async (event) => {
onClose?.(queryClient, event)
if (shouldReconnect) {
timeout = setTimeout(
function () {
reconnectCount.current++
connect()
},
// Exponential Backoff
// attemptNumber will be 0 the first time it attempts to reconnect, so this equation results in a reconnect pattern of 5 second, 10 seconds, 20 seconds, 40 seconds, 80 seconds, and then caps at 100 seconds until the maximum number of attempts is reached
Math.min(Math.pow(2, reconnectCount.current) * 5000, 100_000)
)
} else {
onClose?.(queryClient, event)
}
}
}

initWebsocket()
connect()

return () => {
if (websocket) {
shouldReconnect = false
websocket.close()
if (timeout) {
clearTimeout(timeout)
}
}
}
}, [queryClient, getAccessTokenSilently, onOpen, onMessage, onClose, url, searchParams.toString(), enabled])
Expand Down

0 comments on commit b334f88

Please sign in to comment.