Files
creator-hub/web/src/requestErrorConfig.ts
T

53 lines
1.9 KiB
TypeScript

// 对齐 web.archived dataProvider 的请求语义(fetch → Basic Auth → 401 全局登出)。
export const AUTH_STORAGE_KEY = 'creatorhub.auth';
export class RequestError extends Error {
status: number;
body?: unknown;
constructor(message: string, status: number, body?: unknown) {
super(message);
this.status = status;
this.body = body;
}
}
export function unauthorized(): never {
localStorage.removeItem(AUTH_STORAGE_KEY);
if (window.location.pathname !== '/login') {
window.history.replaceState({}, '', '/login');
window.dispatchEvent(new PopStateEvent('popstate'));
}
throw new RequestError('认证已过期,请重新登录', 401);
}
export async function request(path: string, options: RequestInit = {}): Promise<any> {
const auth = localStorage.getItem(AUTH_STORAGE_KEY);
const headers: Record<string, string> = { ...(options.headers as Record<string, string>) };
if (auth) headers.Authorization = `Basic ${btoa(auth)}`;
let response: Response;
try {
response = await fetch(`/api${path}`, { ...options, headers });
} catch (networkError: any) {
throw new RequestError(`网络请求失败:${networkError?.message ?? networkError}`, 0);
}
if (response.status === 401) unauthorized();
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new RequestError(body.error || `请求失败 (${response.status})`, response.status, body);
}
if (response.status === 204) return null;
const contentType = response.headers?.get?.('content-type') || '';
if (contentType.toLowerCase().includes('text/html')) {
throw new RequestError('API 返回了非 JSON 响应,请检查控制面路由', response.status);
}
return response.json();
}
export function jsonOptions(method: string, data: unknown): RequestInit {
return {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
};
}