import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; let accessToken: string | null = null; let accessTokenRetryCount = 0; type RequestFunc = (url: string, config?: AxiosRequestConfig) => Promise>; type FetchOption = { numberOfRetryBeforeRefetchAccessToken?: number; refetchAccessTokenUri: string; refetchAccessTokenUriConfig?: AxiosRequestConfig; getAccessToken: (response: AccessTokenResponse) => string; numberOfAccessTokenRetry?: number; requestConfig?: AxiosRequestConfig; minWaitTime?: number; maxWaitTime?: number; logger?: ((...data: any[]) => void) | null | undefined; }; const DEFAULT_OPTS = { numberOfRetryBeforeRefetchAccessToken: 5, numberOfAccessTokenRetry: 3, minWaitTime: 0, maxWaitTime: 0, } const resetAll = () => { accessToken = null; accessTokenRetryCount = 0; } const fetchAccessToken = async (options: FetchOption) => { try { accessToken = null; const response = await axios.get<{}, AccessTokenResponse>(options.refetchAccessTokenUri, options.refetchAccessTokenUriConfig); accessToken = options.getAccessToken(response); } catch (error) { options.logger?.(`Access token request failed #${accessTokenRetryCount + 1}: `, error); } finally { accessTokenRetryCount += 1; } } const fetch = async (func: RequestFunc, uri: string, opts: FetchOption) => { const options = { ...DEFAULT_OPTS, ...opts }; let requestRetryCount = 0; accessTokenRetryCount = 0; do { if ( accessToken == null || requestRetryCount >= options.numberOfRetryBeforeRefetchAccessToken ) { await fetchAccessToken(options); await new Promise((resolve) => setTimeout(resolve, Math.random() * (options.maxWaitTime - options.minWaitTime) + options.minWaitTime)); } if (accessToken != null) { requestRetryCount = 0; do { let result; try { result = await func(uri, { headers: { Authorization: `Bearer ${accessToken}`, ...(options.requestConfig?.headers || {}), }, ...(options.requestConfig || {}), }); return result; } catch (error) { options.logger?.(`Request failed #${requestRetryCount + 1} to ${uri}: `, error, ); if (requestRetryCount >= options.numberOfRetryBeforeRefetchAccessToken - 1 && result?.status === 404) { return null; } } finally { requestRetryCount += 1; } await new Promise((resolve) => setTimeout(resolve, Math.random() * (options.maxWaitTime - options.minWaitTime) + options.minWaitTime)); } while (requestRetryCount < options.numberOfRetryBeforeRefetchAccessToken); } } while (accessTokenRetryCount < options.numberOfAccessTokenRetry); throw new Error('API request failed.'); } const get = (uri: string, opts: FetchOption) => { return fetch(axios.get, uri, opts); }; const post = (uri: string, opts: FetchOption) => { return fetch(axios.post, uri, opts); }; export { get, post, resetAll };