ayakashi
Version:
The next generation web scraping framework
178 lines (177 loc) • 6.26 kB
JavaScript
;
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.launch = void 0;
const net_1 = __importDefault(require("net"));
const child_process_1 = require("child_process");
const debug_1 = __importDefault(require("debug"));
const mkdirp_1 = require("mkdirp");
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const d = debug_1.default("ayakashi:engine:launcher");
const DEFAULT_FLAGS = [
// Disable built-in Google Translate service
"--disable-translate",
// Disable all chrome extensions entirely
"--disable-extensions",
// Disable various background network services, including extension updating,
// safe browsing service, upgrade detector, translate, UMA
"--disable-background-networking",
// Disable fetching safebrowsing lists, likely redundant due to disable-background-networking
"--safebrowsing-disable-auto-update",
// Disable syncing to a Google account
"--disable-sync",
// Disable reporting to UMA, but allows for collection
"--metrics-recording-only",
// Disable installation of default apps on first run
"--disable-default-apps",
// Mute any audio
"--mute-audio",
// Skip first run wizards
"--no-first-run",
"--no-default-browser-check",
"--no-service-autorun",
// disable some features
"--disable-smooth-scrolling",
"--disable-suggestions-ui",
"--disable-signin-promo",
"--disable-password-generation",
"--disable-cloud-import",
"--disable-default-apps",
// disable repost dialog
"--disable-prompt-on-repost",
// isolate
// "--site-per-process",
// other
"--disable-gpu",
//disable password wallet popups on linux
"--password-store=basic"
// "--disable-setuid-sandbox",
// "--no-sandbox"
];
function launch(passedFlags, options) {
return __awaiter(this, void 0, void 0, function* () {
try {
let userDir;
if (options.sessionDir) {
d("using session directory:", options.sessionDir);
mkdirp_1.sync(options.sessionDir);
userDir = options.sessionDir;
}
else {
d("using tmp session directory");
userDir = createTmpDir();
}
const flags = DEFAULT_FLAGS.concat([
`--remote-debugging-port=${options.protocolPort}`,
`--user-data-dir=${path_1.resolve(__dirname, userDir)}`
].concat(passedFlags));
d(`Launching: \n${options.chromePath} ${flags.join(" ")}`);
const chrome = yield spawnProcess(options.chromePath, options.protocolPort, flags);
d(`Chrome running with pid ${chrome.pid} on port ${options.protocolPort}.`);
return {
pid: chrome.pid,
port: options.protocolPort,
process: chrome,
forceKill: () => forceKill(chrome)
};
}
catch (err) {
throw err;
}
});
}
exports.launch = launch;
function isDebuggerReady(port) {
return new Promise(function (resolve, reject) {
const client = net_1.default.createConnection(port);
client.once("error", err => {
cleanup(client);
reject(err);
});
client.once("connect", () => {
cleanup(client);
resolve();
});
});
}
function waitUntilReady(port) {
return new Promise(function (resolve, reject) {
let retries = 0;
function poll() {
isDebuggerReady(port).then(() => {
d("Chrome instance is ready");
resolve(retries);
})
.catch(err => {
if (retries > 10) {
d("Max retries reached");
return reject(err);
}
setTimeout(poll, 500);
});
retries += 1;
d(`Waiting for Chrome instance, retries: ${retries}/10`);
}
poll();
});
}
function spawnProcess(chromePath, port, flags) {
return __awaiter(this, void 0, void 0, function* () {
try {
d("spawning...");
const chrome = yield child_process_1.spawn(chromePath, flags, { detached: process.platform !== "win32" });
const chromeDebug = debug_1.default("ayakashi:chrome");
chrome.stderr.on("data", (data) => {
chromeDebug(`stderr: ${data}`);
});
chrome.stdout.on("data", (data) => {
chromeDebug(`stdout: ${data}`);
});
yield waitUntilReady(port);
return chrome;
}
catch (err) {
throw err;
}
});
}
function cleanup(client) {
if (client) {
client.removeAllListeners();
client.end();
client.destroy();
client.unref();
}
}
function forceKill(chrome) {
return new Promise((resolve, reject) => {
chrome.on("exit", () => {
resolve();
});
d("Killing chrome instance");
try {
process.kill(chrome.pid);
}
catch (err) {
d(err);
d(`Chrome could not be killed ${err.message}`);
reject(err);
}
});
}
function createTmpDir() {
return fs_1.mkdtempSync(path_1.join(os_1.tmpdir(), "ayakashi."));
}