feat: establish NekoNest Cloud control and relay
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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" } },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user