UNPKG

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
"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;