@fanboynz/network-scanner 2.0.66 → 3.0.1
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 +140 -11
- package/CHANGELOG.md +164 -0
- package/CLAUDE.md +40 -7
- package/README.md +29 -4
- package/lib/adblock-rust.js +23 -18
- package/lib/adblock.js +127 -82
- package/lib/browserexit.js +213 -203
- package/lib/browserhealth.js +85 -61
- 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 +341 -176
- 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 +9 -2
- package/lib/redirect.js +47 -31
- package/lib/referrer.js +158 -165
- package/lib/searchstring.js +290 -381
- package/lib/smart-cache.js +141 -91
- package/lib/socks-relay.js +21 -7
- package/lib/spawn-async.js +137 -0
- package/lib/validate_rules.js +188 -176
- package/lib/wireguard_vpn.js +111 -117
- package/nwss.js +743 -159
- package/package.json +4 -4
- package/scripts/test-stealth.js +281 -0
package/lib/browserexit.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { execSync } = require('child_process');
|
|
10
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
10
11
|
|
|
11
12
|
// Constants for temp file cleanup
|
|
12
13
|
const CHROME_TEMP_PATHS = [
|
|
@@ -21,6 +22,16 @@ const CHROME_TEMP_PATTERNS = [
|
|
|
21
22
|
/^puppeteer-/
|
|
22
23
|
];
|
|
23
24
|
|
|
25
|
+
// Precomputed colored subsystem prefixes — used so debug/temp-cleanup/user-data
|
|
26
|
+
// log lines look the same as the rest of the codebase's colorized output.
|
|
27
|
+
// Previously these were raw template literals like `[debug] [browser] ...`
|
|
28
|
+
// which printed uncolored, while formatLogMessage-routed messages elsewhere
|
|
29
|
+
// in the codebase had colored [debug] tags. That produced inconsistent
|
|
30
|
+
// 'sometimes colored, sometimes not' output in scan logs.
|
|
31
|
+
const BROWSER_TAG = messageColors.fileOp('[browser]');
|
|
32
|
+
const TEMP_CLEANUP_TAG = messageColors.cleanup('[temp-cleanup]');
|
|
33
|
+
const USER_DATA_TAG = messageColors.fileOp('[user-data]');
|
|
34
|
+
|
|
24
35
|
/**
|
|
25
36
|
* Count and remove matching Chrome/Puppeteer temp entries from a directory using fs
|
|
26
37
|
* @param {string} basePath - Directory to scan
|
|
@@ -32,7 +43,7 @@ function cleanTempDir(basePath, forceDebug) {
|
|
|
32
43
|
try {
|
|
33
44
|
entries = fs.readdirSync(basePath);
|
|
34
45
|
} catch {
|
|
35
|
-
if (forceDebug) console.log(
|
|
46
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} Cannot read ${basePath}`));
|
|
36
47
|
return 0;
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -47,9 +58,9 @@ function cleanTempDir(basePath, forceDebug) {
|
|
|
47
58
|
try {
|
|
48
59
|
fs.rmSync(path.join(basePath, entry), { recursive: true, force: true });
|
|
49
60
|
cleaned++;
|
|
50
|
-
if (forceDebug) console.log(
|
|
61
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} Removed ${basePath}/${entry}`));
|
|
51
62
|
} catch (rmErr) {
|
|
52
|
-
if (forceDebug) console.log(
|
|
63
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} Failed to remove ${basePath}/${entry}: ${rmErr.message}`));
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
@@ -61,17 +72,25 @@ function cleanTempDir(basePath, forceDebug) {
|
|
|
61
72
|
* @param {Object} options - Cleanup options
|
|
62
73
|
* @param {boolean} options.includeSnapTemp - Whether to clean snap temp directories
|
|
63
74
|
* @param {boolean} options.forceDebug - Whether to output debug logs
|
|
64
|
-
* @param {boolean} options.comprehensive -
|
|
75
|
+
* @param {boolean} options.comprehensive - Equivalent to includeSnapTemp; kept
|
|
76
|
+
* for source compatibility with prior callers that distinguished the two.
|
|
77
|
+
* @param {boolean} options.verbose - Whether to print a user-facing summary
|
|
78
|
+
* (in addition to forceDebug's developer logs)
|
|
65
79
|
* @returns {Object} Cleanup results
|
|
66
80
|
*/
|
|
67
81
|
function cleanupChromeTempFiles(options = {}) {
|
|
68
82
|
const {
|
|
69
83
|
includeSnapTemp = false,
|
|
70
84
|
forceDebug = false,
|
|
71
|
-
comprehensive = false
|
|
85
|
+
comprehensive = false,
|
|
86
|
+
verbose = false
|
|
72
87
|
} = options;
|
|
73
88
|
|
|
74
89
|
try {
|
|
90
|
+
if (verbose && !forceDebug) {
|
|
91
|
+
console.log(`${TEMP_CLEANUP_TAG} Scanning Chrome/Puppeteer temporary files...`);
|
|
92
|
+
}
|
|
93
|
+
|
|
75
94
|
const paths = comprehensive || includeSnapTemp
|
|
76
95
|
? CHROME_TEMP_PATHS
|
|
77
96
|
: CHROME_TEMP_PATHS.slice(0, 2); // /tmp and /dev/shm only
|
|
@@ -81,56 +100,23 @@ function cleanupChromeTempFiles(options = {}) {
|
|
|
81
100
|
totalCleaned += cleanTempDir(basePath, forceDebug);
|
|
82
101
|
}
|
|
83
102
|
|
|
84
|
-
if (
|
|
85
|
-
console.log(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return { success: true, itemsCleaned: totalCleaned };
|
|
89
|
-
} catch (cleanupErr) {
|
|
90
|
-
if (forceDebug) {
|
|
91
|
-
console.log(`[debug] [temp-cleanup] Chrome cleanup error: ${cleanupErr.message}`);
|
|
92
|
-
}
|
|
93
|
-
return { success: false, error: cleanupErr.message, itemsCleaned: 0 };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Comprehensive temp file cleanup that checks all known Chrome temp locations
|
|
99
|
-
* @param {Object} options - Cleanup options
|
|
100
|
-
* @param {boolean} options.forceDebug - Whether to output debug logs
|
|
101
|
-
* @param {boolean} options.verbose - Whether to show verbose output
|
|
102
|
-
* @returns {Object} Cleanup results
|
|
103
|
-
*/
|
|
104
|
-
function comprehensiveChromeTempCleanup(options = {}) {
|
|
105
|
-
const { forceDebug = false, verbose = false } = options;
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
if (verbose && !forceDebug) {
|
|
109
|
-
console.log(`[temp-cleanup] Scanning Chrome/Puppeteer temporary files...`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let totalCleaned = 0;
|
|
113
|
-
for (const basePath of CHROME_TEMP_PATHS) {
|
|
114
|
-
totalCleaned += cleanTempDir(basePath, forceDebug);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (verbose && totalCleaned > 0) {
|
|
118
|
-
console.log(`[temp-cleanup] Removed ${totalCleaned} temporary file(s)/folder(s)`);
|
|
119
|
-
} else if (verbose && totalCleaned === 0) {
|
|
120
|
-
console.log(`[temp-cleanup] Clean - no remaining temporary files`);
|
|
103
|
+
if (verbose) {
|
|
104
|
+
console.log(totalCleaned > 0
|
|
105
|
+
? `${TEMP_CLEANUP_TAG} Removed ${totalCleaned} temporary file(s)/folder(s)`
|
|
106
|
+
: `${TEMP_CLEANUP_TAG} Clean - no remaining temporary files`);
|
|
121
107
|
} else if (forceDebug) {
|
|
122
|
-
console.log(
|
|
108
|
+
console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} Cleanup completed (${totalCleaned} items)`));
|
|
123
109
|
}
|
|
124
110
|
|
|
125
111
|
return { success: true, itemsCleaned: totalCleaned };
|
|
126
|
-
} catch (
|
|
127
|
-
const errorMsg = `
|
|
112
|
+
} catch (cleanupErr) {
|
|
113
|
+
const errorMsg = `Chrome temp cleanup failed: ${cleanupErr.message}`;
|
|
128
114
|
if (verbose) {
|
|
129
|
-
console.warn(
|
|
115
|
+
console.warn(`${TEMP_CLEANUP_TAG} ${errorMsg}`);
|
|
130
116
|
} else if (forceDebug) {
|
|
131
|
-
console.log(
|
|
117
|
+
console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} ${errorMsg}`));
|
|
132
118
|
}
|
|
133
|
-
return { success: false, error:
|
|
119
|
+
return { success: false, error: cleanupErr.message, itemsCleaned: 0 };
|
|
134
120
|
}
|
|
135
121
|
}
|
|
136
122
|
|
|
@@ -145,26 +131,21 @@ async function cleanupUserDataDir(userDataDir, forceDebug = false) {
|
|
|
145
131
|
return { success: true, cleaned: false, reason: 'No user data directory specified' };
|
|
146
132
|
}
|
|
147
133
|
|
|
134
|
+
// fs.rmSync({force: true}) treats ENOENT as a no-op, so an existsSync
|
|
135
|
+
// pre-check is two syscalls where one would do (and a TOCTOU besides).
|
|
136
|
+
// If the dir was already gone we just report cleaned:true without drama.
|
|
148
137
|
try {
|
|
149
|
-
|
|
150
|
-
if (!fs.existsSync(userDataDir)) {
|
|
151
|
-
if (forceDebug) {
|
|
152
|
-
console.log(`[debug] [user-data] User data directory does not exist: ${userDataDir}`);
|
|
153
|
-
}
|
|
154
|
-
return { success: true, cleaned: false, reason: 'Directory does not exist' };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
138
|
fs.rmSync(userDataDir, { recursive: true, force: true });
|
|
158
|
-
|
|
139
|
+
|
|
159
140
|
if (forceDebug) {
|
|
160
|
-
console.log(
|
|
141
|
+
console.log(formatLogMessage('debug', `${USER_DATA_TAG} Cleaned user data directory: ${userDataDir}`));
|
|
161
142
|
}
|
|
162
|
-
|
|
143
|
+
|
|
163
144
|
return { success: true, cleaned: true };
|
|
164
|
-
|
|
145
|
+
|
|
165
146
|
} catch (rmErr) {
|
|
166
147
|
if (forceDebug) {
|
|
167
|
-
console.log(
|
|
148
|
+
console.log(formatLogMessage('debug', `${USER_DATA_TAG} Failed to remove user data directory ${userDataDir}: ${rmErr.message}`));
|
|
168
149
|
}
|
|
169
150
|
return { success: false, error: rmErr.message, cleaned: false };
|
|
170
151
|
}
|
|
@@ -178,20 +159,20 @@ async function cleanupUserDataDir(userDataDir, forceDebug = false) {
|
|
|
178
159
|
*/
|
|
179
160
|
async function gracefulBrowserCleanup(browser, forceDebug = false) {
|
|
180
161
|
// FIX: Check browser connection before operations
|
|
181
|
-
if (!browser || !browser.
|
|
182
|
-
if (forceDebug) console.log(
|
|
162
|
+
if (!browser || !browser.connected) {
|
|
163
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser not connected, skipping cleanup`));
|
|
183
164
|
return;
|
|
184
165
|
}
|
|
185
|
-
if (forceDebug) console.log(
|
|
166
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Getting all browser pages...`));
|
|
186
167
|
let pages;
|
|
187
168
|
try {
|
|
188
169
|
pages = await browser.pages();
|
|
189
170
|
} catch (pagesErr) {
|
|
190
|
-
if (forceDebug) console.log(
|
|
171
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Failed to get pages: ${pagesErr.message}`));
|
|
191
172
|
return;
|
|
192
173
|
}
|
|
193
|
-
if (forceDebug) console.log(
|
|
194
|
-
|
|
174
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Found ${pages.length} pages to close`));
|
|
175
|
+
|
|
195
176
|
await Promise.all(pages.map(async (page) => {
|
|
196
177
|
if (!page.isClosed()) {
|
|
197
178
|
try {
|
|
@@ -202,29 +183,29 @@ async function gracefulBrowserCleanup(browser, forceDebug = false) {
|
|
|
202
183
|
} catch (urlErr) {
|
|
203
184
|
// Page closed between check and url call
|
|
204
185
|
}
|
|
205
|
-
|
|
206
|
-
if (forceDebug) console.log(
|
|
186
|
+
|
|
187
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Closing page: ${pageUrl}`));
|
|
207
188
|
await page.close();
|
|
208
|
-
if (forceDebug) console.log(
|
|
189
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Page closed successfully`));
|
|
209
190
|
} catch (err) {
|
|
210
191
|
// Force close if normal close fails
|
|
211
|
-
if (forceDebug) console.log(
|
|
192
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Force closing page: ${err.message}`));
|
|
212
193
|
}
|
|
213
194
|
}
|
|
214
195
|
}));
|
|
215
|
-
|
|
216
|
-
if (forceDebug) console.log(
|
|
217
|
-
|
|
196
|
+
|
|
197
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} All pages closed, closing browser...`));
|
|
198
|
+
|
|
218
199
|
// FIX: Check browser is still connected before closing
|
|
219
200
|
try {
|
|
220
|
-
if (browser.
|
|
201
|
+
if (browser.connected) {
|
|
221
202
|
await browser.close();
|
|
222
|
-
if (forceDebug) console.log(
|
|
203
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser closed successfully`));
|
|
223
204
|
} else {
|
|
224
|
-
if (forceDebug) console.log(
|
|
205
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser already disconnected`));
|
|
225
206
|
}
|
|
226
207
|
} catch (closeErr) {
|
|
227
|
-
if (forceDebug) console.log(
|
|
208
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser close failed: ${closeErr.message}`));
|
|
228
209
|
}
|
|
229
210
|
}
|
|
230
211
|
|
|
@@ -236,115 +217,131 @@ async function gracefulBrowserCleanup(browser, forceDebug = false) {
|
|
|
236
217
|
*/
|
|
237
218
|
async function forceBrowserKill(browser, forceDebug = false) {
|
|
238
219
|
try {
|
|
239
|
-
if (forceDebug) console.log(
|
|
240
|
-
|
|
220
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Attempting force closure of browser process...`));
|
|
221
|
+
|
|
241
222
|
const browserProcess = browser.process();
|
|
242
223
|
if (!browserProcess || !browserProcess.pid) {
|
|
243
|
-
if (forceDebug) console.log(
|
|
224
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} No browser process available`));
|
|
244
225
|
return;
|
|
245
226
|
}
|
|
246
227
|
|
|
247
228
|
const mainPid = browserProcess.pid;
|
|
248
|
-
if (forceDebug) console.log(
|
|
229
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Main Chrome PID: ${mainPid}`));
|
|
249
230
|
|
|
250
|
-
//
|
|
251
|
-
|
|
231
|
+
// PRIMARY PATH: kill OUR browser process tree only.
|
|
232
|
+
//
|
|
233
|
+
// The previous primary path ran `ps | grep "puppeteer.*chrome"` and
|
|
234
|
+
// SIGTERM'd every match, which kills puppeteer-chrome processes spawned
|
|
235
|
+
// by ANY other process on the machine (concurrent nwss runs, automate
|
|
236
|
+
// scripts, other tools). The fix is to walk the live process table once
|
|
237
|
+
// and filter to PIDs whose ancestor chain leads back to OUR mainPid.
|
|
238
|
+
// The broad sweep stays as the fallback only if ps fails or the targeted
|
|
239
|
+
// kill doesn't take down the main PID.
|
|
240
|
+
let killedTargeted = false;
|
|
252
241
|
try {
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
242
|
+
const psOutput = execSync(`ps -eo pid,ppid,cmd`, { encoding: 'utf8', timeout: 5000 });
|
|
243
|
+
const psLines = psOutput.trim().split('\n').slice(1); // drop header
|
|
244
|
+
|
|
245
|
+
// pid -> ppid map for ancestry walks; collect chrome-ish candidates.
|
|
246
|
+
const ppidOf = new Map();
|
|
247
|
+
const chromeCandidates = new Set();
|
|
248
|
+
for (const line of psLines) {
|
|
249
|
+
const m = line.trim().match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
250
|
+
if (!m) continue;
|
|
251
|
+
const pid = parseInt(m[1], 10);
|
|
252
|
+
const ppid = parseInt(m[2], 10);
|
|
253
|
+
if (Number.isNaN(pid) || Number.isNaN(ppid)) continue;
|
|
254
|
+
ppidOf.set(pid, ppid);
|
|
255
|
+
// Chrome's helpers (gpu, renderer, utility) don't all carry the
|
|
256
|
+
// 'puppeteer' substring; rely on ancestry instead of cmd matching.
|
|
257
|
+
// 'chrom' covers both 'chrome' and 'chromium' in one substring scan.
|
|
258
|
+
if (m[3].includes('chrom')) {
|
|
259
|
+
chromeCandidates.add(pid);
|
|
267
260
|
}
|
|
268
261
|
}
|
|
269
|
-
|
|
262
|
+
|
|
263
|
+
// Filter candidates to descendants of (or equal to) mainPid.
|
|
264
|
+
const ourPids = [mainPid];
|
|
265
|
+
for (const pid of chromeCandidates) {
|
|
266
|
+
if (pid === mainPid) continue;
|
|
267
|
+
let cur = ppidOf.get(pid);
|
|
268
|
+
let hops = 0;
|
|
269
|
+
while (cur && cur > 1 && hops < 128) {
|
|
270
|
+
if (cur === mainPid) { ourPids.push(pid); break; }
|
|
271
|
+
cur = ppidOf.get(cur);
|
|
272
|
+
hops++;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
270
276
|
if (forceDebug) {
|
|
271
|
-
console.log(
|
|
277
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} Targeted kill: ${ourPids.length} PIDs in mainPid=${mainPid}'s tree: [${ourPids.join(', ')}]`));
|
|
272
278
|
}
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
for (const pid of
|
|
276
|
-
try {
|
|
277
|
-
|
|
278
|
-
if (forceDebug
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
|
|
280
|
+
// SIGTERM the tree gracefully.
|
|
281
|
+
for (const pid of ourPids) {
|
|
282
|
+
try { process.kill(pid, 'SIGTERM'); }
|
|
283
|
+
catch (killErr) {
|
|
284
|
+
if (forceDebug && killErr.code !== 'ESRCH') {
|
|
285
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} SIGTERM to PID ${pid} failed: ${killErr.message}`));
|
|
286
|
+
}
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Force kill any remaining processes with SIGKILL
|
|
288
|
-
for (const pid of pidsToKill) {
|
|
289
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
290
|
+
|
|
291
|
+
// SIGKILL stragglers.
|
|
292
|
+
for (const pid of ourPids) {
|
|
289
293
|
try {
|
|
290
|
-
|
|
291
|
-
process.kill(pid, 0);
|
|
292
|
-
// If we reach here, process still exists - force kill it
|
|
294
|
+
process.kill(pid, 0); // existence probe
|
|
293
295
|
process.kill(pid, 'SIGKILL');
|
|
294
|
-
if (forceDebug) console.log(
|
|
296
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Force-killed PID ${pid}`));
|
|
295
297
|
} catch (checkErr) {
|
|
296
|
-
// Process already dead (ESRCH error is expected and good)
|
|
297
298
|
if (forceDebug && checkErr.code !== 'ESRCH') {
|
|
298
|
-
console.log(
|
|
299
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} Probe/kill PID ${pid} error: ${checkErr.message}`));
|
|
299
300
|
}
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const remainingCount = execSync(verifyCmd, { encoding: 'utf8', timeout: 2000 }).trim();
|
|
308
|
-
console.log(`[debug] [browser] Remaining Chrome processes after cleanup: ${remainingCount}`);
|
|
309
|
-
} catch (verifyErr) {
|
|
310
|
-
console.log(`[debug] [browser] Could not verify process cleanup: ${verifyErr.message}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
304
|
+
// Confirm mainPid is gone — if not, the targeted kill is considered
|
|
305
|
+
// not-effective and we fall through to the broad sweep below.
|
|
306
|
+
try { process.kill(mainPid, 0); }
|
|
307
|
+
catch (e) { if (e.code === 'ESRCH') killedTargeted = true; }
|
|
314
308
|
} catch (psErr) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
309
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} ps -eo pid,ppid,cmd failed: ${psErr.message}`));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// FALLBACK PATH: targeted kill failed or ps wasn't available. Try the
|
|
313
|
+
// spawned-process handle directly, then last-resort the broad pkill.
|
|
314
|
+
// (killAllPuppeteerChrome in the next module is the truly nuclear option.)
|
|
315
|
+
if (!killedTargeted) {
|
|
316
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Targeted kill did not confirm mainPid death; trying browserProcess handle`));
|
|
318
317
|
try {
|
|
319
318
|
browserProcess.kill('SIGTERM');
|
|
320
319
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
321
|
-
|
|
322
|
-
// Check if main process still exists and force kill if needed
|
|
323
320
|
try {
|
|
324
|
-
process.kill(mainPid, 0);
|
|
325
|
-
browserProcess.kill('SIGKILL');
|
|
326
|
-
if (forceDebug) console.log(
|
|
321
|
+
process.kill(mainPid, 0);
|
|
322
|
+
browserProcess.kill('SIGKILL');
|
|
323
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Fallback: Force-killed main PID ${mainPid}`));
|
|
327
324
|
} catch (checkErr) {
|
|
328
325
|
if (forceDebug && checkErr.code !== 'ESRCH') {
|
|
329
|
-
console.log(
|
|
326
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} Fallback probe PID ${mainPid} error: ${checkErr.message}`));
|
|
330
327
|
}
|
|
331
328
|
}
|
|
332
329
|
} catch (fallbackErr) {
|
|
333
|
-
if (forceDebug) console.log(
|
|
330
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Fallback kill failed: ${fallbackErr.message}`));
|
|
334
331
|
}
|
|
335
332
|
}
|
|
336
|
-
|
|
333
|
+
|
|
337
334
|
} catch (forceKillErr) {
|
|
338
|
-
console.error(
|
|
335
|
+
console.error(formatLogMessage('error', `${BROWSER_TAG} Failed to force kill browser: ${forceKillErr.message}`));
|
|
339
336
|
}
|
|
340
|
-
|
|
337
|
+
|
|
341
338
|
try {
|
|
342
|
-
if (browser.
|
|
339
|
+
if (browser.connected) {
|
|
343
340
|
browser.disconnect();
|
|
344
|
-
if (forceDebug) console.log(
|
|
341
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser connection disconnected`));
|
|
345
342
|
}
|
|
346
343
|
} catch (disconnectErr) {
|
|
347
|
-
if (forceDebug) console.log(
|
|
344
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Failed to disconnect browser: ${disconnectErr.message}`));
|
|
348
345
|
}
|
|
349
346
|
}
|
|
350
347
|
|
|
@@ -354,22 +351,22 @@ async function forceBrowserKill(browser, forceDebug = false) {
|
|
|
354
351
|
* @returns {Promise<void>}
|
|
355
352
|
*/
|
|
356
353
|
async function killAllPuppeteerChrome(forceDebug = false) {
|
|
357
|
-
try {
|
|
358
|
-
if (forceDebug) console.log(
|
|
359
|
-
|
|
354
|
+
try {
|
|
355
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Nuclear option: killing all puppeteer Chrome processes...`));
|
|
356
|
+
|
|
360
357
|
try {
|
|
361
358
|
execSync(`pkill -f "puppeteer.*chrome"`, { stdio: 'ignore', timeout: 5000 });
|
|
362
|
-
if (forceDebug) console.log(
|
|
359
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} pkill completed`));
|
|
363
360
|
} catch (pkillErr) {
|
|
364
361
|
if (forceDebug && pkillErr.status !== 1) {
|
|
365
|
-
console.log(
|
|
362
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} pkill failed with status ${pkillErr.status}: ${pkillErr.message}`));
|
|
366
363
|
}
|
|
367
364
|
}
|
|
368
|
-
|
|
365
|
+
|
|
369
366
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
370
|
-
|
|
367
|
+
|
|
371
368
|
} catch (nuclearErr) {
|
|
372
|
-
console.error(
|
|
369
|
+
console.error(formatLogMessage('error', `${BROWSER_TAG} Nuclear Chrome kill failed: ${nuclearErr.message}`));
|
|
373
370
|
}
|
|
374
371
|
}
|
|
375
372
|
|
|
@@ -397,11 +394,16 @@ async function handleBrowserExit(browser, options = {}) {
|
|
|
397
394
|
verbose = false
|
|
398
395
|
} = options;
|
|
399
396
|
|
|
400
|
-
if (forceDebug) console.log(
|
|
397
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Starting comprehensive browser cleanup...`));
|
|
401
398
|
|
|
399
|
+
// All fields declared upfront so step 3 doesn't extend the object shape at
|
|
400
|
+
// runtime (V8 hidden-class transition); the result shape is also fully
|
|
401
|
+
// documented in one place this way.
|
|
402
402
|
const results = {
|
|
403
403
|
browserClosed: false,
|
|
404
404
|
tempFilesCleanedCount: 0,
|
|
405
|
+
tempFilesCleanedSuccess: false,
|
|
406
|
+
tempFilesCleanedComprehensive: false,
|
|
405
407
|
userDataCleaned: false,
|
|
406
408
|
success: false,
|
|
407
409
|
errors: []
|
|
@@ -410,31 +412,46 @@ async function handleBrowserExit(browser, options = {}) {
|
|
|
410
412
|
try {
|
|
411
413
|
// Step 1: Browser process cleanup
|
|
412
414
|
try {
|
|
413
|
-
// Race cleanup against timeout
|
|
415
|
+
// Race cleanup against a timeout. Attach a no-op .catch to the racing
|
|
416
|
+
// cleanup promise so that when the timeout wins the eventual rejection
|
|
417
|
+
// from the still-running graceful cleanup (page.close / browser.close
|
|
418
|
+
// failing after we move on to forceBrowserKill) doesn't surface as an
|
|
419
|
+
// unhandledRejection warning.
|
|
420
|
+
const cleanupPromise = gracefulBrowserCleanup(browser, forceDebug);
|
|
421
|
+
cleanupPromise.catch(() => {});
|
|
414
422
|
await Promise.race([
|
|
415
|
-
|
|
416
|
-
new Promise((_, reject) =>
|
|
423
|
+
cleanupPromise,
|
|
424
|
+
new Promise((_, reject) =>
|
|
417
425
|
setTimeout(() => reject(new Error('Browser cleanup timeout')), timeout)
|
|
418
426
|
)
|
|
419
427
|
]);
|
|
420
|
-
|
|
428
|
+
|
|
421
429
|
results.browserClosed = true;
|
|
422
|
-
|
|
430
|
+
|
|
423
431
|
} catch (browserCloseErr) {
|
|
424
432
|
results.errors.push(`Browser cleanup failed: ${browserCloseErr.message}`);
|
|
425
|
-
|
|
433
|
+
|
|
426
434
|
if (forceDebug || verbose) {
|
|
427
|
-
console.warn(
|
|
435
|
+
console.warn(formatLogMessage('warn', `${BROWSER_TAG} Browser cleanup had issues: ${browserCloseErr.message}`));
|
|
428
436
|
}
|
|
429
|
-
|
|
430
|
-
// Attempt force kill
|
|
437
|
+
|
|
438
|
+
// Attempt targeted force kill of OUR process tree.
|
|
431
439
|
await forceBrowserKill(browser, forceDebug);
|
|
432
440
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
441
|
+
// Only escalate to the broad pkill if our browser is still up. A
|
|
442
|
+
// successful targeted kill breaks the CDP WebSocket, which flips
|
|
443
|
+
// isConnected() to false — in that case the nuclear path would just
|
|
444
|
+
// murder other people's puppeteer-chrome instances for no gain.
|
|
445
|
+
let stillConnected = false;
|
|
446
|
+
try { stillConnected = browser.connected; } catch (_) {}
|
|
447
|
+
if (stillConnected) {
|
|
448
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Targeted force kill didn't take — escalating to nuclear cleanup`));
|
|
449
|
+
await killAllPuppeteerChrome(forceDebug);
|
|
450
|
+
} else if (forceDebug) {
|
|
451
|
+
console.log(formatLogMessage('debug', `${BROWSER_TAG} Targeted force kill succeeded; skipping nuclear cleanup`));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
results.browserClosed = true; // Assume success after force/nuclear path
|
|
438
455
|
}
|
|
439
456
|
|
|
440
457
|
// Step 2: User data directory cleanup
|
|
@@ -446,31 +463,24 @@ async function handleBrowserExit(browser, options = {}) {
|
|
|
446
463
|
}
|
|
447
464
|
}
|
|
448
465
|
|
|
449
|
-
// Step 3: Temp file cleanup
|
|
466
|
+
// Step 3: Temp file cleanup. Both branches of the prior code ended up
|
|
467
|
+
// walking all three CHROME_TEMP_PATHS (comprehensive used all 3 directly;
|
|
468
|
+
// standard set includeSnapTemp:true which expands to all 3 too) — the
|
|
469
|
+
// only meaningful difference was the verbose summary log. One call now.
|
|
450
470
|
if (cleanTempFiles) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
471
|
+
const tempResult = await cleanupChromeTempFiles({
|
|
472
|
+
includeSnapTemp: true,
|
|
473
|
+
comprehensive: comprehensiveCleanup,
|
|
474
|
+
forceDebug,
|
|
475
|
+
verbose
|
|
476
|
+
});
|
|
477
|
+
results.tempFilesCleanedSuccess = tempResult.success;
|
|
478
|
+
results.tempFilesCleanedComprehensive = comprehensiveCleanup;
|
|
479
|
+
|
|
480
|
+
if (tempResult.success) {
|
|
481
|
+
results.tempFilesCleanedCount = tempResult.itemsCleaned;
|
|
461
482
|
} else {
|
|
462
|
-
|
|
463
|
-
includeSnapTemp: true,
|
|
464
|
-
forceDebug,
|
|
465
|
-
comprehensive: false
|
|
466
|
-
});
|
|
467
|
-
results.tempFilesCleanedSuccess = tempResult.success;
|
|
468
|
-
|
|
469
|
-
if (tempResult.success) {
|
|
470
|
-
results.tempFilesCleanedCount = tempResult.itemsCleaned;
|
|
471
|
-
} else {
|
|
472
|
-
results.errors.push(`Standard temp cleanup failed: ${tempResult.error}`);
|
|
473
|
-
}
|
|
483
|
+
results.errors.push(`${comprehensiveCleanup ? 'Comprehensive' : 'Standard'} temp cleanup failed: ${tempResult.error}`);
|
|
474
484
|
}
|
|
475
485
|
}
|
|
476
486
|
|
|
@@ -479,23 +489,24 @@ async function handleBrowserExit(browser, options = {}) {
|
|
|
479
489
|
(results.errors.length === 0 || !exitOnFailure);
|
|
480
490
|
|
|
481
491
|
if (forceDebug) {
|
|
482
|
-
console.log(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
console.log(formatLogMessage('debug',
|
|
493
|
+
`${BROWSER_TAG} Cleanup completed - Browser: ${results.browserClosed}, ` +
|
|
494
|
+
`Temp files: ${results.tempFilesCleanedCount || 0}, ` +
|
|
495
|
+
`User data: ${results.userDataCleaned}, ` +
|
|
496
|
+
`Errors: ${results.errors.length}`));
|
|
486
497
|
}
|
|
487
|
-
|
|
498
|
+
|
|
488
499
|
return results;
|
|
489
|
-
|
|
500
|
+
|
|
490
501
|
} catch (overallErr) {
|
|
491
502
|
results.errors.push(`Overall cleanup failed: ${overallErr.message}`);
|
|
492
503
|
results.success = false;
|
|
493
|
-
|
|
504
|
+
|
|
494
505
|
if (exitOnFailure) {
|
|
495
|
-
if (forceDebug) console.log(
|
|
506
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Forcing process exit due to cleanup failure`));
|
|
496
507
|
process.exit(1);
|
|
497
508
|
}
|
|
498
|
-
|
|
509
|
+
|
|
499
510
|
return results;
|
|
500
511
|
}
|
|
501
512
|
}
|
|
@@ -506,7 +517,6 @@ module.exports = {
|
|
|
506
517
|
forceBrowserKill,
|
|
507
518
|
killAllPuppeteerChrome,
|
|
508
519
|
cleanupChromeTempFiles,
|
|
509
|
-
comprehensiveChromeTempCleanup,
|
|
510
520
|
cleanupUserDataDir,
|
|
511
521
|
CHROME_TEMP_PATHS,
|
|
512
522
|
CHROME_TEMP_PATTERNS
|