UNPKG

@telstra/core

Version:
167 lines (166 loc) 6.54 kB
import { AuthManager } from '../../authentication/index.js'; import { Constants } from '../../constants/index.js'; import { MaxAuthRetryCountReachedError, MissingTokenDataError } from '../../errors/index.js'; import { Logger, LoggerMixin } from '../../logging/impl/index.js'; import axios from 'axios'; import { getErrorMessage } from '../../utils/index.js'; export class HttpClient extends LoggerMixin { instance; authManager; authRetryCount = 0; constructor(authConfig) { super(new Logger()); this.instance = axios.create({ baseURL: Constants.API_URL, }); this.authManager = new AuthManager(authConfig); this._handleRequest = this._handleRequest.bind(this); this._handleRequestError = this._handleRequestError.bind(this); this._handleResponse = this._handleResponse.bind(this); this._handleResponseError = this._handleResponseError.bind(this); this._initializeRequestInterceptor(); this._initializeResponseInterceptor(); } _initializeRequestInterceptor() { this.instance.interceptors.request.use(this._handleRequest, this._handleRequestError); } _initializeResponseInterceptor() { this.instance.interceptors.response.use(this._handleResponse, this._handleResponseError); } /** * @method _handleRequest * @description Handles the request by adding the necessary headers and checking if the token is expired * @param {AxiosRequestConfig} config * @returns {Promise<AxiosRequestConfig>} * @private * @async * @throws {MissingTokenDataError} * @instance */ async _handleRequest(config) { if (config.url === '/v2/oauth/token') return config; config.headers = config.headers || {}; config.headers['User-Agent'] = Constants.USER_AGENT; const authTokenData = await this.authManager.getAuthTokenData(); const isTokenExpired = this._isTokenExpired(authTokenData?.expires_at || new Date()); if (authTokenData && !isTokenExpired) { config.headers['Authorization'] = `Bearer ${authTokenData.access_token}`; } else { config.headers['Accept'] = `*/*`; config.headers['Content-Type'] = `application/x-www-form-urlencoded`; const authCredentials = await this.authManager.getCredentials(); const tokenData = await this._renewToken(authCredentials); if (!tokenData.access_token || !tokenData.expires_at) { throw new MissingTokenDataError(); } await this.authManager.setAuthTokenData(tokenData); config.headers['Authorization'] = `Bearer ${tokenData.access_token}`; } config.headers['Accept'] = `application/json`; config.headers['Content-Type'] = `application/json`; config.headers['Content-Language'] = `en-au`; return config; } /** * @method _isTokenExpired * @description Checks if the token is expired * @param expires_at {Date} The date the token expires * @returns {boolean} Returns a boolean value indicating whether the token is expired * @private * @async * @instance */ _isTokenExpired(expires_at) { return new Date() > expires_at; } /** * @method _handleRequestError * @description Handles the request error * @param {AxiosError} error * @returns {Promise<AxiosError>} * @private * @instance * @async * @throws {AxiosError} The error that occurred during the request */ async _handleRequestError(error) { this.logger.error('Error occurred during the request', getErrorMessage(error)); return Promise.reject(error); } /** * @method _handleResponse * @description Handles the response * @param {AxiosResponse} response * @returns {AxiosResponse} * @private * @instance * @async */ _handleResponse(response) { return response.data; } /** * @method _handleResponseError * @description Handles the response error * @param {AxiosError} error * @returns {Promise<AxiosError>} * @private * @instance * @async * @throws {AxiosError} The error that occurred during the response * @throws {MaxAuthRetryCountReachedError} */ async _handleResponseError(error) { const { response } = error; const originalRequest = error.config; if (response) { const { status } = response; if (status == 401) { if (this.authRetryCount >= Constants.MAX_AUTH_RETRY_COUNT) { throw new MaxAuthRetryCountReachedError(); } this.authRetryCount++; await this.authManager.setAuthTokenRetryCount(this.authRetryCount); await this.authManager.resetAuthToken(); const authCredentials = await this.authManager.getCredentials(); const renewedToken = await this._renewToken(authCredentials); if (renewedToken) { await this.authManager.setAuthTokenData(renewedToken); if (originalRequest) { return this.instance(originalRequest); } } } } return Promise.reject(error); } /** * @method _renewToken * @description Renews the token data * @param {IAuthCredentials} authCredentials * @returns {Promise<IAuthTokenData>} * @private * @async * @instance * @throws {InvalidTokenError} Invalid token */ async _renewToken(authCredentials) { const params = new URLSearchParams(); params.append('client_id', authCredentials.client_id); params.append('client_secret', authCredentials.client_secret); params.append('grant_type', 'client_credentials'); params.append('scope', 'free-trial-numbers:read free-trial-numbers:write messages:read messages:write reports:read reports:write virtual-numbers:read virtual-numbers:write'); const auth = await this.instance.post('/v2/oauth/token', params); if (!auth) { return auth; } const { access_token, expires_in } = auth; const expiresAt = new Date(new Date().getTime() + parseInt(expires_in) * 1000); return { access_token, expires_at: expiresAt, }; } }