bc-node-sdk
Version:
BetterCommerce's NodeJS SDK encapsulates the base framework for all the Next.js applications.
251 lines (250 loc) • 13.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// Package Imports
const agentkeepalive_1 = __importDefault(require("agentkeepalive"));
// Other Imports
const api_1 = __importDefault(require("../api"));
const parse_util_1 = __importDefault(require("../../utils/parse-util"));
const cipher_util_1 = __importDefault(require("../../utils/cipher-util"));
const app_util_1 = __importDefault(require("../../utils/app-util"));
let keepAliveAgent, httpsKeepAliveAgent;
const keepAliveAgentConfig = process.env.KEEP_ALIVE_AGENT_CONFIG ? parse_util_1.default.tryParseJson(cipher_util_1.default.decrypt(process.env.KEEP_ALIVE_AGENT_CONFIG)) : null;
if (keepAliveAgentConfig) {
// Create a reusable connection instance that can be passed around to different controllers
keepAliveAgent = new agentkeepalive_1.default({
maxSockets: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.maxSockets,
maxFreeSockets: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.maxFreeSockets,
timeout: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.timeout,
freeSocketTimeout: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.freeSocketTimeout, // free socket keepalive for 30 seconds
});
// HTTPS agent
httpsKeepAliveAgent = new agentkeepalive_1.default.HttpsAgent({
maxSockets: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.maxSockets,
maxFreeSockets: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.maxFreeSockets,
timeout: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.timeout,
freeSocketTimeout: keepAliveAgentConfig === null || keepAliveAgentConfig === void 0 ? void 0 : keepAliveAgentConfig.freeSocketTimeout, // free socket keepalive for 30 seconds
});
}
/**
* Class {@link ApiService} is a concrete implementation of the {@link IApiService} interface.
* It encapsulates the logic to make API calls to the {@link https://localhost:3000/api/v1|API server} and handles token retrieval and refresh.
*
* @see https://github.com/axios/axios
* @see https://github.com/nodejs/node/blob/master/doc/api/https.md#httpsrequesturl-options-callback
* @see https://github.com/nodejs/node/blob/master/doc/api/http.md#httprequesturl-options-callback
*/
class ApiService {
/**
* Constructor for the ApiService class.
* It initializes the API base URL and if specified, the keep-alive agent configuration.
* It also creates an instance of the axios client and sets up the request and response interceptors.
* The request interceptor adds the authorization header to the request if the token is available.
* The response interceptor handles 401 responses by refreshing the token and retrying the original request.
* If the token refresh fails, it rejects the promise with the original error.
* @param {string} clientId - The client ID to use for token refresh.
* @param {string} sharedSecret - The shared secret to use for token refresh.
* @param {string} apiBaseUrl - The base URL of the API server.
* @param {string} authBaseUrl - The base URL of the authorization server.
*/
constructor(clientId, sharedSecret, apiBaseUrl, authBaseUrl) {
this.authBaseUrl = null;
this.authToken = null;
this.tokenExpiration = null;
this.authBaseUrl = authBaseUrl;
if (keepAliveAgentConfig) {
this.axiosInstance = api_1.default.create({
// Create an agent for both HTTP and HTTPS
httpAgent: keepAliveAgent,
httpsAgent: httpsKeepAliveAgent,
baseURL: apiBaseUrl,
//timeout: 10000, // Optional: set a timeout for requests
});
}
else {
this.axiosInstance = api_1.default.create({
baseURL: apiBaseUrl,
//timeout: 10000, // Optional: set a timeout for requests
});
}
this.axiosInstance.interceptors.request.use((config) => {
if (this.authToken) {
//console.log(this.authToken)
config.headers['Authorization'] = `Bearer ${this.authToken}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
/**
* Creates an Axios response interceptor that handles 401 responses and
* refreshes the token by calling the /oAuth/token endpoint. It also logs the
* activity of the request and response.
*/
const createAxiosResponseInterceptor = () => {
const interceptor = this.axiosInstance.interceptors.response.use((response) => response, async (error) => {
var _a;
// Reject promise if usual error
if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
this.axiosInstance.interceptors.response.eject(interceptor);
const url = new URL('oAuth/token', this.authBaseUrl);
return this.axiosInstance({
url: url.href,
method: 'post',
data: `client_id=${clientId}&client_secret=${sharedSecret}&grant_type=client_credentials`,
}).then((res) => {
this.setAuthToken(res.data.access_token);
error.response.config.headers['Authorization'] =
'Bearer ' + res.data.access_token;
return this.axiosInstance(error.response.config);
}).catch((error) => {
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
/*if (error.response.status === 401) {
// Token might be expired, refresh token here
this.authToken = null;
this.tokenExpiration = null;
// Optionally, you could implement a token refresh mechanism here
}
return Promise.reject(error);
*/
});
};
createAxiosResponseInterceptor();
}
/**
* Returns an instance of the ApiService class, creating a new one if it doesn't exist already.
* @param {string} clientId - The client ID to use for authentication.
* @param {string} sharedSecret - The shared secret to use for authentication.
* @param {string} apiBaseUrl - The base URL of the API endpoints.
* @param {string} authBaseUrl - The base URL of the authentication endpoints.
* @returns {ApiService} The instance of the ApiService class.
*/
static getInstance(clientId, sharedSecret, apiBaseUrl, authBaseUrl) {
if (!ApiService.instance) {
ApiService.instance = new ApiService(clientId, sharedSecret, apiBaseUrl, authBaseUrl);
}
return ApiService.instance;
}
/**
* Sets the authentication token for the API service.
*
* @param {string} token - The authentication token to be set.
* @returns {Promise<void>} A promise that resolves when the token is set.
*/
async setAuthToken(token /*, expiresIn: number*/) {
this.authToken = token;
//this.tokenExpiration = Date.now() + expiresIn * 1000; // expiresIn is in seconds
}
/**
* Retrieves the authentication token for the API service.
* If the token does not exist or is expired, it refreshes the token.
*
* @returns {Promise<string | null>} A promise that resolves to the current authentication token or null if unavailable.
*/
async getAuthToken() {
if (!this.authToken || this.isTokenExpired()) {
await this.refreshToken();
}
return this.authToken;
}
/**
* Sends a GET request to the specified URL using the provided Axios request configuration.
*
* @template T - The expected response data type.
* @param {string} url - The URL to send the GET request to.
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration.
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to the Axios response.
*/
async get(url, config) {
app_util_1.default.logInfo({ url, config });
app_util_1.default.logInfo({ url, config }, '--- serviceInstanceApi ---');
return this.axiosInstance.get(url, config);
}
/**
* Sends a POST request to the specified URL using the provided Axios request configuration.
*
* @template T - The expected response data type.
* @param {string} url - The URL to send the POST request to.
* @param {any} [data] - The data to be sent as the request body.
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration.
* @returns {Promise<AxiosResponse<T>>} A promise that resolves to the Axios response.
*/
async post(url, data, config) {
app_util_1.default.logInfo({ url, data, config }, '--- serviceInstanceApi ---');
return this.axiosInstance.post(url, data, config);
}
/**
* Sends a PUT request to the specified URL using the provided Axios request configuration.
*
* @template T - The expected response data type.
* @param {string} url - The URL to send the PUT request to.
* @param {any} [data] - The data to be sent as the request body.
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration.
* @returns {Promise<AxiosResponse<T>>} A promise that resolves to the Axios response.
*/
async put(url, data, config) {
app_util_1.default.logInfo({ url, data, config }, '--- serviceInstanceApi ---');
return this.axiosInstance.put(url, data, config);
}
/**
* Sends a PATCH request to the specified URL using the provided Axios request configuration.
*
* @template T - The expected response data type.
* @param {string} url - The URL to send the PATCH request to.
* @param {any} [data] - The data to be sent as the request body.
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration.
* @returns {Promise<AxiosResponse<T>>} A promise that resolves to the Axios response.
*/
async patch(url, data, config) {
app_util_1.default.logInfo({ url, data, config }, '--- serviceInstanceApi ---');
return this.axiosInstance.patch(url, data, config);
}
/**
* Sends a DELETE request to the specified URL using the provided Axios request configuration.
*
* @template T - The expected response data type.
* @param {string} url - The URL to send the DELETE request to.
* @param {AxiosRequestConfig} [config] - Optional Axios request configuration.
* @returns {Promise<AxiosResponse<T>>} A promise that resolves to the Axios response.
*/
async delete(url, config) {
app_util_1.default.logInfo({ url, config }, '--- serviceInstanceApi ---');
return this.axiosInstance.delete(url, config);
}
/**
* Checks if the authentication token has expired.
* @returns {boolean} True if the token has expired, false otherwise.
*/
isTokenExpired() {
return this.tokenExpiration ? Date.now() >= this.tokenExpiration : true;
}
/**
* Refreshes the authentication token by making an API call to the /auth/refresh endpoint.
* The new token is then stored in the AuthService instance.
* @private
* @returns {Promise<void>} A promise that resolves when the token has been refreshed.
*/
async refreshToken() {
try {
// Example API call to refresh token
const response = await this.axiosInstance.post('/auth/refresh');
const newToken = response.data.token;
const expiresIn = response.data.expiresIn; // Token expiration time in seconds
await this.setAuthToken(newToken /*, expiresIn*/);
}
catch (error) {
console.error('Failed to refresh token:', error);
}
}
}
exports.default = ApiService;