UNPKG

magically-sdk

Version:

Official SDK for Magically - Build mobile apps with AI

135 lines (118 loc) 4.05 kB
import { Logger } from './Logger'; import { SDKConfig } from './types'; export interface RequestOptions { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; headers?: Record<string, string>; body?: any; operation?: string; // For logging context } export class APIClient { private config: SDKConfig; private logger: Logger; private baseUrl: string; private apiKey: string | null = null; constructor(config: SDKConfig, loggerPrefix: string = 'MagicallyAPI') { this.config = config; this.logger = new Logger(config.debug || false, loggerPrefix); this.baseUrl = config.apiUrl || 'https://trymagically.com'; // Check for API key in environment (for edge functions) if (typeof globalThis !== 'undefined' && 'MAGICALLY_API_KEY' in globalThis) { this.apiKey = (globalThis as any).MAGICALLY_API_KEY; this.logger.info('API key detected in environment - using API key authentication'); } } /** * Make an authenticated API request with automatic logging */ async request<T = any>( endpoint: string, options: RequestOptions, token?: string | null ): Promise<T> { const startTime = Date.now(); let requestId: string = ''; const url = `${this.baseUrl}${endpoint}`; const headers: Record<string, string> = { 'Content-Type': 'application/json', ...options.headers, }; // Use API key if available (edge environment), otherwise use provided token if (this.apiKey) { headers['Authorization'] = `Bearer ${this.apiKey}`; this.logger.debug('Using API key authentication for request'); } else if (token) { headers['Authorization'] = `Bearer ${token}`; } const requestConfig: RequestInit = { method: options.method, headers, body: options.body ? JSON.stringify(options.body) : undefined, }; // Log the request requestId = this.logger.networkRequest(options.method, url, { headers, body: options.body, operation: options.operation }); try { const response = await fetch(url, requestConfig); const responseData = await response.json(); const duration = Date.now() - startTime; if (!response.ok) { // Log error response this.logger.networkError(requestId, responseData, { duration, operation: options.operation }); // Throw structured error this.handleAPIError(responseData, `${options.operation || 'Request'} failed`); } // Log successful response this.logger.networkResponse(requestId, { status: response.status, statusText: response.statusText, duration, data: this.sanitizeResponseData(responseData, options.operation), operation: options.operation }); return responseData; } catch (error) { // Log network errors if (requestId) { this.logger.networkError(requestId, error, { duration: Date.now() - startTime, operation: options.operation }); } throw error; } } /** * Check if running in edge environment (has API key) */ isEdgeEnvironment(): boolean { return this.apiKey !== null; } /** * Sanitize response data for logging (avoid logging large arrays) */ private sanitizeResponseData(data: any, operation?: string): any { if (operation === 'query' && data.data && Array.isArray(data.data)) { return { ...data, dataCount: data.data.length, data: '[Array of items]' }; } return data; } /** * Handle API errors with structured error information */ private handleAPIError(errorData: any, fallbackMessage: string): never { // Create an error with the response data attached for the Logger const error = new Error(errorData.error || errorData.message || fallbackMessage) as any; error.responseData = errorData; // Preserve the original error response for Logger throw error; } }