import axios from 'axios'; import authServiceApi from './AuthServiceAPI'; import tokenApi from './TokenAPI'; /** * @interface RequestConfig * @type {Object} * @property {MethodType} method - метод запроса * @property {string} url - адрес запроса * @property {Object} body - тело запроса */ /** * Api прикрепляет ко всем запросом заголовок с токеном авторизации. В случае, когда сервер * возвращает ошибку 401 (Не авторизован), пытается рефрешнуть токен. В момент обновления пары токенов * api сохраняет все запросы в очередь. Если ревреш токенов удался, то запросы будут выполнены * в том же порядке, как и пришли, либо упадут с ошибкой, если ревреш выполнить не удалось. * @class HttpAuthApi */ class HttpAuthApi { constructor () { this.pendingRequests = []; this.isPendingRefresh = false; } /** * Выполняет какое-то действие над всеми запросами из очереди, пока она не закончится * @param {Function} handler - функция обработчик запроса */ processPendingRequests = (handler) => { this.isPendingRefresh = false; while (this.pendingRequests.length) { const pendingRequest = this.pendingRequests.shift(); handler(pendingRequest); } } /** * Повторяет все запросы из очереди в порядке их поступления */ repeatRequests = () => { this.processPendingRequests(({resolve, reject, requestConfig}) => { this.request(requestConfig) .then(resolve) .catch(reject); }); } /** * Выбрасывает ошибки для всех запросов из очереди * @param {axios.error} error - ошибка, которую возвращает axios */ rejectRequests = (error) => { this.processPendingRequests(({reject}) => { reject(error); }); } /** * Перехватывает ошибку 401 (Не авторизован), другие ошибки выбрасывает дальше * @param {axios.error} error - ошибка, которую возвращает axios * @param {Function} resolve - функция промиса, которая разрешает его успешно * @param {Function} reject - функция промиса, которая завершает его неудачей * @param {RequestConfig} requestConfig - настройки для выполнения запроса */ errorHandling = (error, resolve, reject, requestConfig) => { if (error.response.status === 401) { this.isPendingRefresh = true; this.pendingRequests({resolve, reject, requestConfig}); authServiceApi.refresh() .then(() => { this.repeatRequests(); }) .catch((refreshError) => { this.rejectRequests(refreshError); /* * Снять авторизацию и сделать роут * Сбросить все запросы с ошибкой авторизации */ }); } else { reject(error); } } /** * Добавляет к запросу токен в качестве заголовка * @param {RequestConfig} requestConfig - настройки для выполнения запроса */ addTokenToRequest = (requestConfig) => ({ ...requestConfig, headers: { 'Authorization': tokenApi.getAccessToken(), }, }) /** * Метод для внешнего использования, выполняет запрос к серверу, используя axios * @param {RequestConfig} requestConfig - настройки для выполнения запроса */ request = (requestConfig) => { return new Promise((resolve, reject) => { axios(this.addTokenToRequest(requestConfig)) .then((response) => resolve(response)) .catch((error) => this.errorHandling(error, resolve, reject, requestConfig)); }); } } const httpAuthApi = new HttpAuthApi(); export default httpAuthApi;