Squash merge feat/expo-app: app-expo, .cursor, workflows, package.json, .husky; remove app-android, app-ios, react-app
This commit is contained in:
68
app-expo/src/core/providers.tsx
Normal file
68
app-expo/src/core/providers.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user