import { useAuthStore } from '~/stores/auth' type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' interface ApiRequestOptions { method?: HttpMethod body?: T query?: Record headers?: HeadersInit auth?: boolean /** AbortSignal — pass to cancel an in-flight request (e.g. on frequency change). */ signal?: AbortSignal } export function useApi() { const auth = useAuthStore() const execute = async (path: string, options: ApiRequestOptions = {}) => { const { method = 'GET', body, query, headers = {}, auth: requiresAuth = true, signal } = options const computedHeaders: Record = { Accept: 'application/json', ...(headers as Record), } if (requiresAuth && auth.accessToken) { computedHeaders.Authorization = `Bearer ${auth.accessToken}` } const requestOptions: any = { method, headers: computedHeaders, query, body, signal, } if (body && !(body instanceof FormData)) { computedHeaders['Content-Type'] = 'application/json' requestOptions.body = body } try { return await $fetch(path, requestOptions) } catch (error: any) { // Propagate abort errors immediately — do not retry on 401. if (error?.name === 'AbortError' || signal?.aborted) throw error const status = error?.status || error?.response?.status if (status === 401 && requiresAuth) { const refreshed = await auth.tryRefresh() if (refreshed) { if (auth.accessToken) { computedHeaders.Authorization = `Bearer ${auth.accessToken}` } return await $fetch(path, { ...requestOptions, signal }) } await auth.logout() } throw error } } return { request: execute, get: (path: string, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'GET' }), post: (path: string, body?: any, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'POST', body }), put: (path: string, body?: any, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'PUT', body }), del: (path: string, options: ApiRequestOptions = {}) => execute(path, { ...options, method: 'DELETE' }), } }