/** * Namecheap API Client - HTTP Client */ import axios from 'axios'; import type { AxiosInstance, AxiosResponse } from 'axios'; import { parseStringPromise } from 'xml2js'; import { NamecheapConfig } from './config.js'; import type { IApiResponse, IErrorResponse } from './types.js'; export class HttpClient { private client: AxiosInstance; private config: NamecheapConfig; /** * Create a new HTTP client for Namecheap API * @param config Namecheap API configuration */ constructor(config: NamecheapConfig) { this.config = config; // Initialize axios instance with default config this.client = axios.create({ baseURL: config.baseUrl, timeout: config.timeout, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/xml' } }); // Add response interceptor for global error handling this.client.interceptors.response.use( response => response, error => this.handleAxiosError(error) ); } /** * Send a request to the Namecheap API * @param command API command to execute * @param params Additional parameters * @returns Parsed API response */ async request( command: string, params: Record = {} ): Promise { try { // Get base parameters that all requests need const baseParams = this.config.getBaseParams(command); // Merge base params with additional params const requestParams = { ...baseParams, ...params }; // Send request to the API const response = await this.client.get('', { params: requestParams }); // Parse XML response to JavaScript object const parsedResponse = await this.parseXmlResponse(response); // Check for API errors this.checkApiErrors(parsedResponse); return parsedResponse; } catch (error) { if (error instanceof Error) { throw error; } else { throw new Error('Unknown error occurred'); } } } /** * Parse XML response to JavaScript object * @param response Axios response object * @returns Parsed response */ private async parseXmlResponse(response: AxiosResponse): Promise { try { const xmlData = response.data; // Parse XML to JavaScript object with some options for better formatting const result = await parseStringPromise(xmlData, { explicitArray: true, explicitRoot: true, mergeAttrs: false, attrkey: '$' }); return result as T; } catch (error) { throw new Error(`Failed to parse XML response: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Check for API errors in the response * @param response Parsed API response * @throws Error if API returned an error */ private checkApiErrors(response: IApiResponse): void { // Check if response status is ERROR if (response.ApiResponse.$.Status === 'ERROR') { const errors = response.ApiResponse.Errors[0]; if (errors && 'Error' in errors) { const errorMessages = errors.Error; throw new Error(`Namecheap API Error: ${errorMessages.join(', ')}`); } else { throw new Error('Namecheap API returned an error without details'); } } } /** * Handle axios errors * @param error Axios error object * @throws Formatted error */ private handleAxiosError(error: any): never { let errorResponse: IErrorResponse = { message: 'Request to Namecheap API failed' }; if (error.response) { // Server responded with a status code outside of 2xx range errorResponse.message = `Namecheap API request failed with status ${error.response.status}`; // Try to parse error response if it's XML if (error.response.data && typeof error.response.data === 'string') { try { const xmlError = error.response.data; errorResponse.message = `API Error: ${xmlError}`; } catch (parseError) { // Couldn't parse error response, use default message } } } else if (error.request) { // Request was made but no response received errorResponse.message = 'No response received from Namecheap API'; if (error.code === 'ECONNABORTED') { errorResponse.message = `Request timed out after ${this.config.timeout}ms`; } } else { // Error setting up the request errorResponse.message = error.message || 'Error setting up the request'; } throw new Error(errorResponse.message); } }