import { getApiConfig } from '../config/api'; import { beginApiWrite } from './api-activity'; import { ApiRequestError, requestApi, type RequestOptions, } from './api-transport'; import { cachedServerUser, clearSession, ensureSession, getAuthContext, recoverSession, rejectSession, } from './auth-session'; export { ApiRequestError } from './api-transport'; const recoverable = new Set([ 'AUTH_REQUIRED', 'AUTH_INVALID', 'AUTH_EXPIRED', 'AUTH_REVOKED', ]); const blocked = new Set([ 'ACCOUNT_NOT_FOUND', 'ACCOUNT_DISABLED', 'WECHAT_LOGIN_BLOCKED', ]); /** 公开接口显式 auth=none;其余接口合并登录,最多恢复并重发一次,不跨账户补发。 */ export async function apiRequest( path: string, options: RequestOptions & { auth?: 'required' | 'none' } = {}, ): Promise { const finish = options.upload || (options.method && options.method !== 'GET') ? beginApiWrite() : undefined; try { return await performApiRequest(path, options); } finally { finish?.(); } } async function performApiRequest( path: string, options: RequestOptions & { auth?: 'required' | 'none' } = {}, ): Promise { // Authorization 由会话层统一提供;公开请求不附带调用方误传的凭证。 const header = Object.fromEntries( Object.entries(options.header || {}).filter( ([key]) => key.toLowerCase() !== 'authorization', ), ); if (options.auth === 'none') return requestApi(path, { ...options, header }, getApiConfig()); const context = getAuthContext(); const previousUserId = cachedServerUser()?.id; const initial = await ensureSession(context); if (previousUserId && initial.user.id !== previousUserId) { clearSession(false, context); throw new ApiRequestError( '账户已变化,请重新操作', 401, 'ACCOUNT_CHANGED', ); } const send = async (accessToken: string) => { if (getAuthContext().key !== context.key) throw new ApiRequestError('接口环境已变化,请重新操作'); const result = await requestApi( path, { ...options, header: { ...header, Authorization: `Bearer ${accessToken}` }, }, context, ); if ( getAuthContext().key !== context.key || cachedServerUser()?.id !== initial.user.id ) throw new ApiRequestError('账户状态已变化,请重新操作'); return result; }; try { return await send(initial.accessToken); } catch (error) { if (!(error instanceof ApiRequestError) || error.statusCode !== 401) throw error; if (!recoverable.has(error.reason)) { if (blocked.has(error.reason)) rejectSession(context, initial.accessToken, true); throw error; } const renewed = await recoverSession(context, initial); if (renewed.user.id !== initial.user.id) { clearSession(false, context); throw new ApiRequestError( '账户已变化,请重新操作', 401, 'ACCOUNT_CHANGED', ); } try { return await send(renewed.accessToken); } catch (second) { if (second instanceof ApiRequestError && second.statusCode === 401) rejectSession( context, renewed.accessToken, blocked.has(second.reason), ); throw second; } } }