70 lines
2.0 KiB
TypeScript
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;
|