Files
trackgo-fe/apps/web/src/core/lib/api-client.ts
T

67 lines
2.7 KiB
TypeScript

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<string>(AppStorageKey.LANGUAGE);
config.headers['ex-language'] = language ?? ENV.DEFAULT_LANGUAGE;
const token = await appStorage.getItem<string>(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;
},
},
);