@montarist/nilvera-api
Version:
An unofficial SDK for integrating with Nilvera e-Invoice, e-Archive services
235 lines (234 loc) • 9.17 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiClient = void 0;
const axios_1 = __importDefault(require("axios"));
const nilvera_api_error_1 = require("../constants/errors/nilvera-api.error");
const types_1 = require("../types");
const DEFAULT_RETRY_CONFIG = {
maxRetries: 3,
retryDelay: 1000, // 1 second
retryableStatuses: [408, 429, 500, 502, 503, 504] // Common retryable status codes
};
/**
* @class ApiClient
* @description A client for making HTTP requests with automatic token, API key handling
*/
class ApiClient {
/**
* @constructor
* @param {string} baseUrl - The base URL for all API requests
* @param {string} apiKey - The API key for authentication
* @param {RetryConfig} [retryConfig] - Optional retry configuration
*/
constructor(baseUrl, apiKey, mode = types_1.Mode.DEVELOPMENT, retryConfig) {
this.apiKey = apiKey;
this.lastCurlCommand = '';
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
this.mode = mode;
this.client = axios_1.default.create({
baseURL: baseUrl,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
});
this.setupInterceptors();
}
/**
* @private
* @method sleep
* @description Utility method to pause execution
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* @private
* @method shouldRetry
* @description Determines if a request should be retried based on the error
*/
shouldRetry(error, attemptNumber) {
if (attemptNumber >= this.retryConfig.maxRetries) {
return false;
}
if (!error.response) {
// Network errors should be retried
return true;
}
return this.retryConfig.retryableStatuses.includes(error.response.status);
}
/**
* @private
* @method executeWithRetry
* @description Executes a request with retry logic
*/
async executeWithRetry(operation, operationName) {
let lastError = null;
let attempt = 1;
while (attempt <= this.retryConfig.maxRetries) {
try {
return await operation();
}
catch (error) {
if (error instanceof nilvera_api_error_1.NilveraApiError) {
throw error;
}
lastError = error;
if (!this.shouldRetry(lastError, attempt)) {
break;
}
if (this.mode === types_1.Mode.DEVELOPMENT) {
console.log(`Attempt ${attempt} failed for ${operationName}. Retrying in ${this.retryConfig.retryDelay}ms...`);
}
await this.sleep(this.retryConfig.retryDelay);
attempt++;
}
}
if (lastError) {
if (this.mode === types_1.Mode.DEVELOPMENT) {
console.log(`All retry attempts failed for ${operationName}`);
}
throw this.handleAxiosError(lastError);
}
throw new Error('Unexpected error occurred');
}
/**
* @private
* @method handleAxiosError
* @description Handles Axios errors and converts them to NilveraApiError
*/
handleAxiosError(error) {
if (error.response) {
// Server responded with error status
const { data, status, config } = error.response;
throw new nilvera_api_error_1.NilveraApiError(data?.message || error.message, status, data?.code, data, config?.url, config?.data ? JSON.parse(config.data) : undefined, this.lastCurlCommand);
}
else if (error.request) {
// Request was made but no response received
throw new nilvera_api_error_1.NilveraApiError('No response received from server', 0, 'NO_RESPONSE', error.request, undefined, undefined, this.lastCurlCommand);
}
else {
// Error in request configuration
throw new nilvera_api_error_1.NilveraApiError(error.message, 0, 'REQUEST_ERROR', undefined, undefined, undefined, this.lastCurlCommand);
}
}
/**
* @private
* @method generateCurlCommand
* @description Generates a curl command from the request configuration
*/
generateCurlCommand(config) {
const method = config.method?.toUpperCase();
const url = `${config.baseURL}${config.url}`;
const headers = {
...config.headers,
Authorization: `Bearer ${this.apiKey}`,
};
const data = config.data ? JSON.stringify(config.data, null, 4) : null;
let curl = `curl --location --request ${method} '${url}' \\`;
Object.entries(headers).forEach(([key, value]) => {
curl += `\n--header '${key}: ${value}' \\`;
});
if (data) {
curl += `\n--data-raw '${data}'`;
}
return curl;
}
/**
* @private
* @method setupInterceptors
* @description Configures request interceptors for authentication headers and curl command generation
*/
setupInterceptors() {
this.client.interceptors.request.use((config) => {
const headers = {
...config.headers,
Authorization: `Bearer ${this.apiKey}`,
};
Object.entries(headers).forEach(([key, value]) => {
config.headers.set(key, value);
});
this.lastCurlCommand = this.generateCurlCommand(config);
if (this.mode === types_1.Mode.DEVELOPMENT) {
console.log('Request Curl Command:', this.lastCurlCommand);
}
return config;
});
this.client.interceptors.response.use((response) => {
if (this.mode === types_1.Mode.DEVELOPMENT) {
console.log('Response Status:', response.status);
}
return {
...response,
data: {
data: response.data,
curlCommand: this.lastCurlCommand
}
};
}, error => this.handleAxiosError(error));
}
/**
* @method get
* @description Performs a GET request to the specified URL with retry mechanism
* @template T - The expected response data type
* @param {string} url - The endpoint URL
* @param {Record<string, any>} [params] - Optional query parameters
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration
* @returns {Promise<ApiResponse<T>>} The response data with curl command
*/
async get(url, params, config) {
return this.executeWithRetry(async () => {
const response = await this.client.get(url, { params, ...config });
return response.data;
}, `GET ${url}`);
}
/**
* @method post
* @description Performs a POST request to the specified URL with retry mechanism
* @template T - The expected response data type
* @param {string} url - The endpoint URL
* @param {any} [data] - The request body
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration
* @returns {Promise<ApiResponse<T>>} The response data with curl command
*/
async post(url, data, config) {
return this.executeWithRetry(async () => {
const response = await this.client.post(url, data, config);
return response.data;
}, `POST ${url}`);
}
/**
* @method put
* @description Performs a PUT request to the specified URL with retry mechanism
* @template T - The expected response data type
* @param {string} url - The endpoint URL
* @param {any} [data] - The request body
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration
* @returns {Promise<ApiResponse<T>>} The response data with curl command
*/
async put(url, data, config) {
return this.executeWithRetry(async () => {
const response = await this.client.put(url, data, config);
return response.data;
}, `PUT ${url}`);
}
/**
* @method delete
* @description Performs a DELETE request to the specified URL with retry mechanism
* @template T - The expected response data type
* @param {string} url - The endpoint URL
* @param {any} [data] - Optional request body
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration
* @returns {Promise<ApiResponse<T>>} The response data with curl command
*/
async delete(url, data, config) {
return this.executeWithRetry(async () => {
const response = await this.client.delete(url, { data, ...config });
return response.data;
}, `DELETE ${url}`);
}
}
exports.ApiClient = ApiClient;