"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChromeLauncher = void 0;
const child_process_1 = require("child_process");
const net_1 = __importDefault(require("net"));
const os_1 = __importDefault(require("os"));
const rimraf_1 = __importDefault(require("rimraf"));
const exception_js_1 = require("../exception.js");
const STANDARD_PATHS = {
    darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    linux: '/opt/google/chrome/chrome',
    win32: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
};
class ChromeLauncher {
    constructor(options = {}) {
        this.childProcess = null;
        this.options = {
            chromePath: STANDARD_PATHS[os_1.default.platform()],
            chromeAddress: '127.0.0.1',
            chromePort: 9222,
            userDataDir: `${os_1.default.tmpdir()}/${Math.random().toString(36)}`,
            cacheDir: `${os_1.default.tmpdir()}/${Math.random().toString(36)}`,
            stdio: 'ignore',
            args: [],
            noDefaultArgs: false,
            terminateProcessOnExit: true,
            connectionTimeout: 5000,
            chromeExtensions: [],
            ...options,
        };
    }
    getDefaultArgs() {
        return [
            '--allow-pre-commit-input',
            '--disable-background-networking',
            '--disable-background-timer-throttling',
            '--disable-backgrounding-occluded-windows',
            '--disable-blink-features=AutomationControlled',
            '--disable-breakpad',
            '--disable-client-side-phishing-detection',
            '--disable-cloud-import',
            '--disable-default-apps',
            '--disable-dev-shm-usage',
            '--disable-hang-monitor',
            '--disable-ipc-flooding-protection',
            '--disable-popup-blocking',
            '--disable-prompt-on-repost',
            '--disable-renderer-backgrounding',
            '--disable-sync',
            '--disable-translate',
            '--no-first-run',
            '--no-default-browser-check',
            '--no-experiments',
            '--password-store=basic',
            '--use-mock-keychain',
            '--disable-search-engine-choice-screen',
        ];
    }
    getEffectiveArgs() {
        const args = this.options.args.filter(Boolean);
        const defaultArgs = this.options.noDefaultArgs ? [] : this.getDefaultArgs();
        return this.mergeArgs([
            `--user-data-dir=${this.options.userDataDir}`,
            `--disk-cache-dir=${this.options.cacheDir}`,
            `--remote-debugging-port=${this.options.chromePort}`,
            `--remote-debugging-address=${this.options.chromeAddress}`,
            `--disable-extensions-except=${this.options.chromeExtensions.join(',')}`,
            ...defaultArgs,
        ], args, ['about:blank']);
    }
    async launch() {
        if (this.childProcess) {
            return await this.waitForPort();
        }
        const { chromePath, stdio } = this.options;
        const args = this.getEffectiveArgs();
        this.childProcess = (0, child_process_1.spawn)(chromePath, args, {
            stdio,
        });
        if (this.options.terminateProcessOnExit) {
            const exitListener = () => this.stop();
            process.addListener('exit', exitListener);
            this.childProcess.once('exit', () => process.removeListener('exit', exitListener));
        }
        this.childProcess.once('exit', () => {
            this.childProcess = null;
        });
        await this.waitForPort();
    }
    stop() {
        if (this.childProcess) {
            this.childProcess.kill();
        }
    }
    async shutdown(timeout) {
        await new Promise(resolve => {
            if (!this.childProcess) {
                return resolve();
            }
            const timer = setTimeout(() => {
                if (this.childProcess) {
                    this.childProcess.kill('SIGKILL');
                    this.childProcess = null;
                }
                resolve();
            }, timeout);
            this.childProcess.on('exit', () => {
                clearTimeout(timer);
                resolve();
            });
            this.childProcess.kill('SIGTERM');
        });
        const { userDataDir } = this.options;
        if (userDataDir) {
            await new Promise(r => {
                (0, rimraf_1.default)(userDataDir, _err => r());
            });
        }
    }
    async waitForPort() {
        const { connectionTimeout } = this.options;
        const startedAt = Date.now();
        while (Date.now() < startedAt + connectionTimeout) {
            try {
                await this.tryConnect();
                return;
            }
            catch (err) {
                await new Promise(r => setTimeout(r, 200));
            }
        }
        throw new exception_js_1.Exception({
            name: 'ChromeLaunchFailed',
            message: 'Could not connect to Chrome debugging port after launch. ' +
                'Make sure valid --remote-debugging-port is used and ' +
                'that another Chrome instance is not running',
            retry: false,
        });
    }
    async tryConnect() {
        const { chromePort, chromeAddress } = this.options;
        return new Promise((resolve, reject) => {
            const onConnect = () => {
                socket.destroy();
                resolve();
            };
            const onError = (err) => {
                socket.destroy();
                reject(err);
            };
            const socket = net_1.default.createConnection({
                host: chromeAddress,
                port: chromePort,
            });
            socket.on('connect', onConnect);
            socket.on('error', onError);
        });
    }
    mergeArgs(...argsList) {
        const objs = argsList.map(args => this.argsToObject(args));
        return this.objectToArgs(Object.assign({}, ...objs));
    }
    argsToObject(args) {
        return Object.fromEntries(args.map(_ => _.split('=')));
    }
    objectToArgs(obj) {
        return Object.entries(obj).map(_ => {
            return _.filter(Boolean).join('=');
        });
    }
}
exports.ChromeLauncher = ChromeLauncher;
//# sourceMappingURL=launcher.js.map