computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.6 KiB
146 lines
3.6 KiB
import Cookies from 'js-cookie'; |
|
import fp from '@fingerprintjs/fingerprintjs'; |
|
import { TOKEN_COOKIE_NAME, removeAuthToken } from './jwt.ts'; |
|
|
|
type HttpOptionsType = RequestInit; |
|
|
|
type AppResponse = Record<string, any>; |
|
|
|
export interface FetchError extends Error { |
|
status: number; |
|
message: string; |
|
} |
|
|
|
type AppError = { |
|
status: number; |
|
message: string; |
|
errors?: { message: string; location: string }[]; |
|
}; |
|
|
|
type ApiReturn<ResponseType> = ResponseType; |
|
|
|
/** |
|
* Wrapper around fetch to make it easy to handle errors |
|
* |
|
* @param url |
|
* @param options |
|
*/ |
|
export async function httpCall<ResponseType = AppResponse>( |
|
url: string, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
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<ResponseType>; |
|
} |
|
|
|
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<ResponseType = AppResponse>( |
|
url: string, |
|
body: Record<string, any>, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
return httpCall<ResponseType>(url, { |
|
...options, |
|
method: 'POST', |
|
body: body instanceof FormData ? body : JSON.stringify(body), |
|
}); |
|
} |
|
|
|
export async function httpGet<ResponseType = AppResponse>( |
|
url: string, |
|
queryParams?: Record<string, any>, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
const searchParams = new URLSearchParams(queryParams).toString(); |
|
const queryUrl = searchParams ? `${url}?${searchParams}` : url; |
|
|
|
return httpCall<ResponseType>(queryUrl, { |
|
credentials: 'include', |
|
method: 'GET', |
|
...options, |
|
}); |
|
} |
|
|
|
export async function httpPatch<ResponseType = AppResponse>( |
|
url: string, |
|
body: Record<string, any>, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
return httpCall<ResponseType>(url, { |
|
...options, |
|
method: 'PATCH', |
|
body: JSON.stringify(body), |
|
}); |
|
} |
|
|
|
export async function httpPut<ResponseType = AppResponse>( |
|
url: string, |
|
body: Record<string, any>, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
return httpCall<ResponseType>(url, { |
|
...options, |
|
method: 'PUT', |
|
body: JSON.stringify(body), |
|
}); |
|
} |
|
|
|
export async function httpDelete<ResponseType = AppResponse>( |
|
url: string, |
|
options?: HttpOptionsType, |
|
): Promise<ApiReturn<ResponseType>> { |
|
return httpCall<ResponseType>(url, { |
|
...options, |
|
method: 'DELETE', |
|
}); |
|
}
|
|
|