-
Notifications
You must be signed in to change notification settings - Fork 90
/
cli.js
executable file
·153 lines (143 loc) · 5.05 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const program = require('commander');
const CHC = require('..');
function append(value, array) {
array.push(value);
return array;
}
program
.usage('[options] URL...')
.option('-t, --host <host>', 'Chrome Debugging Protocol host')
.option('-p, --port <port>', 'Chrome Debugging Protocol port')
.option('-x, --width <dip>', 'frame width in DIP', parseInt)
.option('-y, --height <dip>', 'frame height in DIP', parseInt)
.option('-o, --output <file>', 'write to file instead of stdout')
.option('-c, --content', 'also capture the requests body')
.option('-k, --cache', 'allow caching')
.option('-a, --agent <agent>', 'user agent override')
.option('-b, --block <URL>', 'URL pattern (*) to block (can be repeated)', append, [])
.option('-H, --header <header>', 'Additional headers (can be repeated)', append, [])
.option('-i, --insecure', 'ignore certificate errors')
.option('-g, --grace <ms>', 'time to wait after the load event')
.option('-u, --timeout <ms>', 'time to wait before giving up with a URL')
.option('-r, --retry <number>', 'number of retries on page load failure')
.option('-e, --retry-delay <ms>', 'time to wait before starting a new attempt')
.option('-f, --abort-on-failure', 'stop after the first failure (incompatible with parallel mode)')
.option('-d, --post-data <bytes>', 'maximum POST data size to be returned')
.option('-l, --parallel <n>', 'load <n> URLs in parallel')
.option('-j, --user-metric <js>', 'evaluate <js> after each page load and store the result in the HAR')
.parse(process.argv);
if (program.args.length === 0) {
program.outputHelp();
process.exit(1);
}
function prettify(url) {
try {
const {parse, format} = require('url');
const urlObject = parse(url);
urlObject.protocol = chalk.gray(urlObject.protocol.slice(0, -1));
urlObject.host = chalk.bold(urlObject.host);
return format(urlObject).replace(/[:/?=#]/g, chalk.gray('$&'));
} catch (err) {
// invalid URL delegate error detection
return url;
}
}
function log(string) {
process.stderr.write(string);
}
async function preHook(url, client) {
const {Network, Security} = client;
// optionally ignore certificate errors
if (program.insecure) {
await Security.enable();
await Security.setOverrideCertificateErrors({override: true});
Security.certificateError(({eventId}) => {
Security.handleCertificateError({eventId, action: 'continue'});
});
}
// optionally set user agent
const userAgent = program.agent;
if (typeof userAgent === 'string') {
await Network.setUserAgentOverride({userAgent});
}
// optionally block URLs
if (program.block) {
await Network.setBlockedURLs({urls: program.block});
}
// optionally add extra headers
if (program.header) {
const headers = {};
// convert to object
program.header.forEach((header) => {
const match = header.match(/([^:]+): *(.*)/);
if (match) {
const [, name, value] = match;
headers[name] = value;
}
});
await Network.setExtraHTTPHeaders({headers});
}
}
function generatePostHook(userMetric) {
return async (url, client) => {
// allow the user specified grace time
await new Promise((fulfill, reject) => {
setTimeout(fulfill, program.grace || 0);
});
// allow to add the output of user-provided code to the HAR
let user;
if (userMetric) {
const {result, exceptionDetails} = await client.Runtime.evaluate({
expression: userMetric,
returnByValue: true,
awaitPromise: true,
});
// return the result or the error message
if (exceptionDetails) {
user = exceptionDetails.exception.description;
} else {
user = result.value;
}
}
return user;
};
}
const {host, port, width, height, content, cache, timeout, retry, retryDelay, abortOnFailure, postData, parallel, userMetric} = program;
CHC.run(program.args, {
host, port,
width, height,
content,
cache,
timeout,
retry, retryDelay,
abortOnFailure,
postData,
parallel,
preHook, postHook: generatePostHook(userMetric)
}).on('load', (url) => {
log(`- ${prettify(url)} `);
if (parallel) {
log(chalk.yellow('…\n'));
}
}).on('done', (url) => {
if (parallel) {
log(`- ${prettify(url)} `);
}
log(chalk.green('✓\n'));
}).on('fail', (url, err) => {
if (parallel) {
log(`- ${prettify(url)} `);
}
log(chalk.red(`✗\n ${err.message}\n`));
}).on('har', (har) => {
const fs = require('fs');
const json = JSON.stringify(har, null, 4);
const output = program.output
? fs.createWriteStream(program.output)
: process.stdout;
output.write(json);
output.write('\n');
});