From 5bb8034e2cc8db39f8cc0773a2fcc2f1fc3b1709 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:38:46 +0000 Subject: [PATCH] Fix session expiration issues - Fix anonymous user token validation boolean logic - Add background token refresh mechanism for idle sessions - Improve WebSocket token refresh error handling - Ensure robust session persistence for long-running sessions Co-Authored-By: nate@buster.so --- .../Supabase/SupabaseContextProvider.tsx | 23 ++++++++++++++++++- .../src/hooks/useWebSocket/useWebSocket.ts | 19 ++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx index 24cfb4d86..4f0948cdd 100644 --- a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx +++ b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx @@ -20,6 +20,7 @@ const useSupabaseContextInternal = ({ supabaseContext: UseSupabaseUserContextType; }) => { const refreshTimerRef = useRef | undefined>(undefined); + const backgroundRefreshRef = useRef | undefined>(undefined); const { openErrorNotification, openInfoMessage } = useBusterNotifications(); const [accessToken, setAccessToken] = useState(supabaseContext.accessToken || ''); @@ -42,7 +43,7 @@ const useSupabaseContextInternal = ({ if (isAnonymousUser) { return { access_token: accessToken, - isTokenValid: isTokenExpired + isTokenValid: !isTokenExpired }; } @@ -122,6 +123,26 @@ const useSupabaseContextInternal = ({ }; }, [accessToken, checkTokenValidity]); + useEffect(() => { + if (isAnonymousUser) return; + + const BACKGROUND_CHECK_INTERVAL = 4 * 60 * 1000; + + backgroundRefreshRef.current = setInterval(async () => { + try { + await checkTokenValidity(); + } catch (error) { + console.error('Background token refresh failed:', error); + } + }, BACKGROUND_CHECK_INTERVAL); + + return () => { + if (backgroundRefreshRef.current) { + clearInterval(backgroundRefreshRef.current); + } + }; + }, [isAnonymousUser, checkTokenValidity]); + return { isAnonymousUser, setAccessToken, diff --git a/apps/web/src/hooks/useWebSocket/useWebSocket.ts b/apps/web/src/hooks/useWebSocket/useWebSocket.ts index 20cd72bd7..6fd0483f0 100644 --- a/apps/web/src/hooks/useWebSocket/useWebSocket.ts +++ b/apps/web/src/hooks/useWebSocket/useWebSocket.ts @@ -134,7 +134,16 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generic data type for WebSocket messages const sendJSONMessage = useMemoizedFn(async (data: Record, isFromQueue = false) => { - await checkTokenValidity(); //needed! This will refresh the token if it is expired. All other messages will be queued until the token is refreshed. + try { + await checkTokenValidity(); //needed! This will refresh the token if it is expired. All other messages will be queued until the token is refreshed. + } catch (error) { + console.error('Token validation failed before sending WebSocket message:', error); + if (!isFromQueue) { + sendQueue.current.push(data); // Queue the message for retry + } + return; + } + if (ws.current?.readyState === ReadyState.Closed) { connectWebSocket(); } @@ -165,7 +174,11 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc // Use fetch to check connection first and get headers checkTokenValidity() .then(({ access_token, isTokenValid }) => { - if (!isTokenValid) return; + if (!isTokenValid) { + console.warn('Token is invalid, skipping WebSocket connection'); + setConnectionError('Invalid authentication token'); + return; + } // If fetch succeeds, establish WebSocket connection const socketURLWithAuth = `${url}?authentication=${access_token}`; ws.current = new WebSocket(socketURLWithAuth); @@ -173,7 +186,7 @@ const useWebSocket = ({ url, checkTokenValidity, canConnect, onMessage }: WebSoc }) .catch((error) => { console.error('Connection error:', error); - setConnectionError(error.message); + setConnectionError(error.message || 'Authentication failed'); }); }), { wait: BASE_DELAY }