import { FetchError } from "./fetch-error" type RequestHandlerOptions = Omit & { body?: Record queryParams?: Record } const ENDS_WITH_VERSIONING_REGEX = /\/v\d+(?=\/|$)/ const TRAILING_SLASH_REGEX = /\/$/ export abstract class ApiClient { private apiUrl: string constructor(opts: { apiUrl: string; removeVersioning?: boolean }) { const { apiUrl, removeVersioning = false } = opts if (removeVersioning) { this.apiUrl = apiUrl.replace(ENDS_WITH_VERSIONING_REGEX, "") return } this.apiUrl = apiUrl } /** * The request handler for the Passport API. It accepts an API endpoint and * request options. * @dev Generic type `D` is a type of returned object from the function * @dev WARNING: This function hardcodes `credentials: "include"`, meaning * cookies and authentication headers will be sent with every request. Ensure * this is safe if you plan to use it in other contexts than auth api worker. * @param endpoint - The API endpoint to request. Should start with * forward slash ("/") * @param options - The request options, the `RequestInit` type with * additional type-safe properties * @returns The promise of the API response */ protected async handleRequest( endpoint: `/${string}`, options: RequestHandlerOptions = {}, ): Promise { try { const { method, body, queryParams = {}, ...restOptions } = options // If last character in apiUrl is forward slash we are removing it const authApiUrlRoute = this.apiUrl.replace(TRAILING_SLASH_REGEX, "") const url = new URL(`${authApiUrlRoute}${endpoint}`) // Removes falsy values (e.g. null, undefined etc.) from query params and // appends remaining ones to the URL Object.entries(queryParams) .filter(([, value]) => value) // Remove falsy values .forEach(([key, value]) => url.searchParams.append(key, value!)) const response = await fetch(url, { method, credentials: "include", body: body ? JSON.stringify(body) : undefined, ...restOptions, }) const data = await response.json() if (!response.ok || data?.error) { const error = data?.error || `An error occurred when handling API request: ${JSON.stringify(data)}` throw new FetchError( response.status, `Error [${response.status}]: ${error}`, ) } return data as D } catch (error) { if (error instanceof FetchError) { throw error } const errorMessage = error instanceof Error ? error.message : JSON.stringify(error) throw new FetchError( 500, `Error [500]: An error occurred when handling API request: ${errorMessage}`, ) } } }