namecheap/ts/http-client.ts

159 lines
4.6 KiB
TypeScript
Raw Permalink Normal View History

2025-04-02 15:19:18 +00:00
/**
* 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<T extends IApiResponse>(
command: string,
params: Record<string, string | number | boolean> = {}
): Promise<T> {
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<T>(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<T>(response: AxiosResponse): Promise<T> {
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);
}
}