export enum Method { GET = "GET", POST = "POST", PUT = "PUT", DELETE = "DELETE", PATCH = "PATCH", } export type ResponseInterceptor = (r: Response, rq?: RequestInit) => void; export interface TResponse { status: number; error: boolean; data: T; response: Response; } export type RequestArgs = { url: string; body?: T; data?: FormData; headers?: Record; }; export class Requests { private headers: Record = {}; private responseInterceptors: ResponseInterceptor[] = []; private bearer: () => string | null = () => null; withResponseInterceptor(interceptor: ResponseInterceptor) { this.responseInterceptors.push(interceptor); return this; } withBearer(bearer: () => string | null) { this.bearer = bearer; return this; } private callResponseInterceptors(response: Response, request?: RequestInit) { this.responseInterceptors.forEach(i => i(response, request)); } constructor(headers: Record = {}) { this.headers = headers; } public get(args: RequestArgs): Promise> { return this.do(Method.GET, args); } public post(args: RequestArgs): Promise> { return this.do(Method.POST, args); } public put(args: RequestArgs): Promise> { return this.do(Method.PUT, args); } public delete(args: RequestArgs): Promise> { return this.do(Method.DELETE, args); } public patch(args: RequestArgs): Promise> { return this.do(Method.PATCH, args); } private methodSupportsBody(method: Method): boolean { return method === Method.POST || method === Method.PUT || method === Method.PATCH; } private async do(method: Method, rargs: RequestArgs): Promise> { const payload: RequestInit = { method, headers: { ...rargs.headers, ...this.headers, } as Record, }; if (this.methodSupportsBody(method)) { if (rargs.data) { payload.body = rargs.data; } else { // @ts-expect-error - we know that the header is there payload.headers["Content-Type"] = "application/json"; payload.body = JSON.stringify(rargs.body); } } const maybeBearer = this.bearer(); if (maybeBearer) { // @ts-expect-error - we know that the header is there payload.headers.Authorization = `Bearer ${maybeBearer}`; } const response = await fetch(rargs.url, payload); this.callResponseInterceptors(response, payload); const data: T = await (async () => { if (response.status === 204) { return {} as T; } if (response.headers.get("Content-Type")?.startsWith("application/json")) { try { return await response.json(); } catch (e) { return {} as T; } } return response.body as unknown as T; })(); return { status: response.status, error: !response.ok, data, response, }; } }