Two bugs caused users to be redirected to /auth too frequently: 1. Race condition in Promise.all([initInstance(), initAuth()]): initInstance() makes a gRPC request whose auth interceptor calls getAccessToken() synchronously. When the access token was expired, getAccessToken() eagerly deleted it from localStorage as a "cleanup" side-effect. By the time initAuth() ran and checked hasStoredToken(), localStorage was already empty, so it skipped the getCurrentUser() call and the token refresh cycle entirely — logging the user out even when the refresh-token cookie was still valid. Fix: remove the localStorage deletion from getAccessToken(); clearAccessToken() (called on confirmed auth failure and logout) handles proper cleanup. 2. React Query retry: 1 caused a second refresh+redirect attempt after auth failures. The auth interceptor already handles token refresh and request retry internally. If it still throws Unauthenticated, the redirect is already in flight — a React Query retry only fires another failed refresh and a redundant redirectOnAuthFailure() call. Fix: use a shouldRetry function that skips retries for Unauthenticated errors while keeping the existing once-retry behaviour for other errors.
29 lines
1.3 KiB
TypeScript
29 lines
1.3 KiB
TypeScript
import { Code, ConnectError } from "@connectrpc/connect";
|
|
import { QueryClient } from "@tanstack/react-query";
|
|
|
|
// Don't retry requests that failed due to authentication errors.
|
|
// The auth interceptor in connect.ts already handles token refresh and request retry.
|
|
// If the interceptor still throws Unauthenticated, the session is truly gone and the
|
|
// user will be redirected to /auth. A React Query retry would only fire a second
|
|
// failed refresh attempt and a second redirect call while navigation is already in progress.
|
|
const shouldRetry = (failureCount: number, error: unknown): boolean => {
|
|
if (error instanceof ConnectError && error.code === Code.Unauthenticated) return false;
|
|
return failureCount < 1;
|
|
};
|
|
|
|
export const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
// Balanced approach: Fresh enough for collaboration, but reduces unnecessary refetches
|
|
// Individual queries can override with shorter staleTime if needed (e.g., notifications)
|
|
staleTime: 1000 * 30, // 30 seconds (increased from 10s for better performance)
|
|
gcTime: 1000 * 60 * 5, // 5 minutes (formerly cacheTime)
|
|
retry: shouldRetry,
|
|
refetchOnWindowFocus: true, // Refetch when user returns to tab
|
|
refetchOnReconnect: true, // Refetch when network reconnects
|
|
},
|
|
mutations: {
|
|
retry: shouldRetry,
|
|
},
|
|
},
|
|
});
|