bbpro
Version:
BrowserBox - remote browser isolation tool
356 lines (333 loc) • 11.6 kB
JavaScript
//import {exec} from 'child_process';
import path from 'path';
import {EventEmitter} from 'node:events';
import os from 'os';
import fs from 'fs';
import isDocker from 'is-docker';
import {launch as ChromeLauncher} from './custom-launcher/dist/chrome-launcher.mjs';
import {sleep, DEBUG, CONFIG, untilForever} from '../common.js';
import {COMMON_FORMAT} from './screenShots.js';
//const RESTART_MS = 1000;
const zombies = new Map();
let chromeNumber = 0;
let chrome_started = false;
const deathHandlers = new Map();
const UNUSED_CHROME_FLAGS = [
'--display:1',
'--use-gl=egl',
'--enable-widevine',
// seems to do nothing
'--alsa-fixed-output-sample-rate=16000',
'--audio-output-sample-rate=16000',
];
const FORCE_SOFTWARE_GL_FLAGS = [
`--swiftshader-webgl`,
`--swiftshader`,
`--use-gl=swiftshader`,
];
// (some people's puppeteer, low resource usage flags)
// found i don't need
const PUPPETEER_RESOURCE_SAVING_FLAGS = [
'--no-experiments',
'--no-crash-upload',
'--no-pings',
'--no-proxy-server',
'--no-report-upload',
'--no-vr-runtime',
//'--no-service-autorun',
//'--force-launch-browser',
//'--disable-accelerated-2d-canvas',
//'--disable-gpu',
'--window-position=0,0',
//'--disable-renderer-backgrounding',
//'--disable-background-networking',
//'--disable-webgl2',
//'--disable-webgl',
//'--disable-3d-apis',
//'--disable-backgrounding-occluded-windows',
//'--disable-features=CalculateNativeWinOcclusion',
];
// disabled (some people's puppeteer stability flags)
// found i don't need
const PUPPETEER_STABILITY_FLAGS = [
'--ignore-certificate-errors',
//'--enable-features=NetworkService,NetworkServiceInProcess',
//'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
//'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-extensions-with-background-pages',
'--disable-default-apps',
/*'--disable-dev-shm-usage',*/
'--disable-extensions',
'--disable-features=Translate',
//'--disable-hang-monitor',
/*'--disable-ipc-flooding-protection',*/
//'--disable-popup-blocking',
'--disable-prompt-on-repost',
//'--disable-renderer-backgrounding',
'--disable-sync',
'--no-first-run',
'--disable-features=InterestFeedContentSuggestions',
'--enable-automation',
//'--password-store=basic',
'--no-default-browser-check',
// TODO(sadym): remove '--enable-blink-features=IdleDetection'
// once IdleDetection is turned on by default.
'--enable-blink-features=IdleDetection',
];
// I don't use these and have only seen them mentioned to reduce crashes
// but way I see it is they cost a lot of actual stability my removing
// chrome's isolation model to some extent
const MISC_STABILITY_RELATED_FLAGS_THAT_REDUCE_SECURITY = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-infobars',
'--single-process',
'--no-zygote',
'--no-first-run',
'--window-position=0,0',
'--ignore-certificate-errors',
'--ignore-certificate-errors-skip-list',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--hide-scrollbars',
'--disable-notifications',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-component-extensions-with-background-pages',
'--disable-extensions',
'--disable-features=TranslateUI,BlinkGenPropertyTrees',
'--disable-ipc-flooding-protection',
'--disable-renderer-backgrounding',
'--enable-features=NetworkService,NetworkServiceInProcess',
'--force-color-profile=srgb',
'--metrics-recording-only',
];
const launcher_api = {
async newZombie({port, /*username*/}) {
const crashDir = path.resolve(CONFIG.baseDir, 'browser-crashes');
const udd = path.resolve(CONFIG.baseDir, 'browser-cache');
const upd = path.resolve(udd, 'Default');
if ( ! fs.existsSync( udd ) ) {
fs.mkdirSync(udd, {recursive:true});
}
if ( chrome_started ) {
DEBUG.val && console.log(`Ignoring launch request as chrome already started.`);
}
const DEFAULT_FLAGS = process.platform == 'xdarwin' ? [
... PUPPETEER_STABILITY_FLAGS,
... PUPPETEER_RESOURCE_SAVING_FLAGS,
] : [
`--window-size=${COMMON_FORMAT.width},${COMMON_FORMAT.height}`,
`--crash-dumps-dir=${crashDir}`,
`--profile-directory="${upd}"`,
...(
CONFIG.forceContentDarkMode ? [
`--force-dark-mode`,
] : []
),
/* do not flush cache storage when on a development laptop (as it may flush my regular Chrome profile too!)
I added this after running local development on my MacBook and seeing my YouTube downloads disappear
I don't know if they are connected, but they could be, so removing these to be sure :) ;p xxx;p
*/
/*
...(
process.platform !== "darwin" ? [
'--profiling-flush=1',
'--enable-aggressive-domstorage-flushing',
'--disk-cache-size=2750000000',
] : []
),
*/
/**
webgl and 3d api related (
e.g. github.com landing page animation,
image editing, 3d games, etc, etc
)
**/
...(DEBUG.disableGL ? [
'--disable-webgl2',
'--disable-webgl',
] : []),
...(DEBUG.disable3D ? [
'--disable-3d-apis',
] : []),
...(DEBUG.disabled3D && DEBUG.useGL ? [
'--use-gl=swiftshader',
'--use-angle=default',
] : []),
...(DEBUG.ALL_FLAGS ? MISC_STABILITY_RELATED_FLAGS_THAT_REDUCE_SECURITY : []),
];
chromeNumber += 1;
DEBUG.val && console.log(`Chrome Number: ${chromeNumber}, Executing chrome-launcher`);
const CHROME_FLAGS = Array.from(DEFAULT_FLAGS);
if (!process.env.DEBUG_SKATEBOARD) {
if ( DEBUG.useNewAsgardHeadless ) {
CHROME_FLAGS.push('--headless=new');
} else {
CHROME_FLAGS.push('--headless');
}
} else {
CHROME_FLAGS.push('--no-sandbox');
}
if ( CONFIG.useTorProxy ) {
CHROME_FLAGS.push(`--proxy-server="${process.env.TOR_PROXY}"`);
CHROME_FLAGS.push(`--host-resolver-rules="MAP * 0.0.0.0 , EXCLUDE localhost"`);
}
if (isDocker()) {
console.log("We are in docker");
CHROME_FLAGS.push('--remote-debugging-address=0.0.0.0');
CHROME_FLAGS.push('--no-first-run');
CHROME_FLAGS.push('--start-maximized');
CHROME_FLAGS.push('--bwsi');
CHROME_FLAGS.push('--disable-file-system');
CHROME_FLAGS.push('--enable-features=Vulkan,UseSkiaRenderer,VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization');
CHROME_FLAGS.push('--ignore-gpu-blocklist');
CHROME_FLAGS.push('--disable-seccomp-filter-sandbox');
CHROME_FLAGS.push('--use-gl=egl');
CHROME_FLAGS.push('--disable-software-rasterizer');
CHROME_FLAGS.push('--disable-dev-shm-usage');
CHROME_FLAGS.push('--window-position=0,0');
}
if ( DEBUG.noAudio ) {
CHROME_FLAGS.push('--mute-audio');
}
DEBUG.showFlags && console.log({chromeFlags: CHROME_FLAGS});
const CHROME_OPTS = {
port,
ignoreDefaultFlags: true,
handleSIGINT: false,
userDataDir: process.platform == 'darwin' ? false : path.resolve(CONFIG.baseDir, 'browser-cache'),
logLevel: 'verbose',
chromeFlags: CHROME_FLAGS
};
if ( CHROME_OPTS.userDataDir ) {
fs.mkdirSync(CHROME_OPTS.userDataDir, {recursive:true});
}
console.log(CHROME_OPTS, CHROME_FLAGS);
const zomb = await ChromeLauncher(CHROME_OPTS);
const retVal = {};
zomb.pidFile = path.resolve(CONFIG.baseDir, `chrome-${port}`, `pid`);
console.log("Did chrome start?");
if ( zomb.process ) {
DEBUG.debugChromeStart && console.log(zomb.process, zomb.pid);
fs.mkdirSync(path.resolve(CONFIG.baseDir, `chrome-${port}`), {recursive:true});
fs.writeFileSync(zomb.pidFile, zomb.pid.toString());
chrome_started = true;
zombies.set(port,zomb);
retVal.port = port;
} else {
console.log("Chrome did not start", zomb);
let found = false;
if ( fs.existsSync(zomb.pidFile) ) {
let pid = parseInt(fs.readFileSync(zomb.pidFile).toString());
if ( ! Number.isNaN(pid) ) {
const running = isRunning(pid);
if ( running ) {
console.log(`Existing chrome on port ${port} already found (with pid: ${pid})`);
zomb.pid = pid;
found = true;
chrome_started = true;
zombies.set(port,zomb);
retVal.port = port;
zomb.process = new EventEmitter();
zomb.kill = async () => {
process.kill(zomb.pid, 'SIGKILL');
await sleep(500);
process.kill(zomb.pid, 'SIGKILL');
}
setTimeout(async () => {
untilForever(() => !isRunning(zomb.pid)).then(() => zomb.process.emit('exit'));
}, 0);
} else {
console.info(`Could not find any other chrome process running on port ${port}, even tho pid file was reported.`);
console.info(`Deleting said pid file...`);
fs.unlinkSync(zomb.pidFile);
}
function isRunning(pid) {
try {
process.kill(pid, 0);
return true;
} catch(e) {
return false;
}
}
}
}
if ( ! found ) {
// cleanup just in case
await zomb.kill();
}
}
if ( zomb.process ) {
zomb.process.on('exit', () => {
console.warn("Chrome exiting");
console.info(`Deleting said pid file...`);
fs.unlinkSync(zomb.pidFile);
let handlers = deathHandlers.get(port);
if ( handlers ) {
for( const handler of handlers ) {
try {
handler();
} catch(e) {
console.warn("Error in chrome death handler", e, handler);
}
}
}
});
}
process.on('SIGHUP', undoChrome);
process.on('SIGUSR1', undoChrome);
process.on('SIGTERM', undoChrome);
process.on('SIGINT', undoChrome);
process.on('beforeExit', undoChrome);
return retVal;
async function undoChrome() {
DEBUG.val && console.log("Undo chrome called");
if ( ! chrome_started ) return;
chrome_started = false;
try {
console.warn("Chrome exiting");
console.info(`Deleting said pid file...`);
fs.unlinkSync(zomb.pidFile);
let handlers = deathHandlers.get(port);
const newHandlers = [];
if ( handlers ) {
for( const handler of handlers ) {
try {
handler();
} catch(e) {
newHandlers.push(handler);
console.warn("Error in chrome death handler", e, handler);
}
}
deathHandlers.set(port, newHandlers);
}
await zomb.kill();
process.exit(0);
} catch(e) {
console.warn("Error on kill chrome on exit", e);
process.exit(0);
}
}
},
onDeath(port, func) {
let handlers = deathHandlers.get(port);
if ( ! handlers ) {
handlers = [];
deathHandlers.set(port,handlers);
}
handlers.push(func);
},
kill(port) {
const zombie = zombies.get(port);
DEBUG.val && console.log(`Requesting zombie kill.`);
if ( zombie ) {
return zombie.kill();
}
}
};
export default launcher_api;