import { fetchBaseQuery, type BaseQueryFn } from "@reduxjs/toolkit/query/react"; import type { FetchArgs, FetchBaseQueryError } from "@reduxjs/toolkit/query"; import { resolveSessionContext } from "../session"; /** * The one place the backend URL is resolved: VITE_BASE_API_URL from the env, * falling back to the local dev API (3001 — the portal itself owns 3000 for * the Fayda redirect). Import this; do not re-derive it. */ export const BASE_API_URL = (import.meta as { env?: Record }).env?.[ "VITE_BASE_API_URL" ]?.trim() || "http://localhost:3000/api"; let _onTokenExpired: (() => Promise) | null = null; let _onAuthFailure: (() => void) | null = null; export function configureTokenRefresh(config: { onTokenExpired: () => Promise; onAuthFailure: () => void; }) { _onTokenExpired = config.onTokenExpired; _onAuthFailure = config.onAuthFailure; } export const baseQueryWithReauth: BaseQueryFn< string | FetchArgs, unknown, FetchBaseQueryError > = async (args, api, extraOptions) => { const baseQuery = fetchBaseQuery({ baseUrl: BASE_API_URL, prepareHeaders: (headers) => { const { token, sessionHeaders } = resolveSessionContext( api.getState() as { auth?: { token?: string } }, ); if (token) headers.set("Authorization", `Bearer ${token}`); Object.entries(sessionHeaders).forEach(([k, v]) => headers.set(k, v)); return headers; }, }); let result = await baseQuery(args, api, extraOptions); if (result.error?.status === 401) { if (_onTokenExpired) { try { await _onTokenExpired(); result = await baseQuery(args, api, extraOptions); } catch (err) { // Only a rejected refresh token ends the session. A network blip or a // 5xx leaves the original 401 for the screen to report, rather than // throwing the user out of a session that is still valid. if ((err as { sessionExpired?: boolean })?.sessionExpired) { _onAuthFailure?.(); } } } else { _onAuthFailure?.(); } } return result; };