-
-
Save anymaniax/44c1331a5643081a82da070e45f405f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type HttpRequestInit = Omit<RequestInit, 'headers'> & { | |
headers: Record<string, string>; | |
}; | |
type HttpRequestInterceptorContext = { | |
url: string; | |
init: HttpRequestInit; | |
}; | |
type HttpRequestInterceptor = ( | |
context: HttpRequestInterceptorContext, | |
) => Promise<HttpRequestInterceptorContext> | HttpRequestInterceptorContext; | |
type HttpResponseInterceptorContext<T> = { | |
url: string; | |
init: HttpRequestInit; | |
response: FetchResponse<T>; | |
}; | |
type HttpResponseInterceptor<T> = ( | |
context: HttpResponseInterceptorContext<T>, | |
) => | |
| Promise<HttpResponseInterceptorContext<T>> | |
| HttpResponseInterceptorContext<T>; | |
type HttpInstance<T> = { | |
baseURL: string; | |
headers: Record<string, string>; | |
interceptors: { | |
request: Record<string, HttpRequestInterceptor>; | |
response: Record<string, HttpResponseInterceptor<T>>; | |
}; | |
}; | |
export const HTTP_INSTANCE: HttpInstance<any> = { | |
baseURL: '', | |
headers: {}, | |
interceptors: { | |
request: {}, | |
response: {}, | |
}, | |
}; | |
export type FetchOptions = { | |
baseURL?: string; | |
headers?: Record<string, string>; | |
url: string; | |
method: | |
| 'get' | |
| 'post' | |
| 'put' | |
| 'delete' | |
| 'patch' | |
| 'GET' | |
| 'POST' | |
| 'PUT' | |
| 'DELETE' | |
| 'PATCH'; | |
params?: any; | |
data?: any; | |
responseType?: string; | |
signal?: AbortSignal; | |
next?: { | |
revalidate?: number; | |
tags?: string[]; | |
}; | |
}; | |
export type FetchResponse<T = unknown> = { | |
data: T; | |
headers: Record<string, string>; | |
status: number; | |
}; | |
export type FetchError<T = unknown> = { | |
data: T; | |
headers: Record<string, string>; | |
status: number; | |
}; | |
export const getBody = <T>(c: Response | Request): Promise<T> => { | |
const contentType = c.headers.get('content-type'); | |
if (contentType && contentType.includes('application/json')) { | |
return c.json(); | |
} | |
if (contentType && contentType.includes('application/pdf')) { | |
return c.blob() as Promise<T>; | |
} | |
return c.text() as Promise<T>; | |
}; | |
export const getHeaders = (c: Response | Request): Record<string, string> => { | |
const headers: Record<string, string> = {}; | |
c.headers.forEach((value, key) => { | |
headers[key] = value; | |
}); | |
return headers; | |
}; | |
export const fetchInstance = async <T>( | |
config: FetchOptions, | |
): Promise<FetchResponse<T>> => { | |
const isFormData = config.headers?.['Content-Type'] === 'multipart/form-data'; | |
const isJson = config.headers?.['Content-Type'] === 'application/json'; | |
const baseUrl = config.baseURL; | |
const headers = { | |
...config.headers, | |
...(isJson ? { 'Content-Type': 'application/json' } : {}), | |
}; | |
// Remove Content-Type header if it's not needed to avoid issues | |
if (!isJson) { | |
delete headers['Content-Type']; | |
} | |
const url = | |
`${baseUrl}${config.url}` + | |
(config.params ? `?${new URLSearchParams(config.params)}` : ''); | |
const requestInit: HttpRequestInit = { | |
method: config.method, | |
...(config.data | |
? { body: !isFormData ? JSON.stringify(config.data) : config.data } | |
: {}), | |
headers, | |
signal: config.signal, | |
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
// @ts-ignore | |
next: config.next, | |
}; | |
const requestContext = { url, init: requestInit }; | |
const interceptorRequest = await Object.values( | |
HTTP_INSTANCE.interceptors.request, | |
).reduce(async (acc, interceptor) => { | |
return interceptor(await acc); | |
}, Promise.resolve(requestContext)); | |
const request = new Request(interceptorRequest.url, interceptorRequest.init); | |
const response = await fetch(request); | |
const data = await getBody<T>(response); | |
const responseHeaders = getHeaders(response); | |
const responseContext: HttpResponseInterceptorContext<T> = { | |
url, | |
init: requestInit, | |
response: { | |
status: response.status, | |
headers: responseHeaders, | |
data, | |
}, | |
}; | |
const interceptorResponse = await Object.values( | |
HTTP_INSTANCE.interceptors.response, | |
).reduce(async (acc, interceptor) => { | |
return interceptor(await acc); | |
}, Promise.resolve(responseContext)); | |
if (!response.ok) { | |
return Promise.reject(interceptorResponse.response); | |
} | |
return interceptorResponse.response; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment