import type { AxiosError, AxiosRequestConfig } from 'axios'; import axios, { AxiosHeaders } from 'axios'; import axiosRetry from 'axios-retry'; export interface ProgressEvent { loaded: number; total?: number; progress?: number; bytes?: number; estimated?: number; rate?: number; } export interface RequestOptions { url: string | URL | Request; baseURL?: string | URL; method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; query?: Record | URLSearchParams; data?: string | FormData | Record | File | Blob | URLSearchParams | BufferSource; headers?: Headers | Record; withCredentials?: boolean; maxRedirects?: number; responseType?: 'json' | 'blob' | 'text' | 'stream' | 'arraybuffer'; timeout?: number; // retry retryCount?: number; retryShouldResetTimeout?: boolean; retryCondition?: (error: AxiosError) => boolean | Promise; retryDelay?: (retryCount: number, error: AxiosError) => number; onRetry?: (retryCount: number, error: AxiosError, requestConfig: AxiosRequestConfig) => Promise | void; // on progress onProgress?: (event: ProgressEvent) => void; validateStatus?: (status: number) => boolean; } export interface RequestResponse { data: T; status: number; statusText: string; headers: Headers | Record; } export const request = (opts: RequestOptions) => { const url = typeof opts.url === 'string' ? opts.url : opts.url.toString(); const baseURL = typeof opts.baseURL === 'string' ? opts.baseURL : opts.baseURL?.toString(); // headers let headers: Record = {}; if (opts.headers instanceof Headers) { for (const key of opts.headers.keys()) { headers[key] = opts.headers.get(key)!; } } else if (opts.headers) { headers = opts.headers; } const controller = new AbortController(); const client = axios.create(); if (opts.retryCount) { axiosRetry(client, { retries: opts.retryCount, shouldResetTimeout: opts.retryShouldResetTimeout, retryCondition: opts.retryCondition, retryDelay: opts.retryDelay, onRetry: opts.onRetry }); } const promise = client({ url, baseURL, method: opts.method || 'GET', headers, params: opts.query || {}, timeout: opts.timeout, withCredentials: !!opts.withCredentials, maxRedirects: opts.maxRedirects || 21, responseType: opts.responseType || 'json', validateStatus: opts.validateStatus || ((status) => { return status >= 200 && status < 300; }), onDownloadProgress: (evt) => { opts.onProgress?.({ ...evt }); }, onUploadProgress: (evt) => { opts.onProgress?.({ ...evt }); }, signal: controller.signal }).then((res) => { const headers = res.headers instanceof AxiosHeaders ? res.headers.normalize(false).toJSON(true) : res.headers; return { status: res.status, statusText: res.statusText, data: res.data as T, headers: headers as RequestResponse['headers'] }; }); return { cancel: () => { controller.abort(); }, response: promise }; };