@telstra/core
Version:
Telstra SDK Core
167 lines (166 loc) • 6.54 kB
JavaScript
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,
};
}
}