import type { H3Event } from "h3"; import { createError, getCookie } from "h3"; import { NEWAPI_SESSION_COOKIE, NEWAPI_USER_ID_COOKIE } from "~~/server/utils/newApiAuthCookies"; /** 上游 NewAPI 服务的根地址,只在这里维护一次 */ const BASE_URL = "https://api.qflink.xyz"; type FetchOptions = Parameters[1]; type FetchHeaders = HeadersInit | undefined; interface INewApiRawResponse { /** 上游响应 body,ofetch raw response 使用 _data 保存解析后的 body */ _data?: T; /** 上游响应头,登录时需要从 Set-Cookie 中提取 session */ headers: Headers; } /** * 向上游 NewAPI 发起请求 * * @param path 相对路径,如 "/api/user/register",会自动拼接到 BASE_URL 后面 * @param options 透传给 $fetch 的所有选项(method、body、headers 等) * @returns Promise,T 是上游返回的数据类型 * * @example * const result = await newApiFetch("/api/user/register", { * method: "POST", * body: { username: "xxx" } * }) */ export const newApiFetch = ( path: string, options?: FetchOptions ): Promise => { return $fetch(path, { baseURL: BASE_URL, ...options }) as Promise; }; /** * 向上游 NewAPI 发起请求,并保留响应头。 * * 仅在登录这类需要读取 Set-Cookie 的场景使用;普通业务请求继续用 * newApiFetch,保证上游请求入口仍然集中在本文件。 */ export const newApiFetchRaw = async ( path: string, options?: FetchOptions ): Promise> => { const response = await $fetch.raw(path, { baseURL: BASE_URL, ...options }); return { _data: response._data as T | undefined, headers: response.headers }; }; /** * 向上游 NewAPI 发起已登录请求。 * * 前端不读取 NewAPI session;这里只在 Nuxt server 内从本站 httpOnly * cookie 读取 session/userId,并按 NewAPI 要求补齐 Cookie 与 New-Api-User。 */ export const newApiAuthedFetch = ( event: H3Event, path: string, options?: FetchOptions ): Promise => { const session = getCookie(event, NEWAPI_SESSION_COOKIE); const userId = getCookie(event, NEWAPI_USER_ID_COOKIE); if (!session || !userId) { throw createError({ statusCode: 401, statusMessage: "未登录" }); } return newApiFetch(path, { ...options, headers: { ...normalizeHeaders(options?.headers), Cookie: `session=${session}`, "New-Api-User": userId } }); }; /** 归一化调用方 headers,追加鉴权头时不丢失已有 Content-Type */ const normalizeHeaders = (headers: FetchHeaders) => { if (!headers) return {}; return Object.fromEntries(new Headers(headers).entries()); };