Files
signal-platform/frontend/src/api/client.ts
T
Dennis Thiessen 181cfe6588
Deploy / lint (push) Failing after 8s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped
major update
2026-02-27 16:08:09 +01:00

70 lines
2.0 KiB
TypeScript

import axios from 'axios';
import type { APIEnvelope } from '../lib/types';
import { useAuthStore } from '../stores/authStore';
/**
* Typed error class for API errors, providing structured error handling
* across the application.
*/
export class ApiError extends Error {
constructor(message: string) {
super(message);
this.name = 'ApiError';
}
}
/**
* Central Axios instance configured for the Stock Data Backend API.
* - Base URL: /api/v1/
* - Timeout: 30 seconds
* - JSON content type
*/
const apiClient = axios.create({
baseURL: '/api/v1/',
timeout: 120_000,
headers: { 'Content-Type': 'application/json' },
});
/**
* Request interceptor: attaches JWT Bearer token from the auth store
* to every outgoing request when a token is available.
*/
apiClient.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
/**
* Response interceptor:
* - Success path: unwraps the { status, data, error } envelope, returning
* only the `data` field. Throws ApiError if envelope status is 'error'.
* - Error path: handles 401 by clearing auth and redirecting to login.
* All other errors are wrapped in ApiError with a descriptive message.
*/
apiClient.interceptors.response.use(
(response) => {
const envelope = response.data as APIEnvelope;
if (envelope.status === 'error') {
throw new ApiError(envelope.error ?? 'Unknown API error');
}
// Return unwrapped data — callers receive the inner payload directly.
// We override the response shape here; downstream API functions cast as needed.
response.data = envelope.data;
return response;
},
(error) => {
if (axios.isAxiosError(error) && error.response?.status === 401) {
useAuthStore.getState().logout();
window.location.href = '/login';
}
const msg =
error.response?.data?.error ?? error.message ?? 'Network error';
throw new ApiError(msg);
},
);
export default apiClient;