"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessTokenProviderImpl = exports.TOKEN_REDUCING_VALIDITY_TIME = exports.DEFAULT_TOKEN_EXPIRY_TIME_SEC = exports.RETRY_REFRESH_TIMEOUT = void 0;
const eventemitter3_1 = __importDefault(require("eventemitter3"));
const p_limit_1 = __importDefault(require("p-limit"));
const p_retry_1 = __importDefault(require("p-retry"));
const config_1 = require("../config");
const http_client_impl_1 = require("../http-client/http-client-impl");
const logger_1 = require("../logger");
const delay_1 = require("../util/delay");
const access_token_helper_1 = require("./access-token-helper");
const access_token_provider_interface_1 = require("./access-token-provider.interface");
const oauth_converter_1 = require("./oauth-converter");
/**
 * The interval is used to send the requests to the platform to refresh an access token
 * if first attempt to refresh was failed with the timeout error
 * Seconds.
 */
exports.RETRY_REFRESH_TIMEOUT = 30;
exports.DEFAULT_TOKEN_EXPIRY_TIME_SEC = 10 * 60;
const MAX_TOKEN_EXPIRY_TIME_SEC = 24 * 60 * 60;
/**
 * If 'expiresIn' time is equal to 300 seconds
 * This interval reduce that time, e.g. 300-5=295 sec
 *
 * This parameter is introduced to start reconnection process a bit earlier then token becomes invalid
 *
 * Seconds
 */
exports.TOKEN_REDUCING_VALIDITY_TIME = 5;
/**
 * Class is responsible for retrieving an access token and managing token refreshing
 * To start the process of token receiving/refreshing you should call login() method first and wait until promise will be resolved
 * If login is successful - the loop to refresh the token with half of 'expiresIn' interval will be started
 * To get an access token you can use getToken() method after login promise is resolved
 *
 * Events:
 * TokenProviderEvent.AuthenticationSucceeded - the token is received successfully. Token is available in the callback
 * TokenProviderEvent.AuthenticationFailed - request to get the token is failed.
 * TokenProviderEvent.RefreshSucceeded - the token is refreshed successfully. Token is available in the callback
 * TokenProviderEvent.RefreshFailed - error during the token refreshing.
 * TokenProviderEvent.TokenExpired - Access token has been expired due to 'expiresIn' timeout.
 */
class AccessTokenProviderImpl extends eventemitter3_1.default {
    /**
     *
     * @param appKey Your application key
     * @param url The url to get an access token e.g. 'https://api.refinitiv.com/auth/oauth2/v1/token'
     * @param oAuthGrantType Optional parameters to provide OAuth grant options
     */
    constructor(appKey, getAuthUrl, oAuthGrantType, httpClient = http_client_impl_1.HttpClientImpl.getInstance()) {
        super();
        this.appKey = appKey;
        this.getAuthUrl = getAuthUrl;
        this.oAuthGrantType = oAuthGrantType;
        this.httpClient = httpClient;
        this.limit = (0, p_limit_1.default)(1);
        this.onRetryLoginFailed = async (error) => {
            var _a;
            this.log.warn(`Auth try:${error.attemptNumber} failed. ${error.message}`, ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) ? error.response.data : '');
            await (0, delay_1.delay)(exports.TOKEN_REDUCING_VALIDITY_TIME * 1000);
        };
        this.stopWithError = (error) => {
            this.log.error(`${access_token_provider_interface_1.TokenProviderEvent.TokenExpired} => ${error.message}`);
            this.emit(access_token_provider_interface_1.TokenProviderEvent.TokenExpired, error);
            this.stopRefresh();
        };
        this.isStopped = false;
        this.log = logger_1.logger.getLogger('token:provider');
    }
    /**
     * This method makes request to get an access token
     * Once promise is resolved that means the access token is received successfully and it is available using getToken() method
     * @returns Promise<void>.
     */
    login() {
        return this.limit(async () => {
            var _a;
            try {
                this.log.debug('Starting authentication process.');
                const payload = oauth_converter_1.oAuthConverter.getPasswordGrantPayload(this.oAuthGrantType, this.appKey);
                await this.executeAuthenticationWithSafeGuard(access_token_provider_interface_1.TokenProviderEvent.AuthenticationSucceeded, payload);
            }
            catch (error) {
                this.log.error(`${access_token_provider_interface_1.TokenProviderEvent.AuthenticationFailed} => ${error.message}`, ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) ? error.response.data : '');
                this.emit(access_token_provider_interface_1.TokenProviderEvent.AuthenticationFailed, error);
                throw error;
            }
        });
    }
    emit(event, ...args) {
        if (this.isStopped) {
            this.clearAllTimers();
            this.log.debug(`emit '${event}' skipped`);
            return false;
        }
        return super.emit(event, ...args);
    }
    /**
     * Use to get the access token
     *
     * @returns The access token if .login() promise has already been resolved.
     * If .login() promise is not resolved yet - throws an error
     */
    getToken() {
        var _a;
        if ((_a = this.platformAuthParams) === null || _a === void 0 ? void 0 : _a.accessToken) {
            return this.platformAuthParams.accessToken;
        }
        throw new Error('Access token is not available!');
    }
    /**
     * Clear the timeout interval which is used to refresh the access token
     */
    stopRefresh() {
        this.isStopped = true;
        this.clearAllTimers();
    }
    async processRefreshRequest() {
        var _a;
        this.log.debug(`Start token refreshing.`);
        try {
            const payload = oauth_converter_1.oAuthConverter.getRefreshGrantPayload(this.oAuthGrantType, this.appKey, this.platformAuthParams.refreshToken);
            await this.executeAuthenticationWithSafeGuard(access_token_provider_interface_1.TokenProviderEvent.RefreshSucceeded, payload);
        }
        catch (err) {
            this.log.warn(`${access_token_provider_interface_1.TokenProviderEvent.RefreshFailed} => ${err.message}`, ((_a = err.response) === null || _a === void 0 ? void 0 : _a.data) ? err.response.data : '');
            this.emit(access_token_provider_interface_1.TokenProviderEvent.RefreshFailed, err);
            if ((0, access_token_helper_1.isHttpTimeoutError)(err)) {
                this.log.warn(`Request timed out. Next refresh retry in ${exports.RETRY_REFRESH_TIMEOUT} sec`);
                this.startRefreshTimer(exports.RETRY_REFRESH_TIMEOUT * 1000);
            }
            else if (this.autoReconnectEnabled() && (0, access_token_helper_1.isAllowedHttpErrorToReconnect)(err)) {
                return this.reLogin();
            }
            else {
                this.stopWithError(err);
            }
        }
    }
    async executeAuthenticationWithSafeGuard(event, payload) {
        const start = Date.now();
        const response = await this.httpClient.post(this.getAuthUrl(), payload);
        const durationSec = Math.ceil((Date.now() - start) / 1000);
        this.platformAuthParams = oauth_converter_1.oAuthConverter.parseTokenResponse(response.data);
        this.log.debug(`${event} => ${this.platformAuthParams.accessToken.slice(0, 5)}*****`, `expiresIn=${this.platformAuthParams.expiresIn}-${durationSec}sec`);
        this.platformAuthParams.expiresIn = this.getValidExpiresIn(this.platformAuthParams.expiresIn);
        this.runTimers(this.platformAuthParams.expiresIn - durationSec);
        this.emit(event, this.platformAuthParams.accessToken);
    }
    getValidExpiresIn(expiresIn) {
        if (Number.isNaN(expiresIn) || expiresIn < exports.DEFAULT_TOKEN_EXPIRY_TIME_SEC || expiresIn > MAX_TOKEN_EXPIRY_TIME_SEC) {
            this.log.warn(`Data to refresh a token: ${expiresIn}.`);
            this.log.warn(`The token refreshing delay is set to the minimum allowable value ${exports.DEFAULT_TOKEN_EXPIRY_TIME_SEC} ms.`);
            return exports.DEFAULT_TOKEN_EXPIRY_TIME_SEC;
        }
        return expiresIn;
    }
    autoReconnectEnabled() {
        return config_1.config.get('sessions').platform['default-session']['auto-reconnect'];
    }
    async reLogin() {
        this.log.debug('Starting reconnect!');
        this.clearAllTimers();
        return this.login().catch(this.stopWithError);
    }
    /**
     * Run two timers
     * 1. Expire timer: to emit the error if the access token was not refreshed during the 'expiresIn' interval
     * 2. Refresh token timer: to send the request to refresh the access token
     * @param expireTime Seconds
     */
    runTimers(expireTime) {
        const expiresTimeout = (expireTime - exports.RETRY_REFRESH_TIMEOUT) * 1000;
        this.startExpireTimer(expiresTimeout);
        this.startRefreshTimer((expireTime * 1000) / 2);
    }
    clearAllTimers() {
        this.clearRefreshTimeout();
        this.clearExpiresTimeout();
    }
    startRefreshTimer(timeout) {
        this.clearRefreshTimeout();
        this.refreshTimeoutId = setTimeout(this.processRefreshRequest.bind(this), timeout);
    }
    startExpireTimer(timeout) {
        this.clearExpiresTimeout();
        this.expireTimeoutId = setTimeout(async () => {
            this.clearAllTimers();
            await (0, p_retry_1.default)(this.login.bind(this), {
                factor: 1,
                maxRetryTime: (exports.RETRY_REFRESH_TIMEOUT - exports.TOKEN_REDUCING_VALIDITY_TIME) * 1000,
                onFailedAttempt: this.onRetryLoginFailed,
            }).catch(e => this.stopWithError(new Error('Access token has been expired')));
        }, timeout);
    }
    clearRefreshTimeout() {
        if (this.refreshTimeoutId) {
            clearTimeout(this.refreshTimeoutId);
            this.refreshTimeoutId = undefined;
        }
    }
    clearExpiresTimeout() {
        if (this.expireTimeoutId) {
            clearTimeout(this.expireTimeoutId);
            this.expireTimeoutId = undefined;
        }
    }
}
exports.AccessTokenProviderImpl = AccessTokenProviderImpl;
//# sourceMappingURL=data:application/json;base64, 