From 7d10be1c1fa83dcef42e56d746e95bd3ecf1ff49 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 28 Feb 2026 12:57:52 +0800 Subject: [PATCH] fix(api): support xsrf and backend error message --- lib/api/http.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/api/http.ts b/lib/api/http.ts index 49615fc..4447b45 100644 --- a/lib/api/http.ts +++ b/lib/api/http.ts @@ -17,6 +17,22 @@ async function readJsonBody(res: Response): Promise { } } +function getCookieValue(name: string): string | null { + if (typeof document === "undefined") return null + if (!document.cookie) return null + + for (const part of document.cookie.split("; ")) { + if (part.startsWith(`${name}=`)) return part.slice(name.length + 1) + } + return null +} + +function isApiErrorWithMessage(value: unknown): value is { code: number; message: string } { + if (typeof value !== "object" || value === null) return false + const v = value as { code?: unknown; message?: unknown } + return typeof v.code === "number" && typeof v.message === "string" +} + export async function httpJson(path: string, init?: JsonRequestInit): Promise { if (/^https?:\/\//.test(path)) { throw new Error("Absolute URLs are not allowed") @@ -33,6 +49,15 @@ export async function httpJson(path: string, init?: JsonRequestInit): Promise headers.set("Content-Type", "application/json") } + const method = (rest.method ?? "GET").toUpperCase() + if ( + (method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE") && + !headers.has("XSRF-TOKEN") + ) { + const xsrfToken = getCookieValue("__Host-XSRF-TOKEN") + if (xsrfToken) headers.set("XSRF-TOKEN", xsrfToken) + } + const res = await fetch(path, { ...rest, headers, @@ -45,7 +70,11 @@ export async function httpJson(path: string, init?: JsonRequestInit): Promise return data as T } - const apiError = isApiError(data) ? data : null + const apiError: ApiError | null = isApiError(data) + ? data + : isApiErrorWithMessage(data) + ? { code: data.code, msg: data.message } + : null if (res.status === 401 || apiError?.code === 401) { throw new Error("UNAUTHORIZED") }