/* eslint-disable @typescript-eslint/no-explicit-any */ import axios, { AxiosError, AxiosRequestConfig } from 'axios'; import * as endpoints from './api-endpoints'; import { setTokenHeader } from './headers-helpers'; import type * as t from './types'; async function _get(url: string, options?: AxiosRequestConfig): Promise { const response = await axios.get(url, { ...options }); return response.data; } async function _getResponse(url: string, options?: AxiosRequestConfig): Promise { return await axios.get(url, { ...options }); } async function _post(url: string, data?: any) { const response = await axios.post(url, JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); return response.data; } async function _postMultiPart(url: string, formData: FormData, options?: AxiosRequestConfig) { const response = await axios.post(url, formData, { ...options, headers: { 'Content-Type': 'multipart/form-data' }, }); return response.data; } async function _postTTS(url: string, formData: FormData, options?: AxiosRequestConfig) { const response = await axios.post(url, formData, { ...options, headers: { 'Content-Type': 'multipart/form-data' }, responseType: 'arraybuffer', }); return response.data; } async function _put(url: string, data?: any) { const response = await axios.put(url, JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); return response.data; } async function _delete(url: string): Promise { const response = await axios.delete(url); return response.data; } async function _deleteWithOptions(url: string, options?: AxiosRequestConfig): Promise { const response = await axios.delete(url, { ...options }); return response.data; } async function _patch(url: string, data?: any) { const response = await axios.patch(url, JSON.stringify(data), { headers: { 'Content-Type': 'application/json' }, }); return response.data; } let isRefreshing = false; let failedQueue: { resolve: (value?: any) => void; reject: (reason?: any) => void }[] = []; const refreshToken = (retry?: boolean): Promise => _post(endpoints.refreshToken(retry)); const dispatchTokenUpdatedEvent = (token: string) => { setTokenHeader(token); window.dispatchEvent(new CustomEvent('tokenUpdated', { detail: token })); }; const processQueue = (error: AxiosError | null, token: string | null = null) => { failedQueue.forEach((prom) => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; }; axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (!error.response) { return Promise.reject(error); } if (originalRequest.url?.includes('/api/auth/2fa') === true) { return Promise.reject(error); } if (originalRequest.url?.includes('/api/auth/logout') === true) { return Promise.reject(error); } if (error.response.status === 401 && !originalRequest._retry) { console.warn('401 error, refreshing token'); originalRequest._retry = true; if (isRefreshing) { try { const token = await new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }); }); originalRequest.headers['Authorization'] = 'Bearer ' + token; return await axios(originalRequest); } catch (err) { return Promise.reject(err); } } isRefreshing = true; try { const response = await refreshToken( // Handle edge case where we get a blank screen if the initial 401 error is from a refresh token request originalRequest.url?.includes('api/auth/refresh') === true ? true : false, ); const token = response?.token ?? ''; if (token) { originalRequest.headers['Authorization'] = 'Bearer ' + token; dispatchTokenUpdatedEvent(token); processQueue(null, token); return await axios(originalRequest); } else if (window.location.href.includes('share/')) { console.log( `Refresh token failed from shared link, attempting request to ${originalRequest.url}`, ); } else { window.location.href = '/login'; } } catch (err) { processQueue(err as AxiosError, null); return Promise.reject(err); } finally { isRefreshing = false; } } return Promise.reject(error); }, ); export default { get: _get, getResponse: _getResponse, post: _post, postMultiPart: _postMultiPart, postTTS: _postTTS, put: _put, delete: _delete, deleteWithOptions: _deleteWithOptions, patch: _patch, refreshToken, dispatchTokenUpdatedEvent, };