import { DomainError } from "@/db/repository"; function requireJsonMutation(request: Request): void { const contentType = request.headers .get("content-type") ?.split(";", 1)[0] .trim() .toLowerCase(); if (contentType !== "application/json") { throw new DomainError( "json_required", "写操作只接受 application/json", 415, ); } const contentLength = Number(request.headers.get("content-length") ?? "0"); if (Number.isFinite(contentLength) && contentLength > 32_768) { throw new DomainError("request_too_large", "请求内容过大", 413); } const origin = request.headers.get("origin"); if (origin && origin !== new URL(request.url).origin) { throw new DomainError("invalid_origin", "拒绝跨站写操作", 403); } const fetchSite = request.headers.get("sec-fetch-site"); if (fetchSite && fetchSite !== "same-origin") { throw new DomainError("invalid_fetch_site", "拒绝跨站写操作", 403); } } export async function readJsonMutation(request: Request): Promise { requireJsonMutation(request); const body = await request.text(); if (new TextEncoder().encode(body).byteLength > 32_768) { throw new DomainError("request_too_large", "请求内容过大", 413); } try { return JSON.parse(body) as T; } catch { throw new DomainError("invalid_json", "请求不是有效 JSON", 400); } } export function apiError(error: unknown) { if (error instanceof DomainError) { const headers = new Headers({ "cache-control": "no-store" }); if (error.retryAfterSeconds !== undefined) { headers.set("retry-after", String(error.retryAfterSeconds)); } return Response.json({ error_code: error.code, error: error.code, message: error.message, retryable: error.retryable, ...(error.retryAfterSeconds === undefined ? {} : { retry_after_seconds: error.retryAfterSeconds }), ...(error.actionUrl === undefined ? {} : { action_url: error.actionUrl }), }, { status: error.status, headers }); } console.error("Unhandled NekoNest Cloud API error", error); return Response.json( { error_code: "internal_error", error: "internal_error", message: "服务暂时不可用,请稍后重试", retryable: true, }, { status: 500, headers: { "cache-control": "no-store" } }, ); }