import axios from 'axios' import { useUserStore } from '@/store/modules/user' import { ApiStatus } from './status' import { HttpError, handleError, showError, showSuccess } from './error' import { $t } from '@/locales' const REQUEST_TIMEOUT = 15e3 const LOGOUT_DELAY = 500 const MAX_RETRIES = 0 const RETRY_DELAY = 1e3 const UNAUTHORIZED_DEBOUNCE_TIME = 3e3 let isUnauthorizedErrorShown = false let unauthorizedTimer = null const { VITE_API_URL, VITE_WITH_CREDENTIALS } = import.meta.env const axiosInstance = axios.create({ timeout: REQUEST_TIMEOUT, baseURL: VITE_API_URL, withCredentials: VITE_WITH_CREDENTIALS === 'true', validateStatus: (status) => status >= 200 && status < 300, transformResponse: [ (data, headers) => { const contentType = headers['content-type'] if (contentType?.includes('application/json')) { try { return JSON.parse(data) } catch { return data } } return data } ] }) axiosInstance.interceptors.request.use( (request2) => { const { accessToken } = useUserStore() if (accessToken) request2.headers.set('Authorization', accessToken) if ( request2.data && !(request2.data instanceof FormData) && !request2.headers['Content-Type'] ) { request2.headers.set('Content-Type', 'application/json') request2.data = JSON.stringify(request2.data) } return request2 }, (error) => { showError(createHttpError($t('httpMsg.requestConfigError'), ApiStatus.error)) return Promise.reject(error) } ) axiosInstance.interceptors.response.use( (response) => { const { code, msg } = response.data if (code === ApiStatus.success) return response if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg) throw createHttpError(msg || $t('httpMsg.requestFailed'), code) }, (error) => { if (error.response?.status === ApiStatus.unauthorized) handleUnauthorizedError() return Promise.reject(handleError(error)) } ) function createHttpError(message, code) { return new HttpError(message, code) } function handleUnauthorizedError(message) { const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized) if (!isUnauthorizedErrorShown) { isUnauthorizedErrorShown = true logOut() unauthorizedTimer = setTimeout(resetUnauthorizedError, UNAUTHORIZED_DEBOUNCE_TIME) showError(error, true) throw error } throw error } function resetUnauthorizedError() { isUnauthorizedErrorShown = false if (unauthorizedTimer) clearTimeout(unauthorizedTimer) unauthorizedTimer = null } function logOut() { setTimeout(() => { useUserStore().logOut() }, LOGOUT_DELAY) } function shouldRetry(statusCode) { return [ ApiStatus.requestTimeout, ApiStatus.internalServerError, ApiStatus.badGateway, ApiStatus.serviceUnavailable, ApiStatus.gatewayTimeout ].includes(statusCode) } async function retryRequest(config, retries = MAX_RETRIES) { try { return await request(config) } catch (error) { if (retries > 0 && error instanceof HttpError && shouldRetry(error.code)) { await delay(RETRY_DELAY) return retryRequest(config, retries - 1) } throw error } } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } async function request(config) { if ( ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') && config.params && !config.data ) { config.data = config.params config.params = void 0 } try { const res = await axiosInstance.request(config) if (config.showSuccessMessage && res.data.msg) { showSuccess(res.data.msg) } return res.data.data } catch (error) { if (error instanceof HttpError && error.code !== ApiStatus.unauthorized) { const showMsg = config.showErrorMessage !== false showError(error, showMsg) } return Promise.reject(error) } } const api = { get(config) { return retryRequest({ ...config, method: 'GET' }) }, post(config) { return retryRequest({ ...config, method: 'POST' }) }, put(config) { return retryRequest({ ...config, method: 'PUT' }) }, del(config) { return retryRequest({ ...config, method: 'DELETE' }) }, request(config) { return retryRequest(config) } } export default api