@fanboynz/network-scanner 2.0.65 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +134 -10
- package/CHANGELOG.md +135 -0
- package/CLAUDE.md +18 -7
- package/README.md +12 -4
- package/lib/adblock-rust.js +23 -18
- package/lib/adblock.js +127 -82
- package/lib/browserexit.js +210 -200
- package/lib/browserhealth.js +84 -60
- package/lib/cdp.js +103 -81
- package/lib/clear_sitedata.js +61 -159
- package/lib/cloudflare.js +579 -409
- package/lib/colorize.js +29 -12
- package/lib/compare.js +16 -8
- package/lib/compress.js +2 -1
- package/lib/curl.js +287 -220
- package/lib/domain-cache.js +87 -40
- package/lib/dry-run.js +137 -194
- package/lib/fingerprint.js +20 -18
- package/lib/flowproxy.js +391 -188
- package/lib/ghost-cursor.js +8 -7
- package/lib/grep.js +248 -171
- package/lib/ignore_similar.js +70 -124
- package/lib/interaction.js +132 -235
- package/lib/nettools.js +309 -87
- package/lib/openvpn_vpn.js +12 -11
- package/lib/output.js +92 -59
- package/lib/post-processing.js +216 -162
- package/lib/proxy.js +105 -7
- package/lib/redirect.js +46 -30
- package/lib/referrer.js +158 -165
- package/lib/searchstring.js +290 -381
- package/lib/smart-cache.js +141 -91
- package/lib/socks-relay.js +267 -0
- package/lib/spawn-async.js +137 -0
- package/lib/validate_rules.js +188 -176
- package/lib/wireguard_vpn.js +111 -117
- package/nwss.js +872 -149
- package/package.json +6 -5
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local no-auth SOCKS5 relay for authenticated SOCKS5 upstreams.
|
|
3
|
+
*
|
|
4
|
+
* Chromium cannot authenticate SOCKS5 proxies (crbug.com/256785 — it only
|
|
5
|
+
* implements the no-auth method 0x00; credentials in --proxy-server are
|
|
6
|
+
* discarded, and page.authenticate() is HTTP-407-only so it can't help —
|
|
7
|
+
* SOCKS auth happens at the TCP handshake before any HTTP).
|
|
8
|
+
*
|
|
9
|
+
* Workaround: run an in-process no-auth SOCKS5 server bound to 127.0.0.1.
|
|
10
|
+
* Chromium connects to it without auth (which it CAN do); for each
|
|
11
|
+
* connection we open an authenticated tunnel to the real upstream via the
|
|
12
|
+
* `socks` package (RFC 1929 user/pass) and pipe the two together. Domain
|
|
13
|
+
* address types are forwarded as hostnames so remote DNS still works
|
|
14
|
+
* end-to-end (no DNS leak).
|
|
15
|
+
*
|
|
16
|
+
* Relays are keyed by upstream identity and reused. closeAllRelays() must
|
|
17
|
+
* be called on scan exit / signal so listening sockets don't leak.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const net = require('net');
|
|
21
|
+
const { SocksClient } = require('socks');
|
|
22
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
23
|
+
const SOCKS_RELAY_TAG = messageColors.processing('[socks-relay]');
|
|
24
|
+
|
|
25
|
+
// upstreamKey -> { server, port, activeSockets:Set<net.Socket> }
|
|
26
|
+
const _relays = new Map();
|
|
27
|
+
|
|
28
|
+
function upstreamKey(u) {
|
|
29
|
+
return `${u.host}:${u.port}:${u.username || ''}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handle one Chromium->relay connection: minimal SOCKS5 server handshake,
|
|
34
|
+
* then an authenticated upstream tunnel, then bidirectional pipe.
|
|
35
|
+
*/
|
|
36
|
+
// Bail on a client that connects and never completes SOCKS5 negotiation.
|
|
37
|
+
// Generous enough for a Chromium loopback handshake (microseconds), short
|
|
38
|
+
// enough to catch a stalled / half-open client before the OS TCP keepalive
|
|
39
|
+
// notices (default ~2 hours on Linux).
|
|
40
|
+
const HANDSHAKE_TIMEOUT_MS = 10000;
|
|
41
|
+
|
|
42
|
+
function handleClient(client, upstream, forceDebug) {
|
|
43
|
+
let phase = 'greeting';
|
|
44
|
+
let buf = Buffer.alloc(0);
|
|
45
|
+
let upstreamSock = null;
|
|
46
|
+
let settled = false;
|
|
47
|
+
// Handshake-phase watchdog handle. Assigned after cleanup is declared so
|
|
48
|
+
// both references in this scope resolve unambiguously.
|
|
49
|
+
let handshakeTimer = null;
|
|
50
|
+
|
|
51
|
+
const cleanup = () => {
|
|
52
|
+
if (settled) return;
|
|
53
|
+
settled = true;
|
|
54
|
+
if (handshakeTimer) { clearTimeout(handshakeTimer); handshakeTimer = null; }
|
|
55
|
+
try { client.destroy(); } catch (_) {}
|
|
56
|
+
if (upstreamSock) { try { upstreamSock.destroy(); } catch (_) {} }
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Bail on a client that connects and never completes SOCKS5 negotiation
|
|
60
|
+
// (stalled / half-open / non-SOCKS protocol). Without this, such a socket
|
|
61
|
+
// sits in activeSockets until the OS TCP keepalive notices — default
|
|
62
|
+
// ~2 hours on Linux. unref'd so a pending watchdog never holds the
|
|
63
|
+
// process alive after closeAllRelays().
|
|
64
|
+
handshakeTimer = setTimeout(() => {
|
|
65
|
+
if (phase !== 'piping') {
|
|
66
|
+
if (forceDebug) {
|
|
67
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} handshake timeout (phase=${phase}) — closing`));
|
|
68
|
+
}
|
|
69
|
+
cleanup();
|
|
70
|
+
}
|
|
71
|
+
}, HANDSHAKE_TIMEOUT_MS);
|
|
72
|
+
if (typeof handshakeTimer.unref === 'function') handshakeTimer.unref();
|
|
73
|
+
|
|
74
|
+
const onData = async (chunk) => {
|
|
75
|
+
buf = Buffer.concat([buf, chunk]);
|
|
76
|
+
try {
|
|
77
|
+
if (phase === 'greeting') {
|
|
78
|
+
// [0x05, NMETHODS, METHODS...]
|
|
79
|
+
if (buf.length < 2) return;
|
|
80
|
+
const nMethods = buf[1];
|
|
81
|
+
if (buf.length < 2 + nMethods) return;
|
|
82
|
+
const offered = buf.subarray(2, 2 + nMethods);
|
|
83
|
+
buf = buf.subarray(2 + nMethods);
|
|
84
|
+
// We only speak no-auth (0x00) to the local client. Chromium always
|
|
85
|
+
// offers it; if a client somehow didn't, reply "no acceptable
|
|
86
|
+
// methods" rather than violate the protocol by selecting unoffered.
|
|
87
|
+
if (!offered.includes(0x00)) {
|
|
88
|
+
try { client.write(Buffer.from([0x05, 0xFF])); } catch (_) {}
|
|
89
|
+
return cleanup();
|
|
90
|
+
}
|
|
91
|
+
client.write(Buffer.from([0x05, 0x00])); // select "no auth"
|
|
92
|
+
phase = 'request';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (phase === 'request') {
|
|
96
|
+
// [0x05, CMD, 0x00, ATYP, ADDR..., PORT(2 BE)]
|
|
97
|
+
if (buf.length < 4) return;
|
|
98
|
+
if (buf[0] !== 0x05) { failReply(client, 0x01); return cleanup(); }
|
|
99
|
+
const cmd = buf[1];
|
|
100
|
+
const atyp = buf[3];
|
|
101
|
+
let host, port, hdrLen;
|
|
102
|
+
|
|
103
|
+
if (atyp === 0x01) { // IPv4
|
|
104
|
+
if (buf.length < 10) return;
|
|
105
|
+
host = `${buf[4]}.${buf[5]}.${buf[6]}.${buf[7]}`;
|
|
106
|
+
port = buf.readUInt16BE(8);
|
|
107
|
+
hdrLen = 10;
|
|
108
|
+
} else if (atyp === 0x03) { // domain
|
|
109
|
+
if (buf.length < 5) return;
|
|
110
|
+
const dLen = buf[4];
|
|
111
|
+
if (buf.length < 7 + dLen) return;
|
|
112
|
+
host = buf.subarray(5, 5 + dLen).toString('utf8');
|
|
113
|
+
port = buf.readUInt16BE(5 + dLen);
|
|
114
|
+
hdrLen = 7 + dLen;
|
|
115
|
+
} else if (atyp === 0x04) { // IPv6
|
|
116
|
+
if (buf.length < 22) return;
|
|
117
|
+
const seg = [];
|
|
118
|
+
for (let i = 0; i < 16; i += 2) seg.push(buf.readUInt16BE(4 + i).toString(16));
|
|
119
|
+
host = seg.join(':');
|
|
120
|
+
port = buf.readUInt16BE(20);
|
|
121
|
+
hdrLen = 22;
|
|
122
|
+
} else {
|
|
123
|
+
failReply(client, 0x08); // address type not supported
|
|
124
|
+
return cleanup();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (cmd !== 0x01) { // only CONNECT
|
|
128
|
+
failReply(client, 0x07);
|
|
129
|
+
return cleanup();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Hand the stream to .pipe() from here. Pause + detach this handler
|
|
133
|
+
// so a data event during the upstream connect can't re-enter.
|
|
134
|
+
phase = 'connecting';
|
|
135
|
+
client.pause();
|
|
136
|
+
client.off('data', onData);
|
|
137
|
+
const early = buf.subarray(hdrLen); // any bytes after the request header
|
|
138
|
+
buf = null;
|
|
139
|
+
|
|
140
|
+
let info;
|
|
141
|
+
try {
|
|
142
|
+
info = await SocksClient.createConnection({
|
|
143
|
+
proxy: {
|
|
144
|
+
host: upstream.host,
|
|
145
|
+
port: upstream.port,
|
|
146
|
+
type: 5,
|
|
147
|
+
userId: upstream.username,
|
|
148
|
+
password: upstream.password || '',
|
|
149
|
+
},
|
|
150
|
+
command: 'connect',
|
|
151
|
+
destination: { host, port },
|
|
152
|
+
timeout: 20000,
|
|
153
|
+
});
|
|
154
|
+
} catch (e) {
|
|
155
|
+
if (forceDebug) {
|
|
156
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} upstream connect failed (${host}:${port}): ${e.message}`));
|
|
157
|
+
}
|
|
158
|
+
failReply(client, 0x05); // connection refused
|
|
159
|
+
return cleanup();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
upstreamSock = info.socket;
|
|
163
|
+
try { upstreamSock.setNoDelay(true); } catch (_) {}
|
|
164
|
+
upstreamSock.on('error', cleanup);
|
|
165
|
+
upstreamSock.on('close', cleanup);
|
|
166
|
+
client.on('error', cleanup);
|
|
167
|
+
client.on('close', cleanup);
|
|
168
|
+
|
|
169
|
+
// SOCKS5 success (BND.ADDR 0.0.0.0:0 — Chromium ignores it for CONNECT)
|
|
170
|
+
client.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]));
|
|
171
|
+
if (early && early.length) upstreamSock.write(early);
|
|
172
|
+
client.pipe(upstreamSock);
|
|
173
|
+
upstreamSock.pipe(client);
|
|
174
|
+
client.resume();
|
|
175
|
+
phase = 'piping';
|
|
176
|
+
// Negotiation complete — disarm the handshake watchdog so a
|
|
177
|
+
// long-running download isn't killed mid-transfer.
|
|
178
|
+
if (handshakeTimer) { clearTimeout(handshakeTimer); handshakeTimer = null; }
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
if (forceDebug) {
|
|
182
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} handler error: ${e.message}`));
|
|
183
|
+
}
|
|
184
|
+
cleanup();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
client.on('data', onData);
|
|
189
|
+
client.on('error', cleanup);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// SOCKS5 failure reply (valid only before piping starts).
|
|
193
|
+
function failReply(client, code) {
|
|
194
|
+
try { client.write(Buffer.from([0x05, code, 0x00, 0x01, 0, 0, 0, 0, 0, 0])); } catch (_) {}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Ensure a relay exists for the given upstream; returns its local port.
|
|
199
|
+
* Idempotent — repeated calls for the same upstream reuse one relay.
|
|
200
|
+
*
|
|
201
|
+
* @param {{host:string,port:number,username:string,password:string}} upstream
|
|
202
|
+
* @param {boolean} forceDebug
|
|
203
|
+
* @returns {Promise<number>} local 127.0.0.1 port the relay listens on
|
|
204
|
+
*/
|
|
205
|
+
async function ensureRelay(upstream, forceDebug = false) {
|
|
206
|
+
const key = upstreamKey(upstream);
|
|
207
|
+
const existing = _relays.get(key);
|
|
208
|
+
if (existing) return existing.port;
|
|
209
|
+
|
|
210
|
+
const activeSockets = new Set();
|
|
211
|
+
const server = net.createServer((clientSock) => {
|
|
212
|
+
// Disable Nagle: page scanning is full of small-packet phases (per-origin
|
|
213
|
+
// TLS handshakes, small XHR/API calls, the SOCKS handshake itself).
|
|
214
|
+
// Nagle + delayed-ACK adds ~40ms stalls on those; relays should not.
|
|
215
|
+
try { clientSock.setNoDelay(true); } catch (_) {}
|
|
216
|
+
activeSockets.add(clientSock);
|
|
217
|
+
clientSock.on('close', () => activeSockets.delete(clientSock));
|
|
218
|
+
handleClient(clientSock, upstream, forceDebug);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await new Promise((resolve, reject) => {
|
|
222
|
+
const onErr = (e) => reject(e);
|
|
223
|
+
server.once('error', onErr);
|
|
224
|
+
server.listen(0, '127.0.0.1', () => {
|
|
225
|
+
server.removeListener('error', onErr);
|
|
226
|
+
// Keep a listener so a late server error doesn't crash the process.
|
|
227
|
+
server.on('error', (e) => {
|
|
228
|
+
if (forceDebug) console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} server error: ${e.message}`));
|
|
229
|
+
});
|
|
230
|
+
resolve();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const port = server.address().port;
|
|
235
|
+
_relays.set(key, { server, port, activeSockets });
|
|
236
|
+
if (forceDebug) {
|
|
237
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} 127.0.0.1:${port} -> ${upstream.host}:${upstream.port} (auth user "${upstream.username}")`));
|
|
238
|
+
}
|
|
239
|
+
return port;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Sync lookup of an already-started relay's port. Returns null if no relay
|
|
244
|
+
* has been started for this upstream (caller should have called ensureRelay
|
|
245
|
+
* upfront).
|
|
246
|
+
*/
|
|
247
|
+
function getRelayPort(upstream) {
|
|
248
|
+
const r = _relays.get(upstreamKey(upstream));
|
|
249
|
+
return r ? r.port : null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Tear down every relay: destroy in-flight sockets, close listeners.
|
|
254
|
+
* Safe to call multiple times.
|
|
255
|
+
*/
|
|
256
|
+
async function closeAllRelays(forceDebug = false) {
|
|
257
|
+
for (const [key, r] of _relays) {
|
|
258
|
+
for (const s of r.activeSockets) { try { s.destroy(); } catch (_) {} }
|
|
259
|
+
await new Promise((res) => {
|
|
260
|
+
try { r.server.close(() => res()); } catch (_) { res(); }
|
|
261
|
+
});
|
|
262
|
+
if (forceDebug) console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} closed relay for ${key}`));
|
|
263
|
+
}
|
|
264
|
+
_relays.clear();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { ensureRelay, getRelayPort, closeAllRelays };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// === Shared async-spawn helper ===
|
|
2
|
+
// Single canonical implementation of "spawn an external command, collect
|
|
3
|
+
// stdout/stderr with hard caps, enforce a kill timeout" — extracted from
|
|
4
|
+
// what used to be ~400 lines of near-identical Promise wrappers across
|
|
5
|
+
// lib/curl.js, lib/searchstring.js, and lib/grep.js (two sites in the
|
|
6
|
+
// latter). Each caller previously had its own copy of the same pattern:
|
|
7
|
+
// truncate-on-cap, SIGKILL belt-and-braces timer, stdout buffer concat,
|
|
8
|
+
// error/close handlers.
|
|
9
|
+
//
|
|
10
|
+
// Design notes:
|
|
11
|
+
// - Resolves (never rejects). Callers inspect the result object to
|
|
12
|
+
// distinguish failure modes instead of needing try/catch. Pre-existing
|
|
13
|
+
// call sites all converted failure into a result-object anyway —
|
|
14
|
+
// this just standardizes the shape.
|
|
15
|
+
// - stdout/stderr returned as Buffer so the caller decides decoding
|
|
16
|
+
// (curl's `--write-out` metadata is line-oriented and tolerant of
|
|
17
|
+
// UTF-8 decoding mid-multibyte; binary HTTP responses are not).
|
|
18
|
+
// - lib/wireguard_vpn.js intentionally stays on spawnSync — its calls
|
|
19
|
+
// are startup-only (validation, health check) and the synchronous
|
|
20
|
+
// semantics are simpler. Not all spawn calls benefit from async.
|
|
21
|
+
|
|
22
|
+
const { spawn } = require('child_process');
|
|
23
|
+
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
25
|
+
const DEFAULT_MAX_STDOUT = 50 * 1024 * 1024; // 50MB
|
|
26
|
+
const KILL_GRACE_MS = 5000; // belt-and-braces SIGKILL after timeout+grace
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Spawn a process asynchronously, collect stdout/stderr with hard caps,
|
|
30
|
+
* enforce a kill timeout. Resolves with a result object describing what
|
|
31
|
+
* happened — never rejects.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} cmd - Executable name (no shell — args are not parsed)
|
|
34
|
+
* @param {string[]} args - Argument array
|
|
35
|
+
* @param {object} [opts]
|
|
36
|
+
* @param {number} [opts.timeout=30000] - Soft timeout in ms; primary
|
|
37
|
+
* limit is whatever the executable itself enforces (e.g. curl
|
|
38
|
+
* --max-time). This is the belt-and-braces SIGKILL deadline fired at
|
|
39
|
+
* `timeout + 5000` ms.
|
|
40
|
+
* @param {number} [opts.maxStdout=52428800] - Cap on stdout collection.
|
|
41
|
+
* When the cap is exceeded the child is killed with SIGTERM and the
|
|
42
|
+
* result has `truncated: true`.
|
|
43
|
+
* @param {string|Buffer} [opts.input] - Data to write to the child's
|
|
44
|
+
* stdin then close. EPIPE on stdin is swallowed (child may exit early).
|
|
45
|
+
* @param {boolean} [opts.collectStderr=true] - When false, stderr is
|
|
46
|
+
* drained but not retained (saves memory when caller doesn't need it).
|
|
47
|
+
* @returns {Promise<{
|
|
48
|
+
* code: number|null,
|
|
49
|
+
* signal: string|null,
|
|
50
|
+
* stdout: Buffer,
|
|
51
|
+
* stderr: Buffer,
|
|
52
|
+
* truncated: boolean,
|
|
53
|
+
* error: string|null
|
|
54
|
+
* }>}
|
|
55
|
+
*/
|
|
56
|
+
function runProcess(cmd, args, opts = {}) {
|
|
57
|
+
const {
|
|
58
|
+
timeout = DEFAULT_TIMEOUT_MS,
|
|
59
|
+
maxStdout = DEFAULT_MAX_STDOUT,
|
|
60
|
+
input,
|
|
61
|
+
collectStderr = true
|
|
62
|
+
} = opts;
|
|
63
|
+
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
let child;
|
|
66
|
+
try {
|
|
67
|
+
child = spawn(cmd, args);
|
|
68
|
+
} catch (spawnErr) {
|
|
69
|
+
resolve({
|
|
70
|
+
code: null, signal: null,
|
|
71
|
+
stdout: Buffer.alloc(0), stderr: Buffer.alloc(0),
|
|
72
|
+
truncated: false, error: spawnErr.message
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stdoutChunks = [];
|
|
78
|
+
const stderrChunks = [];
|
|
79
|
+
let stdoutBytes = 0;
|
|
80
|
+
let truncated = false;
|
|
81
|
+
|
|
82
|
+
child.stdout.on('data', (chunk) => {
|
|
83
|
+
if (truncated) return;
|
|
84
|
+
if (stdoutBytes + chunk.length > maxStdout) {
|
|
85
|
+
truncated = true;
|
|
86
|
+
try { child.kill('SIGTERM'); } catch (_) {}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
stdoutBytes += chunk.length;
|
|
90
|
+
stdoutChunks.push(chunk);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (collectStderr) {
|
|
94
|
+
child.stderr.on('data', (chunk) => { stderrChunks.push(chunk); });
|
|
95
|
+
} else {
|
|
96
|
+
child.stderr.on('data', () => {}); // drain but discard
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// SIGKILL belt-and-braces after timeout+grace. unref'd so the timer
|
|
100
|
+
// doesn't keep the event loop alive on its own; if the process exits
|
|
101
|
+
// earlier, the timer is cleared in the close handler.
|
|
102
|
+
const killTimer = setTimeout(() => {
|
|
103
|
+
try { child.kill('SIGKILL'); } catch (_) {}
|
|
104
|
+
}, timeout + KILL_GRACE_MS);
|
|
105
|
+
if (typeof killTimer.unref === 'function') killTimer.unref();
|
|
106
|
+
|
|
107
|
+
child.on('error', (err) => {
|
|
108
|
+
clearTimeout(killTimer);
|
|
109
|
+
resolve({
|
|
110
|
+
code: null, signal: null,
|
|
111
|
+
stdout: Buffer.concat(stdoutChunks),
|
|
112
|
+
stderr: Buffer.concat(stderrChunks),
|
|
113
|
+
truncated, error: err.message
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
child.on('close', (code, signal) => {
|
|
118
|
+
clearTimeout(killTimer);
|
|
119
|
+
resolve({
|
|
120
|
+
code, signal,
|
|
121
|
+
stdout: Buffer.concat(stdoutChunks),
|
|
122
|
+
stderr: Buffer.concat(stderrChunks),
|
|
123
|
+
truncated, error: null
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (input !== undefined) {
|
|
128
|
+
// EPIPE if the child exited before we finished writing (e.g. grep
|
|
129
|
+
// matched and bailed early, or our truncation kill fired). Swallow
|
|
130
|
+
// so it doesn't surface as an unhandled stream error.
|
|
131
|
+
child.stdin.on('error', () => {});
|
|
132
|
+
child.stdin.end(input);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { runProcess };
|