Files
frontend/src/shared/api/client.ts
T

91 lines
2.3 KiB
TypeScript

import createClient from "openapi-fetch";
import createQueryClient from "openapi-react-query";
import type { paths } from "./generated/schema";
const HTTP_UNAUTHORIZED = 401;
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:3000";
type AuthHandlers = {
accessToken: () => string | null;
refresh: () => Promise<boolean>;
clear: () => void;
};
let auth: AuthHandlers | null = null;
let hasAuthenticatedSession = false;
const authSessionListeners = new Set<() => void>();
function setAuthenticatedSession(next: boolean) {
if (next === hasAuthenticatedSession) return;
hasAuthenticatedSession = next;
for (const listener of authSessionListeners) listener();
}
function configureAuthClient(handlers: AuthHandlers | null) {
auth = handlers;
setAuthenticatedSession(Boolean(auth?.accessToken()));
}
function getAuthenticatedSessionSnapshot() {
return hasAuthenticatedSession;
}
function subscribeAuthenticatedSession(listener: () => void) {
authSessionListeners.add(listener);
return () => authSessionListeners.delete(listener);
}
async function authenticatedFetch(request: Request): Promise<Response> {
const isAuthRequest = new URL(request.url).pathname.startsWith(
"/api/v1/auth/",
);
const token = auth?.accessToken();
const withToken = (source: Request, value: string) => {
const headers = new Headers(source.headers);
headers.set("Authorization", `Bearer ${value}`);
return new Request(source, { headers });
};
const authorized =
token && !isAuthRequest ? withToken(request, token) : request;
const retryRequest = authorized.clone();
const response = await fetch(authorized);
if (
response.status !== HTTP_UNAUTHORIZED ||
isAuthRequest ||
!auth ||
(
await response
.clone()
.json()
.catch(() => null)
)?.code !== "UNAUTHORIZED"
)
return response;
if (!(await auth.refresh())) {
auth.clear();
return response;
}
const refreshedToken = auth.accessToken();
return refreshedToken
? fetch(withToken(retryRequest, refreshedToken))
: response;
}
const apiClient = createClient<paths>({
baseUrl: apiBaseUrl,
credentials: "include",
fetch: authenticatedFetch,
});
const apiQueryClient = createQueryClient(apiClient);
export {
apiClient,
apiQueryClient,
configureAuthClient,
getAuthenticatedSessionSnapshot,
subscribeAuthenticatedSession,
};