import Cookies from 'js-cookie'; import fp from '@fingerprintjs/fingerprintjs'; import { TOKEN_COOKIE_NAME, removeAuthToken } from './jwt.ts'; type HttpOptionsType = RequestInit; type AppResponse = Record; export interface FetchError extends Error { status: number; message: string; } type AppError = { status: number; message: string; errors?: { message: string; location: string }[]; }; type ApiReturn = ResponseType; /** * Wrapper around fetch to make it easy to handle errors * * @param url * @param options */ export async function httpCall( url: string, options?: HttpOptionsType, ): Promise> { const fullUrl = url.startsWith('http') ? url : `${import.meta.env.PUBLIC_API_URL}${url}`; try { const fingerprintPromise = await fp.load(); const fingerprint = await fingerprintPromise.get(); const isMultiPartFormData = options?.body instanceof FormData; const headers = new Headers({ Accept: 'application/json', Authorization: `Bearer ${Cookies.get(TOKEN_COOKIE_NAME)}`, fp: fingerprint.visitorId, ...(options?.headers ?? {}), }); if (!isMultiPartFormData) { headers.set('Content-Type', 'application/json'); } const response = await fetch(fullUrl, { credentials: 'include', ...options, headers, }); // @ts-ignore const doesAcceptHtml = options?.headers?.['Accept'] === 'text/html'; const data = doesAcceptHtml ? await response.text() : await response.json(); // Logout user if token is invalid if (data?.status === 401) { removeAuthToken(); window.location.href = '/login'; return null as unknown as ApiReturn; } if (!response.ok) { if (data.errors) { const error = new Error() as FetchError; error.message = data.message; error.status = response?.status; throw error; } else { throw new Error('An unexpected error occurred'); } } return data as ResponseType; } catch (error: any) { throw error; } } export async function httpPost( url: string, body: Record, options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, method: 'POST', body: body instanceof FormData ? body : JSON.stringify(body), }); } export async function httpGet( url: string, queryParams?: Record, options?: HttpOptionsType, ): Promise> { const searchParams = new URLSearchParams(queryParams).toString(); const queryUrl = searchParams ? `${url}?${searchParams}` : url; return httpCall(queryUrl, { credentials: 'include', method: 'GET', ...options, }); } export async function httpPatch( url: string, body: Record, options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, method: 'PATCH', body: JSON.stringify(body), }); } export async function httpPut( url: string, body: Record, options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, method: 'PUT', body: JSON.stringify(body), }); } export async function httpDelete( url: string, options?: HttpOptionsType, ): Promise> { return httpCall(url, { ...options, method: 'DELETE', }); }