Files
life-echo/app-expo/src/core/providers.tsx

73 lines
2.3 KiB
TypeScript
Raw Normal View History

import React, { type PropsWithChildren } from 'react';
import { initApiClient } from '@/core/api/client';
import { AppSettingsProvider } from '@/core/app-settings-context';
import { MemoirReadingSettingsProvider } from '@/core/memoir-reading-settings-context';
import { NetworkError } from '@/core/api/types';
import { tokenManager } from '@/core/auth/token-manager';
import { config } from '@/core/config';
import { authKeys } from '@/features/auth/auth-query-keys';
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(authKeys.tokenCheck, false);
queryClient.setQueryData(authKeys.session, null);
tokenManager.clearTokens();
}
initApiClient({
getAccessToken: tokenManager.getAccessToken,
refreshTokens,
onAuthFailure,
});
export function AppProviders({ children }: PropsWithChildren) {
return (
<AppQueryProvider>
<AppSettingsProvider>
<MemoirReadingSettingsProvider>{children}</MemoirReadingSettingsProvider>
</AppSettingsProvider>
</AppQueryProvider>
);
}