Skip to content

Instantly share code, notes, and snippets.

@anymaniax
Created May 13, 2024 12:33
Show Gist options
  • Save anymaniax/44c1331a5643081a82da070e45f405f0 to your computer and use it in GitHub Desktop.
Save anymaniax/44c1331a5643081a82da070e45f405f0 to your computer and use it in GitHub Desktop.
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