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 <nate@buster.so>
This commit is contained in:
Devin AI 2025-08-11 21:38:46 +00:00
parent a369ba2b32
commit 5bb8034e2c
2 changed files with 38 additions and 4 deletions

View File

@ -20,6 +20,7 @@ const useSupabaseContextInternal = ({
supabaseContext: UseSupabaseUserContextType;
}) => {
const refreshTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const backgroundRefreshRef = useRef<ReturnType<typeof setInterval> | 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,

View File

@ -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<string, any>, 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 }