159 lines
4.6 KiB
TypeScript
159 lines
4.6 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|
|
}
|