UNPKG

ayakashi

Version:

The next generation web scraping framework

199 lines (198 loc) 8.53 kB
"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 }); exports.getInstance = void 0; const CDP = require("chrome-remote-interface"); const debug_1 = __importDefault(require("debug")); const launcher_1 = require("./launcher"); const createTarget_1 = require("./createTarget"); const opLog_1 = require("../opLog/opLog"); const retryOnErrorOrTimeout_1 = require("../utils/retryOnErrorOrTimeout"); const d = debug_1.default("ayakashi:engine:browser"); const HOST = "localhost"; const MAX_LAUNCH_ATTEMPTS = 3; function getInstance() { const SIGINT = "SIGINT"; let isHeadless = true; let isPersistentSession = false; let masterConnection; const opLog = opLog_1.getOpLog(); return { chromeInstance: null, init: function (options) { return __awaiter(this, void 0, void 0, function* () { if (this.chromeInstance) return; if (!options) throw new Error("init_options_not_set"); if (!options.chromePath) { throw new Error("chrome_path_not_set"); } this.chromeInstance = yield launchChrome({ chromePath: options.chromePath, headless: options.headless === false ? false : true, autoOpenDevTools: options.autoOpenDevTools === true ? true : false, launchAttempts: options.launchAttempts || MAX_LAUNCH_ATTEMPTS, windowSize: `${options.windowWidth || 1920},${options.windowHeight || 1080}`, proxyUrl: options.proxyUrl || "", sessionDir: options.sessionDir || "", protocolPort: options.protocolPort }); if (!this.chromeInstance) { throw new Error("chrome_not_launched"); } isHeadless = options.headless !== false; isPersistentSession = !!options.sessionDir; try { masterConnection = yield retryOnErrorOrTimeout_1.retryOnErrorOrTimeOut(() => { return getMasterConnection(HOST, this.chromeInstance.port); }); const sigintListener = () => __awaiter(this, void 0, void 0, function* () { d("trap SIGINT, closing chrome"); yield this.close(); process.removeListener(SIGINT, sigintListener); }); process.on(SIGINT, sigintListener); } catch (err) { d(err); throw new Error("could not establish master connection"); } }); }, close: function () { return __awaiter(this, void 0, void 0, function* () { let forceKill = false; try { if (this.chromeInstance) { yield masterConnection.Browser.close(); this.chromeInstance = null; } } catch (err) { d(err); forceKill = true; } try { if (forceKill && this.chromeInstance) { d("force killing chrome..."); yield this.chromeInstance.forceKill(); } } catch (err) { d(err); opLog.error("Failed to close chrome"); } }); }, createTarget: function () { return __awaiter(this, void 0, void 0, function* () { if (this.chromeInstance) { try { return createTarget_1.createTarget(HOST, this.chromeInstance.port, masterConnection, isHeadless && !isPersistentSession); } catch (err) { d(err); throw new Error("could_not_create_target"); } } else { return null; } }); }, destroyTarget: function (targetId, browserContextId) { return __awaiter(this, void 0, void 0, function* () { if (!this.chromeInstance) return; if (browserContextId) { d("disposing browserContextId", browserContextId); yield masterConnection.Target.disposeBrowserContext({ browserContextId: browserContextId }); } d("closing target", targetId); yield masterConnection.Target.closeTarget({ targetId: targetId }); }); } }; } exports.getInstance = getInstance; function launchChrome(options) { return __awaiter(this, void 0, void 0, function* () { const opLog = opLog_1.getOpLog(); d(`Launching Chrome instance. Headless: ${options.headless}`); const flags = []; if (options.headless) { flags.push("--headless"); } if (options.autoOpenDevTools) { flags.push("--auto-open-devtools-for-tabs"); } if (options.proxyUrl) { flags.push(`--proxy-server=${options.proxyUrl}`); } flags.push(`--window-size=${options.windowSize}`); flags.push("--allow-insecure-localhost"); let attempt = 0; let instance = null; while (!instance && (attempt += 1) < options.launchAttempts) { d(`Launching Chrome. Attempt ${attempt}/${options.launchAttempts}...`); try { instance = yield launcher_1.launch(flags, { chromePath: options.chromePath, sessionDir: options.sessionDir, protocolPort: options.protocolPort }); } catch (err) { d(`Can not launch Chrome in attempt ${attempt}/${options.launchAttempts}. Error code: ${err.code}`, err); if (err.code === "EAGAIN" || err.code === "ECONNREFUSED") { yield sleep(attempt * 1000); if (attempt >= options.launchAttempts) { throw err; } } else { throw err; } } } if (!instance) { if (process.platform === "linux") { opLog.warn("If you are running this on a headless linux server, you might be missing some dependencies."); opLog.warn("Learn how to fix it here: https://ayakashi-io.github.io/docs/installation#installing-missing-chromium-dependencies-on-a-linux-server"); } throw new Error(`Can't launch Chrome! (attempts: ${attempt - 1}/${options.launchAttempts})`); } return instance; }); } function sleep(delay) { return new Promise(resolve => setTimeout(resolve, delay)); } function getMasterConnection(host, port) { return __awaiter(this, void 0, void 0, function* () { const { webSocketDebuggerUrl } = yield CDP.Version({ host, port }); const masterConnection = yield CDP({ target: webSocketDebuggerUrl || `ws://${host}:${port}/devtools/browser` }); if (!masterConnection.Target) { throw new Error("could not establish master connection"); } return masterConnection; }); }