examples/storefront Public
// JSON responses and the errors the API answers with. Every error body is// { error, message } plus whatever details the error carries.export class HttpError extends Error { readonly status: number; readonly code: string; readonly details: Record<string, unknown>; constructor(status: number, code: string, message: string, details: Record<string, unknown> = {}) { super(message); this.status = status; this.code = code; this.details = details; }}export function json(body: unknown, status = 200, headers: Record<string, string> = {}): Response { return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8", ...headers }, });}export function errorResponse(e: HttpError): Response { return json({ error: e.code, message: e.message, ...e.details }, e.status);}const MAX_BODY = 64 * 1024;export async function readJson(req: Request): Promise<Record<string, unknown>> { if (!(req.headers.get("content-type") ?? "").startsWith("application/json")) { throw new HttpError(415, "unsupported_media_type", "Send the body as application/json."); } const text = await req.text(); if (text.length > MAX_BODY) throw new HttpError(413, "too_large", "The body is at most 64 KiB."); let value: unknown; try { value = JSON.parse(text); } catch { throw new HttpError(400, "bad_json", "The body is not valid JSON."); } if (!value || typeof value !== "object" || Array.isArray(value)) { throw new HttpError(400, "bad_json", "The body must be a JSON object."); } return value as Record<string, unknown>;}// Merchant routes (/admin/*) take the shop's admin token as a bearer token.export function requireAdmin(req: Request, token: string | undefined): void { const got = req.headers.get("authorization") ?? ""; if (!token || !sameBytes(got, `Bearer ${token}`)) { throw new HttpError(401, "unauthorized", "Merchant routes need the admin token."); }}// Compares in time that depends only on the length, so the token can't be guessed byte by byte.function sameBytes(a: string, b: string): boolean { const x = new TextEncoder().encode(a); const y = new TextEncoder().encode(b); if (x.length !== y.length) return false; let diff = 0; for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!; return diff === 0;}