73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
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<T>(request: Request): Promise<T> {
|
|
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" } },
|
|
);
|
|
}
|