UNPKG

@dima_aryze/reforge

Version:

TypeScript/JavaScript SDK for Reforge - Cross-chain token operations

253 lines 8.08 kB
/** * Base HTTP client implementation for the Reforge SDK */ import axios from 'axios'; import { ReforgeError, ReforgeValidationError } from '../errors'; import { createLogger, deepMerge, generateRequestId, sanitizeUrl } from '../utils'; import { ErrorTransformer } from './errors'; import { RequestInterceptor } from './interceptors'; import { RetryHandler } from './retry'; /** * Default configuration for HTTP requests */ const DEFAULT_CONFIG = { timeout: 30000, headers: {}, debug: false, retry: { attempts: 3, delay: 1000, maxDelay: 30000, backoffMultiplier: 2, }, }; /** * Base HTTP client with comprehensive error handling and retry logic */ export class BaseHttpClient { constructor(options) { Object.defineProperty(this, "httpClient", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "eventListeners", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "errorTransformer", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "retryHandler", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "apiKey", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.validateOptions(options); // Merge configuration with defaults this.config = deepMerge({}, DEFAULT_CONFIG, { baseUrl: options.baseUrl, timeout: options.timeout, headers: options.headers, debug: options.debug, retry: options.retry, }); this.apiKey = options.apiKey; this.logger = createLogger(this.config.debug ? 'debug' : 'warn'); this.errorTransformer = new ErrorTransformer(); this.retryHandler = new RetryHandler(this.config.retry, this.logger); // Initialize HTTP client this.httpClient = options.httpClient || this.createHttpClient(); this.logger.info('HTTP client initialized', { baseUrl: this.config.baseUrl, hasApiKey: !!this.apiKey, debug: this.config.debug, }); } /** * Validate constructor options */ validateOptions(options) { if (!options.apiKey || typeof options.apiKey !== 'string') { throw new ReforgeValidationError('API key is required and must be a string'); } if (!options.baseUrl || typeof options.baseUrl !== 'string') { throw new ReforgeValidationError('Base URL is required and must be a string'); } try { new URL(options.baseUrl); } catch { throw new ReforgeValidationError('Base URL must be a valid URL'); } } /** * Create and configure the Axios instance */ createHttpClient() { const client = axios.create({ baseURL: sanitizeUrl(this.config.baseUrl), timeout: this.config.timeout, headers: { 'Content-Type': 'application/json', 'User-Agent': 'reforge-sdk/1.0.0', ...this.config.headers, }, }); // Setup interceptors const requestInterceptor = new RequestInterceptor(this.apiKey, this.logger); requestInterceptor.setup(client); // Response interceptor client.interceptors.response.use(response => { this.logger.debug('Response received', { status: response.status, url: response.config.url, }); return response; }, error => { const transformedError = this.errorTransformer.transform(error); this.logger.error('Response error', transformedError.message); return Promise.reject(transformedError); }); return client; } /** * Emit an event to all registered listeners */ emitEvent(type, data) { const listeners = this.eventListeners.get(type) || []; const eventData = { type, timestamp: new Date(), data, }; listeners.forEach(callback => { try { callback(eventData); } catch (error) { this.logger.error('Event listener error', error); } }); } /** * Make HTTP request with comprehensive error handling and retry logic */ async makeRequest(method, path, data, options = {}) { return this.retryHandler.execute(async (attempt) => { this.emitEvent('request:start', { method, path, attempt }); try { const axiosConfig = { method, url: path, timeout: options.timeout || this.config.timeout, headers: { ...options.headers, ...(options.skipAuth ? { 'skip-auth': 'true' } : {}), 'X-Request-ID': generateRequestId(), }, }; if (data) { if (method === 'GET') { axiosConfig.params = data; } else { axiosConfig.data = data; } } const response = await this.httpClient.request(axiosConfig); this.emitEvent('request:success', { method, path, attempt, status: response.status, }); return response.data; } catch (error) { const transformedError = error instanceof ReforgeError ? error : this.errorTransformer.transform(error); this.emitEvent('request:error', { method, path, attempt, error: transformedError, }); throw transformedError; } }, options.retry); } /** * Update the API key */ setApiKey(apiKey) { if (typeof apiKey !== 'string' || !apiKey.trim()) { throw new ReforgeValidationError('API key must be a non-empty string'); } this.apiKey = apiKey.trim(); this.logger.info('API key updated'); } /** * Get the current API key */ getApiKey() { return this.apiKey; } /** * Add event listener */ on(event, callback) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event).push(callback); } /** * Remove event listener */ off(event, callback) { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); } } } /** * Remove all event listeners */ removeAllListeners(event) { if (event) { this.eventListeners.delete(event); } else { this.eventListeners.clear(); } } } //# sourceMappingURL=base.js.map