import React, { type PropsWithChildren } from 'react'; import { initApiClient } from '@/core/api/client'; import { AppSettingsProvider } from '@/core/app-settings-context'; import { NetworkError } from '@/core/api/types'; import { tokenManager } from '@/core/auth/token-manager'; import { config } from '@/core/config'; import { AppQueryProvider, queryClient } from '@/core/query'; /** * Returns false only when the refresh token is genuinely rejected * (no token stored, or server returned non-2xx). * Throws NetworkError on transport-level failures so the caller * can distinguish "session dead" from "network down". */ async function refreshTokens(): Promise { const refreshToken = await tokenManager.getRefreshToken(); if (!refreshToken) return false; let res: Response; try { res = await fetch(`${config.apiBaseUrl}${config.api.refreshPath}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }), }); } catch (err) { throw new NetworkError( err instanceof Error ? err.message : 'Token refresh network failure', err, ); } if (!res.ok) return false; const data = (await res.json()) as { access_token: string; refresh_token: string; }; await tokenManager.setTokens(data.access_token, data.refresh_token); return true; } /** * Called by the API client when token refresh is explicitly rejected. * Must synchronously flip query caches so useSession() immediately * sees unauthenticated — tokenManager.clearTokens() is fire-and-forget * since the cache flip is the authoritative signal. */ function onAuthFailure() { queryClient.setQueryData(['auth', 'token-check'], false); queryClient.setQueryData(['session'], null); tokenManager.clearTokens(); } initApiClient({ getAccessToken: tokenManager.getAccessToken, refreshTokens, onAuthFailure, }); export function AppProviders({ children }: PropsWithChildren) { return ( {children} ); }