akua-sdk
Version:
TypeScript SDK for Akua Acquiring Processor
193 lines (192 loc) • 6.97 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const axios_1 = __importDefault(require("axios"));
/**
* HttpClient is a class that provides a client for making HTTP requests to the Akua API.
* It handles the creation of Axios instances for the API endpoints,
* and provides methods for making GET, POST, PUT, PATCH, and DELETE requests.
* It also includes interceptors for error handling and environment validation.
*/
class HttpClient {
client;
pointsClient;
baseUrl;
pointsBaseUrl;
environment;
clientConfig;
tokenData;
tokenGenerationTime;
isRefreshingToken = false;
constructor(config) {
this.environment = config.environment;
const isProduction = config.environment === 'production';
this.baseUrl = isProduction ? 'https://api.akua.la' : 'https://sandbox.akua.la';
this.pointsBaseUrl = isProduction
? 'https://api.akua.la/points-api'
: 'https://sandbox.akua.la/points-api';
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
};
this.client = axios_1.default.create({
baseURL: this.baseUrl,
headers,
});
this.pointsClient = axios_1.default.create({
baseURL: this.pointsBaseUrl,
headers,
});
this.clientConfig = config;
this.setupInterceptors(this.client);
this.setupInterceptors(this.pointsClient);
}
async refreshToken() {
// If we're already refreshing the token, don't do it again
if (this.isRefreshingToken) {
return;
}
try {
this.isRefreshingToken = true;
const response = await this.client.post('/oauth/token', {
grant_type: 'client_credentials',
audience: this.baseUrl,
client_id: this.clientConfig.clientId,
client_secret: this.clientConfig.clientSecret,
});
this.tokenData = response.data;
this.tokenGenerationTime = Date.now();
}
finally {
this.isRefreshingToken = false;
}
}
setupInterceptors(client) {
client.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error.response) {
const errorData = error.response.data || {};
const details = Object.entries(errorData)
.filter(([key]) => key !== 'message')
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
const errorResponse = {
data: null,
success: false,
error: {
code: String(error.response.status),
message: errorData.message || 'An unknown error occurred',
details: Object.keys(details).length > 0 ? details : undefined,
},
};
return Promise.reject(errorResponse);
}
const networkError = {
data: null,
success: false,
error: {
code: 'NETWORK_ERROR',
message: error.message || 'Network error occurred',
},
};
return Promise.reject(networkError);
});
client.interceptors.request.use(async (config) => {
// Refresh the token if it's about to expire, with a 5 minute buffer
const shouldRefreshToken = !this.isRefreshingToken &&
(!this.tokenData ||
Date.now() > this.tokenGenerationTime + this.tokenData.expires_in * 1000 - 5 * 60 * 1000);
if (shouldRefreshToken) {
await this.refreshToken();
}
if (this.tokenData) {
config.headers.Authorization = `Bearer ${this.tokenData.access_token}`;
}
return config;
});
}
getClient(path) {
return path.startsWith('/points-api') ? this.pointsClient : this.client;
}
async get(path, config) {
const client = this.getClient(path);
const response = await client.get(path, config);
return {
data: response.data,
success: true,
};
}
async post(path, data, config) {
const client = this.getClient(path);
const response = await client.post(path, data, config);
return {
data: response.data,
success: true,
};
}
async put(path, data, config) {
const client = this.getClient(path);
const response = await client.put(path, data, config);
return {
data: response.data,
success: true,
};
}
async patch(path, data, config) {
const client = this.getClient(path);
const response = await client.patch(path, data, config);
return {
data: response.data,
success: true,
};
}
async delete(path, config) {
const client = this.getClient(path);
const response = await client.delete(path, config);
return {
data: response.data,
success: true,
};
}
/**
* Gets the audience URL for the current environment
* @returns {string} The audience URL
*/
getAudienceUrl() {
return this.baseUrl;
}
/**
* Gets the current environment (sandbox or production)
* @returns {Environment} The current environment
*/
getEnvironment() {
return this.environment;
}
/**
* Validates if the current environment matches the required environment
* @param {Environment} requiredEnvironment - The environment that is required for the operation
* @param {string} operationName - The name of the operation being validated
* @returns {ApiResponse<T> | null} Returns an error response if validation fails, null if validation passes
*/
validateEnvironment(requiredEnvironment, operationName) {
if (this.environment !== requiredEnvironment) {
return {
data: null,
success: false,
error: {
code: 'ENVIRONMENT_ERROR',
message: `The ${operationName} method is only available in the ${requiredEnvironment} environment`,
details: {
environment: this.environment,
required_environment: requiredEnvironment,
},
},
};
}
return null;
}
}
exports.HttpClient = HttpClient;