/* eslint-disable @typescript-eslint/no-explicit-any */ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from 'axios' import type { Fn } from '@datago/utils' export interface UseRequestReturn { isLoading: boolean isFinished: boolean isAborted: boolean statusCode: number | null response: AxiosResponse | null error: any data: T | null abort: Fn execute: (throwOnFailed?: boolean) => Promise // methods get(): UseRequestReturn post(payload?: unknown, contentType?: ContentType): UseRequestReturn patch(payload?: unknown, contentType?: ContentType): UseRequestReturn put(payload?: unknown, contentType?: ContentType): UseRequestReturn delete(payload?: unknown, contentType?: ContentType): UseRequestReturn } export interface UseRequestOptions { instance?: AxiosInstance /** * Will automatically run Request when `useRequest` is used * * @default true */ immediate?: boolean } type RequestInterceptUse = AxiosInstance['interceptors']['request']['use'] type ResponseInterceptUse = AxiosInstance['interceptors']['response']['use'] export interface CreateRequestOptions { baseURL?: string timeout?: number requestIntercept?: (use: RequestInterceptUse) => void responseIntercept?: (use: ResponseInterceptUse) => void } export type RequestMethod = 'get' | 'post' | 'patch' | 'put' | 'delete' export type ContentType = 'json' | 'text' | 'formData' | 'form' const contentTypeMapping: Record = { json: 'application/json', text: 'text/plain', formData: 'multipart/form-data', form: 'application/x-www-form-urlencoded', } function isRequestOption(obj: object): obj is UseRequestOptions { return ['immediate', 'beforeRequest', 'afterRequest', 'onRequestError'].some((key) => key in obj) } export function createRequest(options: CreateRequestOptions) { const { requestIntercept, responseIntercept, ..._options } = options const instance = axios.create(_options) if (requestIntercept) { requestIntercept(instance.interceptors.request.use.bind(instance.interceptors.request)) } if (responseIntercept) { responseIntercept(instance.interceptors.response.use.bind(instance.interceptors.response)) } function useFactoryRequest(url: string, ...args: any[]) { let options = { instance } let requestConfig = {} if (args.length > 0) { if (isRequestOption(args[0])) { options = { ...options, ...args[0] } } else { requestConfig = { ...args[0], } } } if (args.length > 1 && isRequestOption(args[1])) { options = { ...options, ...args[1] } } return useRequest(url, requestConfig, options) } return useFactoryRequest as typeof useRequest } export function useRequest(url: string): UseRequestReturn export function useRequest(url: string, useRequestOptions: UseRequestOptions): UseRequestReturn export function useRequest( url: string, config: AxiosRequestConfig, useRequestOptions?: UseRequestOptions ): UseRequestReturn export function useRequest(url: string, ...args: any[]): UseRequestReturn { let options: UseRequestOptions = { immediate: true } let config: AxiosRequestConfig = {} type InterConfig = { method: RequestMethod; payload: unknown; contentType?: ContentType } const interConfig: InterConfig = { method: 'get', payload: undefined as unknown } if (args.length > 0) { if (isRequestOption(args[0])) { options = { ...options, ...args[0] } } else { config = args[0] } } if (args.length > 1) { if (isRequestOption(args[1])) { options = { ...options, ...args[1] } } } const { instance = axios } = options const cancelTokenSource: CancelTokenSource = axios.CancelToken.source() config.cancelToken = cancelTokenSource.token const shell: UseRequestReturn = { isLoading: false, isFinished: false, isAborted: false, statusCode: null, response: null, data: null, error: null, abort, execute, get: setMethod('get'), post: setMethod('post'), patch: setMethod('patch'), put: setMethod('put'), delete: setMethod('delete'), } function abort() { shell.isAborted = true shell.isFinished = true shell.isLoading = false cancelTokenSource.cancel() } function execute(throwOnFailed = false) { shell.isLoading = true shell.isFinished = false shell.isAborted = false shell.statusCode = null shell.error = null const defaultConfig: AxiosRequestConfig = { method: interConfig.method, headers: {}, } if (interConfig.payload) { const headers = defaultConfig.headers as Record if (interConfig.contentType) { headers['Content-Type'] = contentTypeMapping[interConfig.contentType] ?? interConfig.contentType } defaultConfig.data = interConfig.payload } return new Promise((resolve, reject) => { instance(url, { ...defaultConfig, ...config, headers: { ...defaultConfig.headers, ...config.headers, }, }) .then((response) => { shell.response = response shell.statusCode = response.status shell.data = response.data resolve(response) }) .catch((error) => { shell.error = error if (throwOnFailed) { reject(error) } else { resolve(undefined) } }) .finally(() => { shell.isLoading = false shell.isFinished = true }) }) } function setMethod(method: RequestMethod) { return (payload?: unknown, contentType?: ContentType) => { if (!shell.isLoading) { interConfig.method = method interConfig.payload = payload interConfig.contentType = contentType return shell as any } return undefined } } if (options.immediate) { setTimeout(execute, 0) } return shell as UseRequestReturn }