53 lines
1.9 KiB
TypeScript
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),
|
|
};
|
|
}
|