312 lines
8.2 KiB
JavaScript
312 lines
8.2 KiB
JavaScript
|
#!/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 <host>', 'HTTP frontend host')
|
||
|
.option('-p, --port <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 [<target>]')
|
||
|
.description('inspect a target (defaults to the first available target)')
|
||
|
.option('-w, --web-socket', 'interpret <target> as a WebSocket URL instead of a target id')
|
||
|
.option('-j, --protocol <file.json>', '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 [<url>]')
|
||
|
.description('create a new target/tab')
|
||
|
.action((url) => {
|
||
|
action = _new.bind(null, url);
|
||
|
});
|
||
|
|
||
|
program
|
||
|
.command('activate <id>')
|
||
|
.description('activate a target/tab by id')
|
||
|
.action((id) => {
|
||
|
action = activate.bind(null, id);
|
||
|
});
|
||
|
|
||
|
program
|
||
|
.command('close <id>')
|
||
|
.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);
|
||
|
}
|
||
|
}
|