import { createHttpClient } from '@repo/core-api/http-client'; import { faroAdapter } from '@repo/core-api/observability'; import { ENV } from '../environment'; import { AppStorageKey, appStorage } from '../storage/local'; /** * Enterprise HTTP client for `apps/web`. * * - Full Faro observability via the shared `faroAdapter` * - Automatic Bearer token injection from localStorage * - 401 redirect to `/auth/login` * - Supports per-request `telemetryContext` for custom spans/tags * * All interceptors (auth, observability, error normalization) * are baked into this instance. Import this singleton throughout * the web application — never create raw axios instances. */ export const apiClient = createHttpClient( { baseURL: ENV.API_BASE_URL, timeout: 15000, observability: faroAdapter, }, { // ── Auth Interceptor ────────────────────────────────────────── onRequest: async (config) => { config.headers['ex-app-name'] = ENV.APP_NAME; config.headers['ex-app-version'] = ENV.APP_VERSION; config.headers['ex-timezone-offset-minutes'] = new Date().getTimezoneOffset(); config.headers['ex-timezone-offset-hours'] = Math.floor(new Date().getTimezoneOffset() / 60); const language = await appStorage.getItem(AppStorageKey.LANGUAGE); config.headers['ex-language'] = language ?? ENV.DEFAULT_LANGUAGE; const token = await appStorage.getItem(AppStorageKey.ACCESS_TOKEN); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }, // ── Error Interceptor ───────────────────────────────────────── onResponseError: async (error) => { const status = error.response?.status; // Catch 401 (Unauthorized) on request if (status === 401) { // Delete invalid tokens await appStorage.removeItem(AppStorageKey.ACCESS_TOKEN); // Retrieve the current path and query string (example: /dashboard/settings?tab=profile) const currentPath = window.location.pathname + window.location.search; // Retrieve the current path and query string (example: /dashboard/settings?tab=profile) const redirectParam = encodeURIComponent(currentPath); // Use replace() instead of href. // replace() will not save the history of the page with this error, // so that if the user presses the 'back' button in the browser from the login page, // they won't get stuck in an infinite loop back to the login page. window.location.replace(`/auth/login?redirect=${redirectParam}`); } throw error; }, }, );