Rebasely Beta: data may be purged

examples/storefront Public

TypeScript · 64 lines · 2.3 KiB · at 82cc041

// 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;}