A social knowledge tool for researchers built on ATProto
at development 2.0 kB view raw
1import { ApiError, ApiErrorResponse } from '../types/errors'; 2import { TokenManager } from '../../services/TokenManager'; 3 4export abstract class BaseClient { 5 constructor( 6 protected baseUrl: string, 7 protected tokenManager?: TokenManager, 8 ) {} 9 10 protected async request<T>( 11 method: string, 12 endpoint: string, 13 data?: any, 14 ): Promise<T> { 15 const makeRequest = async (): Promise<T> => { 16 const url = `${this.baseUrl}${endpoint}`; 17 const token = this.tokenManager 18 ? await this.tokenManager.getAccessToken() 19 : null; 20 21 const headers: Record<string, string> = { 22 'Content-Type': 'application/json', 23 }; 24 25 if (token) { 26 headers['Authorization'] = `Bearer ${token}`; 27 } 28 29 const config: RequestInit = { 30 method, 31 headers, 32 }; 33 34 if ( 35 data && 36 (method === 'POST' || method === 'PUT' || method === 'PATCH') 37 ) { 38 config.body = JSON.stringify(data); 39 } 40 41 const response = await fetch(url, config); 42 return this.handleResponse<T>(response); 43 }; 44 45 try { 46 return await makeRequest(); 47 } catch (error) { 48 // Handle 401/403 errors with automatic token refresh (only if we have a token manager) 49 if ( 50 this.tokenManager && 51 error instanceof ApiError && 52 (error.status === 401 || error.status === 403) 53 ) { 54 return this.tokenManager.handleAuthError(makeRequest); 55 } 56 throw error; 57 } 58 } 59 60 private async handleResponse<T>(response: Response): Promise<T> { 61 if (!response.ok) { 62 let errorData: ApiErrorResponse; 63 64 try { 65 errorData = await response.json(); 66 } catch { 67 errorData = { 68 message: response.statusText || 'Unknown error', 69 }; 70 } 71 72 throw new ApiError( 73 errorData.message, 74 response.status, 75 errorData.code, 76 errorData.details, 77 ); 78 } 79 80 return response.json(); 81 } 82}