import { isApiError } from "@/lib/errors" export type UploadFileType = "avatar" | "chat" | "post" | "verification" | "dispute" 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 } let csrfReady: Promise | null = null async function prepareCsrf() { if (getCookieValue("__Host-XSRF-TOKEN") && getCookieValue("__Host-XSRF-GUARD")) return csrfReady ??= fetch("/healthz", { cache: "no-store", credentials: "include", }) .then(() => {}) .finally(() => { csrfReady = null }) await csrfReady if (!getCookieValue("__Host-XSRF-TOKEN")) { await fetch("/api/v1/games?offset=0&limit=1", { cache: "no-store", credentials: "include", }) } } async function readJsonBody(res: Response): Promise<{ json: unknown | null; text: string }> { const text = await res.text() if (!text) return { json: null, text: "" } try { return { json: JSON.parse(text) as unknown, text } } catch { return { json: null, text } } } function messageFromJson(json: unknown): string | undefined { if (typeof json !== "object" || json === null) return undefined const value = json as { message?: unknown; msg?: unknown } if (typeof value.message === "string") return value.message if (typeof value.msg === "string") return value.msg return undefined } export async function getFileById(fileId: string): Promise { const res = await fetch(`/api/v1/files?key=${encodeURIComponent(fileId)}`) if (res.ok) return await res.blob() const text = await res.text() let json: unknown = null if (text) { try { json = JSON.parse(text) as unknown } catch { json = null } } const maybeObj = (typeof json === "object" && json !== null ? json : null) as { code?: unknown message?: unknown msg?: unknown } | null const code = (typeof maybeObj?.code === "number" ? maybeObj.code : res.status) as number const msg = typeof maybeObj?.message === "string" ? maybeObj.message : typeof maybeObj?.msg === "string" ? maybeObj.msg : text || res.statusText || "Request failed" if (res.status === 401 || code === 401) throw new Error("UNAUTHORIZED") throw { code, msg } } export async function uploadFile(file: File, type: UploadFileType): Promise { const formData = new FormData() formData.set("type", type) formData.set("file", file) await prepareCsrf() const headers = new Headers() const xsrfToken = getCookieValue("__Host-XSRF-TOKEN") if (xsrfToken) headers.set("XSRF-TOKEN", xsrfToken) const res = await fetch("/api/v1/upload", { method: "POST", headers, body: formData, credentials: "include", }) const { json, text } = await readJsonBody(res) if (res.ok) { if (typeof json === "object" && json !== null) { const value = json as { url?: unknown } if (typeof value.url === "string") return value.url } throw { code: 500, msg: "上传响应缺少文件地址" } } if (res.status === 401) throw new Error("UNAUTHORIZED") if (isApiError(json)) throw json throw { code: res.status, msg: messageFromJson(json) ?? (text || res.statusText || "Request failed"), } }