@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.
@@ -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(`[debug] [temp-cleanup] Cannot read ${basePath}`);
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(`[debug] [temp-cleanup] Removed ${basePath}/${entry}`);
61
+ if (forceDebug) console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} Removed ${basePath}/${entry}`));
51
62
  } catch (rmErr) {
52
- if (forceDebug) console.log(`[debug] [temp-cleanup] Failed to remove ${basePath}/${entry}: ${rmErr.message}`);
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 - Whether to perform comprehensive cleanup of all temp locations
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 (forceDebug) {
85
- console.log(`[debug] [temp-cleanup] Cleanup completed (${totalCleaned} items)`);
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(`[debug] [temp-cleanup] Comprehensive cleanup completed (${totalCleaned} items)`);
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 (err) {
127
- const errorMsg = `Comprehensive temp file cleanup failed: ${err.message}`;
112
+ } catch (cleanupErr) {
113
+ const errorMsg = `Chrome temp cleanup failed: ${cleanupErr.message}`;
128
114
  if (verbose) {
129
- console.warn(`[temp-cleanup] ${errorMsg}`);
115
+ console.warn(`${TEMP_CLEANUP_TAG} ${errorMsg}`);
130
116
  } else if (forceDebug) {
131
- console.log(`[debug] [temp-cleanup] ${errorMsg}`);
117
+ console.log(formatLogMessage('debug', `${TEMP_CLEANUP_TAG} ${errorMsg}`));
132
118
  }
133
- return { success: false, error: err.message, itemsCleaned: 0 };
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(`[debug] [user-data] Cleaned user data directory: ${userDataDir}`);
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(`[debug] [user-data] Failed to remove user data directory ${userDataDir}: ${rmErr.message}`);
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.isConnected()) {
182
- if (forceDebug) console.log(`[debug] [browser] Browser not connected, skipping cleanup`);
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(`[debug] [browser] Getting all browser pages...`);
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(`[debug] [browser] Failed to get pages: ${pagesErr.message}`);
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(`[debug] [browser] Found ${pages.length} pages to close`);
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(`[debug] [browser] Closing page: ${pageUrl}`);
186
+
187
+ if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Closing page: ${pageUrl}`));
207
188
  await page.close();
208
- if (forceDebug) console.log(`[debug] [browser] Page closed successfully`);
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(`[debug] [browser] Force closing page: ${err.message}`);
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(`[debug] [browser] All pages closed, closing browser...`);
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.isConnected()) {
201
+ if (browser.connected) {
221
202
  await browser.close();
222
- if (forceDebug) console.log(`[debug] [browser] Browser closed successfully`);
203
+ if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser closed successfully`));
223
204
  } else {
224
- if (forceDebug) console.log(`[debug] [browser] Browser already disconnected`);
205
+ if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser already disconnected`));
225
206
  }
226
207
  } catch (closeErr) {
227
- if (forceDebug) console.log(`[debug] [browser] Browser close failed: ${closeErr.message}`);
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(`[debug] [browser] Attempting force closure of browser process...`);
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(`[debug] [browser] No browser process available`);
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(`[debug] [browser] Main Chrome PID: ${mainPid}`);
229
+ if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Main Chrome PID: ${mainPid}`));
249
230
 
250
- // Find and kill ALL related Chrome processes
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
- // Find all Chrome processes with puppeteer in command line
254
- const psCmd = `ps -eo pid,cmd | grep "puppeteer.*chrome" | grep -v grep`;
255
- const psOutput = execSync(psCmd, { encoding: 'utf8', timeout: 5000 });
256
- const lines = psOutput.trim().split('\n').filter(line => line.trim());
257
-
258
- const pidsToKill = [];
259
-
260
- for (const line of lines) {
261
- const match = line.trim().match(/^\s*(\d+)/);
262
- if (match) {
263
- const pid = parseInt(match[1]);
264
- if (!isNaN(pid)) {
265
- pidsToKill.push(pid);
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(`[debug] [browser] Found ${pidsToKill.length} Chrome processes to kill: [${pidsToKill.join(', ')}]`);
277
+ console.log(formatLogMessage('debug', `${BROWSER_TAG} Targeted kill: ${ourPids.length} PIDs in mainPid=${mainPid}'s tree: [${ourPids.join(', ')}]`));
272
278
  }
273
-
274
- // Kill all processes with SIGTERM first (graceful)
275
- for (const pid of pidsToKill) {
276
- try {
277
- process.kill(pid, 'SIGTERM');
278
- if (forceDebug) console.log(`[debug] [browser] Sent SIGTERM to PID ${pid}`);
279
- } catch (killErr) {
280
- if (forceDebug) console.log(`[debug] [browser] Failed to send SIGTERM to PID ${pid}: ${killErr.message}`);
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
- // Wait for graceful termination
285
- await new Promise(resolve => setTimeout(resolve, 1000));
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
- // Check if process still exists using signal 0
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(`[debug] [browser] Force killed PID ${pid} with SIGKILL`);
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(`[debug] [browser] Error checking/killing PID ${pid}: ${checkErr.message}`);
299
+ console.log(formatLogMessage('debug', `${BROWSER_TAG} Probe/kill PID ${pid} error: ${checkErr.message}`));
299
300
  }
300
301
  }
301
302
  }
302
303
 
303
- // Final verification - check if any processes are still alive
304
- if (forceDebug) {
305
- try {
306
- const verifyCmd = `ps -eo pid,cmd | grep "puppeteer.*chrome" | grep -v grep | wc -l`;
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
- // Fallback to original method if ps command fails
316
- if (forceDebug) console.log(`[debug] [browser] ps command failed, using fallback method: ${psErr.message}`);
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); // Check existence
325
- browserProcess.kill('SIGKILL'); // Force kill if still alive
326
- if (forceDebug) console.log(`[debug] [browser] Fallback: Force killed main PID ${mainPid}`);
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(`[debug] [browser] Fallback check error for PID ${mainPid}: ${checkErr.message}`);
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(`[debug] [browser] Fallback kill failed: ${fallbackErr.message}`);
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(`[error] [browser] Failed to force kill browser: ${forceKillErr.message}`);
335
+ console.error(formatLogMessage('error', `${BROWSER_TAG} Failed to force kill browser: ${forceKillErr.message}`));
339
336
  }
340
-
337
+
341
338
  try {
342
- if (browser.isConnected()) {
339
+ if (browser.connected) {
343
340
  browser.disconnect();
344
- if (forceDebug) console.log(`[debug] [browser] Browser connection disconnected`);
341
+ if (forceDebug) console.log(formatLogMessage('debug', `${BROWSER_TAG} Browser connection disconnected`));
345
342
  }
346
343
  } catch (disconnectErr) {
347
- if (forceDebug) console.log(`[debug] [browser] Failed to disconnect browser: ${disconnectErr.message}`);
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(`[debug] [browser] Nuclear option: killing all puppeteer Chrome processes...`);
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(`[debug] [browser] pkill completed`);
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(`[debug] [browser] pkill failed with status ${pkillErr.status}: ${pkillErr.message}`);
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(`[error] [browser] Nuclear Chrome kill failed: ${nuclearErr.message}`);
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(`[debug] [browser] Starting comprehensive browser cleanup...`);
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
- gracefulBrowserCleanup(browser, forceDebug),
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(`[warn] [browser] Browser cleanup had issues: ${browserCloseErr.message}`);
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
- // Nuclear option if force kill didn't work
434
- if (forceDebug) console.log(`[debug] [browser] Attempting nuclear cleanup...`);
435
- await killAllPuppeteerChrome(forceDebug);
436
-
437
- results.browserClosed = true; // Assume success after nuclear option
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
- if (comprehensiveCleanup) {
452
- const tempResult = await comprehensiveChromeTempCleanup({ forceDebug, verbose });
453
- results.tempFilesCleanedSuccess = tempResult.success;
454
- results.tempFilesCleanedComprehensive = true;
455
-
456
- if (tempResult.success) {
457
- results.tempFilesCleanedCount = tempResult.itemsCleaned;
458
- } else {
459
- results.errors.push(`Comprehensive temp cleanup failed: ${tempResult.error}`);
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
- const tempResult = await cleanupChromeTempFiles({
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(`[debug] [browser] Cleanup completed - Browser: ${results.browserClosed}, ` +
483
- `Temp files: ${results.tempFilesCleanedCount || 0}, ` +
484
- `User data: ${results.userDataCleaned}, ` +
485
- `Errors: ${results.errors.length}`);
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(`[debug] [browser] Forcing process exit due to cleanup failure`);
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