import axios, { type AxiosError, type AxiosInstance, type AxiosRequestConfig } from 'axios'; import { API_POSE_FRAMES_PATH, API_POSE_STATUS_PATH, API_POSE_ZONES_PATH } from '@/constants/api'; import type { ApiError, HistoricalFrames, PoseStatus, ZoneConfig } from '@/types/api'; class ApiService { private baseUrl = ''; private client: AxiosInstance; constructor() { this.client = axios.create({ timeout: 5000, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); } setBaseUrl(url: string): void { this.baseUrl = url ?? ''; } private buildUrl(path: string): string { if (!this.baseUrl) { return path; } if (path.startsWith('http://') || path.startsWith('https://')) { return path; } const normalized = this.baseUrl.replace(/\/$/, ''); return `${normalized}${path.startsWith('/') ? path : `/${path}`}`; } private normalizeError(error: unknown): ApiError { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError<{ message?: string }>; const message = axiosError.response?.data && typeof axiosError.response.data === 'object' && 'message' in axiosError.response.data ? String((axiosError.response.data as { message?: string }).message) : axiosError.message || 'Request failed'; return { message, status: axiosError.response?.status, code: axiosError.code, details: axiosError.response?.data, }; } if (error instanceof Error) { return { message: error.message }; } return { message: 'Unknown error' }; } private async requestWithRetry(config: AxiosRequestConfig, retriesLeft: number): Promise { try { const response = await this.client.request({ ...config, url: this.buildUrl(config.url || ''), }); return response.data; } catch (error) { if (retriesLeft > 0) { return this.requestWithRetry(config, retriesLeft - 1); } throw this.normalizeError(error); } } get(path: string): Promise { return this.requestWithRetry({ method: 'GET', url: path }, 2); } post(path: string, body: unknown): Promise { return this.requestWithRetry({ method: 'POST', url: path, data: body }, 2); } getStatus(): Promise { return this.get(API_POSE_STATUS_PATH); } getZones(): Promise { return this.get(API_POSE_ZONES_PATH); } getFrames(limit: number): Promise { return this.get(`${API_POSE_FRAMES_PATH}?limit=${encodeURIComponent(String(limit))}`); } } export const apiService = new ApiService();