| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- 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 rawApiKey = localStorage.getItem('kb_api_key');
- const rawToken = localStorage.getItem('authToken') || localStorage.getItem('token');
- const activeTenantId = localStorage.getItem('kb_active_tenant_id');
- const language = localStorage.getItem('userLanguage') || 'ja';
- // Helper to filter out invalid values
- const isValid = (val: string | null) => {
- if (!val) return false;
- const v = val.trim().toLowerCase();
- return v !== '' && v !== 'undefined' && v !== 'null' && v !== '[object object]';
- };
- const apiKey = isValid(rawApiKey) ? rawApiKey : null;
- const token = isValid(rawToken) ? rawToken : null;
- 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}`;
- }
- // Final fail-safe: Ensure no header is 'undefined' string
- Object.keys(headers).forEach(key => {
- if (headers[key]?.toLowerCase().includes('undefined')) {
- delete headers[key];
- }
- });
- if (activeTenantId && isValid(activeTenantId)) {
- headers['x-tenant-id'] = activeTenantId;
- }
- // DEBUG: Only log first few chars
- console.log('[ApiClient] Auth Headers:', {
- hasApiKey: !!headers['x-api-key'],
- hasAuth: !!headers['Authorization'],
- authPreview: headers['Authorization']?.substring(0, 20),
- tenantId: headers['x-tenant-id']
- });
- return headers;
- }
- // New API call method, returns { data, status }
- async get<T = any>(url: string): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'GET',
- headers: this.getAuthHeaders(),
- });
- const data = await response.json();
- if (response.status === 401) {
- this.handleUnauthorized();
- throw new Error(data.message || 'Unauthorized');
- }
- if (!response.ok) {
- throw new Error(data.message || 'Request failed');
- }
- return { data, status: response.status };
- }
- 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,
- });
- const data = await response.json();
- if (response.status === 401) {
- this.handleUnauthorized();
- throw new Error(data.message || 'Unauthorized');
- }
- if (!response.ok) {
- throw new Error(data.message || 'Request failed');
- }
- return { data, status: response.status };
- }
- 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,
- });
- const data = await response.json();
- if (response.status === 401) {
- this.handleUnauthorized();
- throw new Error(data.message || 'Unauthorized');
- }
- if (!response.ok) {
- throw new Error(data.message || 'Request failed');
- }
- return { data, status: response.status };
- }
- async delete<T = any>(url: string): Promise<ApiResponse<T>> {
- const response = await fetch(`${this.baseURL}${url}`, {
- method: 'DELETE',
- headers: this.getAuthHeaders(),
- });
- const data = await response.json();
- if (response.status === 401) {
- this.handleUnauthorized();
- throw new Error(data.message || 'Unauthorized');
- }
- if (!response.ok) {
- throw new Error(data.message || 'Request failed');
- }
- return { data, status: response.status };
- }
- // 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);
- Object.entries(authHeaders).forEach(([k, v]) => headers.set(k, v));
- const language = localStorage.getItem('userLanguage') || 'ja';
- headers.set('x-user-language', language);
- 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) {
- this.handleUnauthorized();
- 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);
|