"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractSession = void 0;
const isArray_1 = __importDefault(require("lodash/isArray"));
const p_limit_1 = __importDefault(require("p-limit"));
const tough_cookie_1 = require("tough-cookie");
const config_1 = require("../../config");
const config_interfaces_1 = require("../../config/config.interfaces");
const constants_1 = require("../../constants");
const omm_stream_interface_1 = require("../../delivery/stream/omm-stream.interface");
const protocol_1 = require("../../delivery/stream/protocol");
const rdp_stream_interface_1 = require("../../delivery/stream/rdp-stream.interface");
const stream_connection_1 = require("../../delivery/stream/stream-connection");
const stream_message_converter_1 = require("../../delivery/stream/stream-message-converter");
const http_client_1 = require("../../http-client");
const logger_1 = require("../../logger");
const connection_error_1 = require("../../state/connection-error");
const recover_error_1 = require("../../state/recover-error");
const state_manager_1 = require("../../state/state-manager");
const session_interface_1 = require("../session.interface");
const hash_generator_1 = require("../../util/hash-generator");
const protocols = {
    [config_interfaces_1.StreamingType.OMM]: new protocol_1.OmmProtocol(),
    [config_interfaces_1.StreamingType.RDP]: new protocol_1.RdpProtocol(),
};
// ! tough-cookie requires url to save/get cookie
// URL isn't important because each session sends requests only to one platform
// DUMMY_TOUGH_COOKIE_URL is stub URL to store cookies in non browser environment
const DUMMY_TOUGH_COOKIE_URL = 'https://isnotrealurl.com';
class AbstractSession extends state_manager_1.AbstractStateManager {
    constructor(httpClient = http_client_1.HttpClientImpl.getInstance()) {
        super();
        this.httpClient = httpClient;
        this.isEndpointSupported = true;
        this.log = logger_1.logger.getLogger('session:common');
        this.sessionEventsEmitter = this;
        this.streamConnections = new Map();
        this.getStreamConnectionLimit = (0, p_limit_1.default)(1);
        this.cookieJar = new tough_cookie_1.CookieJar();
    }
    get invalidStateMessage() {
        return constants_1.ErrorMessages.INVALID_SESSION_STATE_MESSAGE;
    }
    async sendRequest(requestParams) {
        this.log.info('Begin request sending...');
        this.log.debug('Request parameters:', requestParams);
        this.validateState();
        this.log.debug('Is cookie Jar supported:', this.cookieJarSupport);
        if (this.cookieJarSupport) {
            const requestParamsWithCookies = await this.addCookies(requestParams);
            this.log.debug('Params with cookie:', requestParamsWithCookies);
            const result = await this.request(requestParamsWithCookies);
            await this.saveCookies(result.headers);
            this.log.debug('Cookies have been saved');
            return result;
        }
        return this.request(requestParams);
    }
    getOMMStreamConnection(api = omm_stream_interface_1.DEFAULT_OMM_STREAMING_CONNECTION_NAME, validateState = true) {
        return this.getStreamConnection(protocols[config_interfaces_1.StreamingType.OMM], api, validateState);
    }
    getRDPStreamConnection(api = rdp_stream_interface_1.DEFAULT_RDP_STREAMING_CONNECTION_NAME, validateState = true) {
        return this.getStreamConnection(protocols[config_interfaces_1.StreamingType.RDP], api, validateState);
    }
    /**
     * The same as getOverriddenEndpoints but for particular endpoint
     * @param name - Optional. Name of endpoint we would like to get.
     */
    getOverriddenEndpoint(group, name) {
        const endpointFullConfig = this.getOverriddenEndpoints()[group];
        return endpointFullConfig ? endpointFullConfig[name] : endpointFullConfig;
    }
    async cleanUp() {
        this.log.debug('Close all streaming connections. Protocols:', Array.from(this.streamConnections.keys()));
        await Promise.all(Array.from(this.streamConnections.values()).map(streamConnection => streamConnection.close()));
    }
    getStreamingTypes(api) {
        const [dataApi, service, endpoint] = api.split('/');
        const connectionConfig = config_1.config.get(`apis.${dataApi}.${service}`);
        if (!connectionConfig) {
            throw new Error(`"${api}" streaming connection is not defined is the config!`);
        }
        const connectionEndpointConfig = connectionConfig.endpoints[endpoint];
        if (!connectionEndpointConfig) {
            throw new Error(`"${endpoint}" streaming connection endpoint is not defined is the config!`);
        }
        return connectionEndpointConfig.protocols || [];
    }
    checkAndValidateState(validateState) {
        if (validateState) {
            this.validateState();
        }
    }
    async createStreamConnection(protocol, api) {
        try {
            const socketCreatorsList = await this.getSocketCreators(api, protocol.getProtocolName());
            return new stream_connection_1.StreamConnectionImpl(protocol, socketCreatorsList, () => this.getStreamLoginParams(), new hash_generator_1.NumericHashGenerator());
        }
        catch (err) {
            this.emit(session_interface_1.SessionEvent.Error, err, this);
            throw err;
        }
    }
    async getStreamConnection(protocol, api, validateState) {
        this.log.debug('Get streaming connection. Connection name:', api);
        return this.getStreamConnectionLimit(async () => {
            this.checkAndValidateState(validateState);
            const type = protocol.getProtocolType();
            const configTypes = this.getStreamingTypes(api);
            if (!configTypes.includes(type)) {
                throw new Error('StreamingType conflict: requested config type is not supported!');
            }
            const connectionKey = `${type}/${api}`;
            const needCreateConnection = !this.streamConnections.has(connectionKey);
            this.log.debug('Connection with such streaming type does not exist:', needCreateConnection);
            if (needCreateConnection) {
                this.log.debug('Create new streaming connection. Name:', connectionKey);
                const createdStreamConnection = await this.createStreamConnection(protocol, api);
                this.checkAndValidateState(validateState);
                this.log.debug('Cache streaming connection with name:', connectionKey);
                this.streamConnections.set(connectionKey, createdStreamConnection);
            }
            else {
                await this.checkPipeErrors();
            }
            const streamConnection = this.streamConnections.get(connectionKey);
            try {
                this.log.info('Open streaming connection');
                await streamConnection.open();
            }
            catch (err) {
                let finalError = this.mapReconnectError(err, connectionKey);
                if (finalError === err) {
                    finalError = this.mapError(err, connectionKey);
                }
                this.log.error(`Opening streaming connection. Name: "${connectionKey}"`, err);
                this.emit(session_interface_1.SessionEvent.Error, finalError, this);
                throw finalError;
            }
            this.log.info(`Streaming connection '${connectionKey}' has been opened successfully`);
            return streamConnection;
        });
    }
    mapError(err, api) {
        if (err.type === 'error') {
            return new Error((0, stream_message_converter_1.getStreamConnectionError)(api));
        }
        else {
            return err;
        }
    }
    mapReconnectError(err, api) {
        if (err instanceof recover_error_1.RecoverError) {
            return new Error((0, stream_message_converter_1.getStreamConnectionRecoverError)(api));
        }
        else if (err instanceof connection_error_1.ConnectionError) {
            return new Error((0, stream_message_converter_1.getStreamConnectionError)(api));
        }
        return err;
    }
    async addCookies(request) {
        const { headers = {} } = request, rest = __rest(request, ["headers"]);
        const { cookie, Cookie: alternativeCookie } = headers, restHeaders = __rest(headers, ["cookie", "Cookie"]);
        const savedCookies = await this.getCookies();
        const joinedCookies = [cookie, alternativeCookie, savedCookies].filter(Boolean).join('; ');
        if (!request.headers && !savedCookies) {
            return request;
        }
        return Object.assign(Object.assign({}, rest), { headers: Object.assign(Object.assign({}, (joinedCookies ? { cookie: joinedCookies } : {})), restHeaders) });
    }
    async getCookies() {
        const cookies = await this.cookieJar.getCookies(DUMMY_TOUGH_COOKIE_URL, { allPaths: true });
        return cookies.map(cookie => cookie.cookieString()).join('; ');
    }
    async saveCookies(headers = {}) {
        const originalSetCookie = headers['set-cookie'];
        const setCookie = ((originalSetCookie && !(0, isArray_1.default)(originalSetCookie) ? [originalSetCookie] : originalSetCookie) || []);
        const promises = setCookie.map(async (value) => {
            const cookie = tough_cookie_1.Cookie.parse(value);
            if (cookie) {
                await this.cookieJar.setCookie(cookie, DUMMY_TOUGH_COOKIE_URL);
            }
            return Promise.resolve(void 0);
        });
        await Promise.all(promises);
    }
}
exports.AbstractSession = AbstractSession;
//# sourceMappingURL=data:application/json;base64, 