69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
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<boolean> {
|
|
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 (
|
|
<AppQueryProvider>
|
|
<AppSettingsProvider>{children}</AppSettingsProvider>
|
|
</AppQueryProvider>
|
|
);
|
|
}
|