import { parse as parseCookie, serialize as serializeCookie } from "cookie"; import { API_BASE, INTERNAL_API_BASE } from "~/env.server"; import { ApiError, ErrorCode } from "./api/error"; import { tokenCookieName } from "~/lib/utils"; export type RequestParams = { token?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any body?: any; headers?: Record; isInternal?: boolean; }; export type RequestMethod = "GET" | "POST" | "PATCH" | "DELETE"; export async function baseRequest( method: RequestMethod, path: string, params: RequestParams = {}, ): Promise { // Internal requests, unauthenticated requests, and GET requests bypass the rate limiting proxy. // All other requests go through the proxy, and are rate limited. let base = params.isInternal || !params.token || method === "GET" ? INTERNAL_API_BASE : API_BASE; base += params.isInternal ? "/internal" : "/v2"; const url = `${base}${path}`; const resp = await fetch(url, { method, body: params.body ? JSON.stringify(params.body) : undefined, headers: { ...params.headers, ...(params.token ? { Authorization: params.token } : {}), ...(params.body ? { "Content-Type": "application/json" } : {}), }, }); if (resp.headers.get("Content-Type")?.indexOf("application/json") === -1) { // If we don't get a JSON response, the server almost certainly encountered an internal error it couldn't recover from // (that, or the reverse proxy, which should also be treated as a 500 error) throw { status: 500, code: ErrorCode.InternalServerError, message: "Internal server error", } as ApiError; } if (resp.status < 200 || resp.status >= 400) throw (await resp.json()) as ApiError; return resp; } export async function fastRequest(method: RequestMethod, path: string, params: RequestParams = {}) { await baseRequest(method, path, params); } export default async function serverRequest( method: RequestMethod, path: string, params: RequestParams = {}, ) { const resp = await baseRequest(method, path, params); return (await resp.json()) as T; } export const getToken = (req: Request) => getCookie(req, tokenCookieName); export function getCookie(req: Request, cookieName: string): string | undefined { const header = req.headers.get("Cookie"); if (!header) return undefined; const cookie = parseCookie(header); return cookieName in cookie ? cookie[cookieName] : undefined; } const YEAR = 365 * 86400; export const writeCookie = (cookieName: string, value: string, maxAge: number | undefined = YEAR) => serializeCookie(cookieName, value, { maxAge, path: "/", sameSite: "lax", httpOnly: true, secure: true, });