| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import { API_BASE_URL } from '../utils/constants';
- interface ApiResponse<T = any> {
- data: T;
- status: number;
- }
- class ApiClient {
- private baseURL: string;
- constructor(baseURL: string) {
- this.baseURL = baseURL;
- }
- private getAuthHeaders(): Record<string, string> {
- const apiKey = localStorage.getItem('kb_api_key');
- const activeTenantId = localStorage.getItem('kb_active_tenant_id');
- const token = localStorage.getItem('authToken') || localStorage.getItem('token');
- const language = localStorage.getItem('userLanguage') || 'zh';
- const headers: Record<string, string> = {
- 'Content-Type': 'application/json',
- 'x-user-language': language,
- };
- if (apiKey) {
- if (apiKey.startsWith('kb_')) {
- headers['x-api-key'] = apiKey;
- } else {
- headers['Authorization'] = `Bearer ${apiKey}`;
- }
- } else if (token) {
- headers['Authorization'] = `Bearer ${token}`;
- }
- if (activeTenantId && activeTenantId !== 'undefined' && activeTenantId !== 'null') {
- headers['x-tenant-id'] = activeTenantId;
- }
- return headers;
- }
- private async handleResponse<T>(response: Response): Promise<ApiResponse<T>> {
- const text = await response.text();
- let data: any;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- data = null;
- }
- if (!response.ok) {
- throw new Error(data?.message || text || 'Request failed');
- }
- return { data: data as T, status: response.status };
- }
- // 新しい API 呼び出し方法、{ data, status } を返す
- async get<T = any>(url: string): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'GET',
- headers: this.getAuthHeaders(),
- });
- return this.handleResponse<T>(response);
- }
- async post<T = any>(url: string, body?: any): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'POST',
- headers: this.getAuthHeaders(),
- body: body ? JSON.stringify(body) : undefined,
- });
- return this.handleResponse<T>(response);
- }
- async put<T = any>(url: string, body?: any): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'PUT',
- headers: this.getAuthHeaders(),
- body: body ? JSON.stringify(body) : undefined,
- });
- return this.handleResponse<T>(response);
- }
- async patch<T = any>(url: string, body?: any): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'PATCH',
- headers: this.getAuthHeaders(),
- body: body ? JSON.stringify(body) : undefined,
- });
- return this.handleResponse<T>(response);
- }
- async delete<T = any>(url: string): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'DELETE',
- headers: this.getAuthHeaders(),
- });
- return this.handleResponse<T>(response);
- }
-
- // New methods for special formats
- async getBlob(url: string): Promise<Blob> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'GET',
- headers: this.getAuthHeaders(),
- });
- if (!response.ok) {
- throw new Error('Request failed');
- }
- return await response.blob();
- }
- async postMultipart<T = any>(url: string, formData: FormData): Promise<ApiResponse<T>> {
- const headers = this.getAuthHeaders();
- // Remove Content-Type to let the browser set it with the correct boundary
- delete headers['Content-Type'];
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'POST',
- headers,
- body: formData,
- });
- return this.handleResponse<T>(response);
- }
-
- // Legacy compatibility method — returns raw Response for streaming and other special cases
- async request(path: string, options: RequestInit = {}): Promise<Response> {
- const authHeaders = this.getAuthHeaders();
- const headers = new Headers(options.headers);
-
- // Merge auth headers into request headers
- Object.entries(authHeaders).forEach(([key, value]) => {
- headers.set(key, value);
- });
- let url = path;
- if (!path.startsWith('http')) {
- const cleanPath = path.startsWith('/') ? path : `/${path}`;
- url = `${this.baseURL}${cleanPath}`;
- }
- const response = await fetch(url, {
- ...options,
- headers,
- });
- if (response.status === 401) {
- localStorage.removeItem('kb_api_key');
- localStorage.removeItem('authToken');
- window.location.href = '/login';
- throw new Error('Unauthorized');
- }
- return response;
- }
- private handleUnauthorized() {
- console.warn('[ApiClient] 401 Unauthorized detected. Cleaning up and redirecting to login...');
- localStorage.removeItem('kb_api_key');
- localStorage.removeItem('authToken');
- localStorage.removeItem('token');
- localStorage.removeItem('kb_active_tenant_id');
- // Only redirect if we are not already on the login page
- if (window.location.pathname !== '/login') {
- window.location.href = '/login';
- }
- }
- }
- export const apiClient = new ApiClient(API_BASE_URL);
|