#!/usr/bin/env node 'use strict'; const repl = require('repl'); const util = require('util'); const fs = require('fs'); const path = require('path'); const program = require('commander'); const CDP = require('../'); const packageInfo = require('../package.json'); function display(object) { return util.inspect(object, { colors: process.stdout.isTTY, depth: null }); } function toJSON(object) { return JSON.stringify(object, null, 4); } /// function inspect(target, args, options) { options.local = args.local; // otherwise the active target if (target) { if (args.webSocket) { // by WebSocket URL options.target = target; } else { // by target id options.target = (targets) => { return targets.findIndex((_target) => { return _target.id === target; }); }; } } if (args.protocol) { options.protocol = JSON.parse(fs.readFileSync(args.protocol)); } CDP(options, (client) => { const cdpRepl = repl.start({ prompt: process.stdin.isTTY ? '\x1b[32m>>>\x1b[0m ' : '', ignoreUndefined: true, writer: display }); // XXX always await promises on the REPL const defaultEval = cdpRepl.eval; cdpRepl.eval = (cmd, context, filename, callback) => { defaultEval(cmd, context, filename, async (err, result) => { if (err) { // propagate errors from the eval callback(err); } else { // awaits the promise and either return result or error try { callback(null, await Promise.resolve(result)); } catch (err) { callback(err); } } }); }; const homePath = process.env.HOME || process.env.USERPROFILE; const historyFile = path.join(homePath, '.cri_history'); const historySize = 10000; function loadHistory() { // only if run from a terminal if (!process.stdin.isTTY) { return; } // attempt to open the history file let fd; try { fd = fs.openSync(historyFile, 'r'); } catch (err) { return; // no history file present } // populate the REPL history fs.readFileSync(fd, 'utf8') .split('\n') .filter((entry) => { return entry.trim(); }) .reverse() // to be compatible with repl.history files .forEach((entry) => { cdpRepl.history.push(entry); }); } function saveHistory() { // only if run from a terminal if (!process.stdin.isTTY) { return; } // only store the last chunk const entries = cdpRepl.history.slice(0, historySize).reverse().join('\n'); fs.writeFileSync(historyFile, entries + '\n'); } // utility custom command cdpRepl.defineCommand('target', { help: 'Display the current target', action: () => { console.log(client.webSocketUrl); cdpRepl.displayPrompt(); } }); // utility to purge all the event handlers cdpRepl.defineCommand('reset', { help: 'Remove all the registered event handlers', action: () => { client.removeAllListeners(); cdpRepl.displayPrompt(); } }); // enable history loadHistory(); // disconnect on exit cdpRepl.on('exit', () => { if (process.stdin.isTTY) { console.log(); } client.close(); saveHistory(); }); // exit on disconnection client.on('disconnect', () => { console.error('Disconnected.'); saveHistory(); process.exit(1); }); // add protocol API for (const domainObject of client.protocol.domains) { // walk the domain names const domainName = domainObject.domain; cdpRepl.context[domainName] = {}; // walk the items in the domain for (const itemName in client[domainName]) { // add CDP object to the REPL context const cdpObject = client[domainName][itemName]; cdpRepl.context[domainName][itemName] = cdpObject; } } }).on('error', (err) => { console.error('Cannot connect to remote endpoint:', err.toString()); }); } function list(options) { CDP.List(options, (err, targets) => { if (err) { console.error(err.toString()); process.exit(1); } console.log(toJSON(targets)); }); } function _new(url, options) { options.url = url; CDP.New(options, (err, target) => { if (err) { console.error(err.toString()); process.exit(1); } console.log(toJSON(target)); }); } function activate(args, options) { options.id = args; CDP.Activate(options, (err) => { if (err) { console.error(err.toString()); process.exit(1); } }); } function close(args, options) { options.id = args; CDP.Close(options, (err) => { if (err) { console.error(err.toString()); process.exit(1); } }); } function version(options) { CDP.Version(options, (err, info) => { if (err) { console.error(err.toString()); process.exit(1); } console.log(toJSON(info)); }); } function protocol(args, options) { options.local = args.local; CDP.Protocol(options, (err, protocol) => { if (err) { console.error(err.toString()); process.exit(1); } console.log(toJSON(protocol)); }); } /// let action; program .option('-v, --v', 'Show this module version') .option('-t, --host ', 'HTTP frontend host') .option('-p, --port ', 'HTTP frontend port') .option('-s, --secure', 'HTTPS/WSS frontend') .option('-n, --use-host-name', 'Do not perform a DNS lookup of the host'); program .command('inspect []') .description('inspect a target (defaults to the first available target)') .option('-w, --web-socket', 'interpret as a WebSocket URL instead of a target id') .option('-j, --protocol ', 'Chrome Debugging Protocol descriptor (overrides `--local`)') .option('-l, --local', 'Use the local protocol descriptor') .action((target, args) => { action = inspect.bind(null, target, args); }); program .command('list') .description('list all the available targets/tabs') .action(() => { action = list; }); program .command('new []') .description('create a new target/tab') .action((url) => { action = _new.bind(null, url); }); program .command('activate ') .description('activate a target/tab by id') .action((id) => { action = activate.bind(null, id); }); program .command('close ') .description('close a target/tab by id') .action((id) => { action = close.bind(null, id); }); program .command('version') .description('show the browser version') .action(() => { action = version; }); program .command('protocol') .description('show the currently available protocol descriptor') .option('-l, --local', 'Return the local protocol descriptor') .action((args) => { action = protocol.bind(null, args); }); program.parse(process.argv); // common options const options = { host: program.host, port: program.port, secure: program.secure, useHostName: program.useHostName }; if (action) { action(options); } else { if (program.v) { console.log(packageInfo.version); } else { program.outputHelp(); process.exit(1); } }