"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const isNode = require('detect-node');
const EventEmitter = require('events');
const axios_1 = __importDefault(require("axios"));
const allSettled = require('promise.allsettled');
const any = require('promise.any');
const CancelToken = axios_1.default.CancelToken;
const debug = require('debug')('autoDetect');
/**
 * Configs - Add possible port & path combos in to be auto detected
 */
const mnSocketConfigs = [
    { type: 'mnSocket', scheme: 'ws', port: 9788, path: '/db/', env: 'any' },
    { type: 'mnSocket', scheme: 'ws', port: 9788, path: '/db', env: 'any' },
    { type: 'mnSocket', scheme: 'wss', port: 443, path: '/db/', env: 'prod' },
];
const wnSocketConfigs = [
    { type: 'wnSocket', scheme: 'ws', port: 13535, path: '/db', env: 'any' },
    { type: 'wnSocket', scheme: 'ws', port: 13535, path: '/db/', env: 'any' },
    { type: 'wnSocket', scheme: 'wss', port: 443, path: '/wn0', env: 'prod' },
];
const mnRestConfigs = [
    { type: 'mnRest', scheme: 'http', port: 9788, path: '/api', env: 'any' },
    { type: 'mnRest', scheme: 'https', port: 443, path: '/mn/api', env: 'prod' },
];
const wnRestConfigs = [
    { type: 'wnRest', scheme: 'https', port: 443, path: '/wn/api', env: 'prod' },
    { type: 'wnRest', scheme: 'https', port: 443, path: '/wn0/api', env: 'prod' },
    { type: 'wnRest', scheme: 'http', port: 13535, path: '/api', env: 'any' },
];
const prodFilter = isProd => c => !isProd || (isProd && c.env === 'prod');
/**
 * Auto detect the settings for a hostname
 *
 * @param {string} host - Hostname
 *
 * @returns {object}
 */
class DetectionService extends EventEmitter {
    constructor(production = false) {
        super();
        this.production = production;
        this.setMaxListeners(15);
    }
    detect(host) {
        this.cancelToken = new CancelToken((c) => {
            // An executor function receives a cancel function as a parameter
            this.cancel = c;
        });
        return allSettled([
            any(mnSocketConfigs.filter(prodFilter(this.production)).map(c => this.checkSocket(Object.assign(Object.assign({}, c), { host })))),
            any(wnSocketConfigs.filter(prodFilter(this.production)).map(c => this.checkSocket(Object.assign(Object.assign({}, c), { host })))),
            any(mnRestConfigs.filter(prodFilter(this.production)).map(c => this.checkRest(Object.assign(Object.assign({}, c), { host })))),
            any(wnRestConfigs.filter(prodFilter(this.production)).map(c => this.checkRest(Object.assign(Object.assign({}, c), { host })))),
        ]).then((results) => {
            this.emit('done');
            this.cancel('All done!');
            const fullfilled = results
                .filter(r => r.status === 'fulfilled')
                .map(r => r.value)
                .reduce((acc, c) => {
                acc[c.type] = { host: c.host, port: c.port, path: c.path, scheme: c.scheme };
                return acc;
            }, {});
            this.removeAllListeners();
            if (Object.keys(fullfilled).length < 4) {
                throw new Error('Failed to connect');
            }
            return fullfilled;
        });
    }
    /**
     * Check the socket connection for a given config
     *
     * @param {object} Config object
     *
     * @returns {Promise}
     */
    checkSocket(config) {
        return this.timeout(new Promise((resolve) => {
            try {
                const scheme = (config.port === 443) ? 'wss' : 'ws';
                let ws;
                if (isNode) {
                    const WebSocket = require('ws');
                    ws = new WebSocket(`${scheme}://${config.host}:${config.port}${config.path}`);
                }
                else {
                    ws = new WebSocket(`${scheme}://${config.host}:${config.port}${config.path}`);
                }
                this.on('done', () => {
                    try {
                        ws.close();
                    }
                    catch (_a) { }
                });
                ws.addEventListener('open', () => {
                    resolve(config);
                    ws.close();
                });
                ws.addEventListener('close', () => {
                    ws.close();
                });
                ws.addEventListener('error', () => {
                    ws.close();
                });
            }
            catch (err) {
                debug('Socket failed %o %o', config, err);
            }
        }), 2000);
    }
    /**
     * Check the REST connection for a given config
     *
     * @param {object} Config object
     *
     * @returns {Promise}
     */
    checkRest(config) {
        return __awaiter(this, void 0, void 0, function* () {
            const body = JSON.stringify({ jws: '' });
            const scheme = (config.port === 443) ? 'https' : 'http';
            try {
                const res = yield axios_1.default.post(`${scheme}://${config.host}:${config.port}${config.path}`, body, {
                    cancelToken: this.cancelToken,
                    headers: {
                        'Content-type': 'application/json',
                    },
                    timeout: 1000,
                });
                debug('Done rest');
                try {
                    const json = JSON.parse(res.data);
                    if (Object.keys(json)[0] === 'jws') {
                        return config;
                    }
                }
                catch (err) {
                    debug('Failed %o %o', config, err);
                }
            }
            catch (err) {
                if (err.response && [400, 500].includes(err.response.status)) {
                    return config;
                }
                throw err;
            }
        });
    }
    timeout(promise, ms) {
        return Promise.race([
            promise,
            new Promise(rej => setTimeout(() => rej(`Timed out in ${ms} ms`), ms)),
            new Promise((res, rej) => this.on('done', () => rej())),
        ]);
    }
}
exports.DetectionService = DetectionService;
