@fanboynz/network-scanner 1.0.35

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.
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Browser exit and cleanup handler module
3
+ * Provides graceful and forced browser closure functionality with comprehensive temp file cleanup
4
+ */
5
+
6
+ // Constants for temp file cleanup
7
+ const CHROME_TEMP_PATHS = [
8
+ '/tmp',
9
+ '/dev/shm',
10
+ '/tmp/snap-private-tmp/snap.chromium/tmp'
11
+ ];
12
+
13
+ const CHROME_TEMP_PATTERNS = [
14
+ '.com.google.Chrome.*', // Google Chrome temp files
15
+ '.org.chromium.Chromium.*',
16
+ 'puppeteer-*',
17
+ '.com.google.Chrome.*' // Ensure Google Chrome pattern is included
18
+ ];
19
+
20
+ /**
21
+ * Clean Chrome temporary files and directories
22
+ * @param {Object} options - Cleanup options
23
+ * @param {boolean} options.includeSnapTemp - Whether to clean snap temp directories
24
+ * @param {boolean} options.forceDebug - Whether to output debug logs
25
+ * @param {boolean} options.comprehensive - Whether to perform comprehensive cleanup of all temp locations
26
+ * @returns {Promise<Object>} Cleanup results
27
+ */
28
+ async function cleanupChromeTempFiles(options = {}) {
29
+ const {
30
+ includeSnapTemp = false,
31
+ forceDebug = false,
32
+ comprehensive = false
33
+ } = options;
34
+
35
+ try {
36
+ const { execSync } = require('child_process');
37
+
38
+ // Base cleanup commands for standard temp directories
39
+ const cleanupCommands = [
40
+ 'rm -rf /tmp/.com.google.Chrome.* 2>/dev/null || true',
41
+ 'rm -rf /tmp/.org.chromium.Chromium.* 2>/dev/null || true',
42
+ 'rm -rf /tmp/puppeteer-* 2>/dev/null || true',
43
+ 'rm -rf /dev/shm/.com.google.Chrome.* 2>/dev/null || true',
44
+ 'rm -rf /dev/shm/.org.chromium.Chromium.* 2>/dev/null || true'
45
+ ];
46
+
47
+ // Add snap-specific cleanup if requested
48
+ if (includeSnapTemp || comprehensive) {
49
+ cleanupCommands.push(
50
+ 'rm -rf /tmp/snap-private-tmp/snap.chromium/tmp/.org.chromium.Chromium.* 2>/dev/null || true',
51
+ 'rm -rf /tmp/snap-private-tmp/snap.chromium/tmp/puppeteer-* 2>/dev/null || true'
52
+ );
53
+ }
54
+
55
+ let totalCleaned = 0;
56
+
57
+ for (const command of cleanupCommands) {
58
+ try {
59
+ // Get file count before cleanup for reporting
60
+ const listCommand = command.replace('rm -rf', 'ls -1d').replace(' 2>/dev/null || true', ' 2>/dev/null | wc -l || echo 0');
61
+ const fileCount = parseInt(execSync(listCommand, { stdio: 'pipe' }).toString().trim()) || 0;
62
+
63
+ if (fileCount > 0) {
64
+ execSync(command, { stdio: 'ignore' });
65
+ totalCleaned += fileCount;
66
+
67
+ if (forceDebug) {
68
+ const pathPattern = command.match(/rm -rf ([^ ]+)/)?.[1] || 'unknown';
69
+ console.log(`[debug] [temp-cleanup] Cleaned ${fileCount} items from ${pathPattern}`);
70
+ }
71
+ }
72
+ } catch (cmdErr) {
73
+ // Ignore individual command errors but log in debug mode
74
+ if (forceDebug) {
75
+ console.log(`[debug] [temp-cleanup] Cleanup command failed: ${command} (${cmdErr.message})`);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (forceDebug) {
81
+ console.log(`[debug] [temp-cleanup] Standard cleanup completed (${totalCleaned} items)`);
82
+ }
83
+
84
+ return { success: true, itemsCleaned: totalCleaned };
85
+
86
+ } catch (cleanupErr) {
87
+ if (forceDebug) {
88
+ console.log(`[debug] [temp-cleanup] Chrome cleanup error: ${cleanupErr.message}`);
89
+ }
90
+ return { success: false, error: cleanupErr.message, itemsCleaned: 0 };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Comprehensive temp file cleanup that systematically checks all known Chrome temp locations
96
+ * @param {Object} options - Cleanup options
97
+ * @param {boolean} options.forceDebug - Whether to output debug logs
98
+ * @param {boolean} options.verbose - Whether to show verbose output
99
+ * @returns {Promise<Object>} Cleanup results
100
+ */
101
+ async function comprehensiveChromeTempCleanup(options = {}) {
102
+ const { forceDebug = false, verbose = false } = options;
103
+
104
+ try {
105
+ const { execSync } = require('child_process');
106
+ let totalCleaned = 0;
107
+
108
+ if (verbose && !forceDebug) {
109
+ console.log(`[temp-cleanup] Scanning Chrome/Puppeteer temporary files...`);
110
+ }
111
+
112
+ for (const basePath of CHROME_TEMP_PATHS) {
113
+ // Check if the base path exists before trying to clean it
114
+ try {
115
+ const pathExists = execSync(`test -d "${basePath}" && echo "exists" || echo "missing"`, { stdio: 'pipe' })
116
+ .toString().trim() === 'exists';
117
+
118
+ if (!pathExists) {
119
+ if (forceDebug) {
120
+ console.log(`[debug] [temp-cleanup] Skipping non-existent path: ${basePath}`);
121
+ }
122
+ continue;
123
+ }
124
+
125
+ for (const pattern of CHROME_TEMP_PATTERNS) {
126
+ const fullPattern = `${basePath}/${pattern}`;
127
+
128
+ // Count items before deletion
129
+ const countCommand = `ls -1d ${fullPattern} 2>/dev/null | wc -l || echo 0`;
130
+ const itemCount = parseInt(execSync(countCommand, { stdio: 'pipe' }).toString().trim()) || 0;
131
+
132
+ if (itemCount > 0) {
133
+ const deleteCommand = `rm -rf ${fullPattern} 2>/dev/null || true`;
134
+ execSync(deleteCommand, { stdio: 'ignore' });
135
+ totalCleaned += itemCount;
136
+
137
+ if (forceDebug) {
138
+ console.log(`[debug] [temp-cleanup] Removed ${itemCount} items matching ${fullPattern}`);
139
+ }
140
+ }
141
+ }
142
+ } catch (pathErr) {
143
+ if (forceDebug) {
144
+ console.log(`[debug] [temp-cleanup] Error checking path ${basePath}: ${pathErr.message}`);
145
+ }
146
+ }
147
+ }
148
+
149
+ if (verbose && totalCleaned > 0) {
150
+ console.log(`[temp-cleanup] ? Removed ${totalCleaned} temporary file(s)/folder(s)`);
151
+ } else if (verbose && totalCleaned === 0) {
152
+ console.log(`[temp-cleanup] ?? No temporary files found to remove`);
153
+ } else if (forceDebug) {
154
+ console.log(`[debug] [temp-cleanup] Comprehensive cleanup completed (${totalCleaned} items)`);
155
+ }
156
+
157
+ return { success: true, itemsCleaned: totalCleaned };
158
+
159
+ } catch (err) {
160
+ const errorMsg = `Comprehensive temp file cleanup failed: ${err.message}`;
161
+ if (verbose) {
162
+ console.warn(`[temp-cleanup] ? ${errorMsg}`);
163
+ } else if (forceDebug) {
164
+ console.log(`[debug] [temp-cleanup] ${errorMsg}`);
165
+ }
166
+ return { success: false, error: err.message, itemsCleaned: 0 };
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Cleanup specific user data directory (for browser instances)
172
+ * @param {string} userDataDir - Path to user data directory to clean
173
+ * @param {boolean} forceDebug - Whether to output debug logs
174
+ * @returns {Promise<Object>} Cleanup results
175
+ */
176
+ async function cleanupUserDataDir(userDataDir, forceDebug = false) {
177
+ if (!userDataDir) {
178
+ return { success: true, cleaned: false, reason: 'No user data directory specified' };
179
+ }
180
+
181
+ try {
182
+ const fs = require('fs');
183
+
184
+ if (!fs.existsSync(userDataDir)) {
185
+ if (forceDebug) {
186
+ console.log(`[debug] [user-data] User data directory does not exist: ${userDataDir}`);
187
+ }
188
+ return { success: true, cleaned: false, reason: 'Directory does not exist' };
189
+ }
190
+
191
+ fs.rmSync(userDataDir, { recursive: true, force: true });
192
+
193
+ if (forceDebug) {
194
+ console.log(`[debug] [user-data] Cleaned user data directory: ${userDataDir}`);
195
+ }
196
+
197
+ return { success: true, cleaned: true };
198
+
199
+ } catch (rmErr) {
200
+ if (forceDebug) {
201
+ console.log(`[debug] [user-data] Failed to remove user data directory ${userDataDir}: ${rmErr.message}`);
202
+ }
203
+ return { success: false, error: rmErr.message, cleaned: false };
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Attempts to gracefully close all browser pages and the browser instance
209
+ * @param {import('puppeteer').Browser} browser - The Puppeteer browser instance
210
+ * @param {boolean} forceDebug - Whether to output debug logs
211
+ * @returns {Promise<void>}
212
+ */
213
+ async function gracefulBrowserCleanup(browser, forceDebug = false) {
214
+ if (forceDebug) console.log(`[debug] [browser] Getting all browser pages...`);
215
+ const pages = await browser.pages();
216
+ if (forceDebug) console.log(`[debug] [browser] Found ${pages.length} pages to close`);
217
+
218
+ await Promise.all(pages.map(async (page) => {
219
+ if (!page.isClosed()) {
220
+ try {
221
+ if (forceDebug) console.log(`[debug] [browser] Closing page: ${page.url()}`);
222
+ await page.close();
223
+ if (forceDebug) console.log(`[debug] [browser] Page closed successfully`);
224
+ } catch (err) {
225
+ // Force close if normal close fails
226
+ if (forceDebug) console.log(`[debug] [browser] Force closing page: ${err.message}`);
227
+ }
228
+ }
229
+ }));
230
+
231
+ if (forceDebug) console.log(`[debug] [browser] All pages closed, closing browser...`);
232
+ await browser.close();
233
+ if (forceDebug) console.log(`[debug] [browser] Browser closed successfully`);
234
+ }
235
+
236
+ /**
237
+ * Force kills the browser process using system signals
238
+ * @param {import('puppeteer').Browser} browser - The Puppeteer browser instance
239
+ * @param {boolean} forceDebug - Whether to output debug logs
240
+ * @returns {Promise<void>}
241
+ */
242
+ async function forceBrowserKill(browser, forceDebug = false) {
243
+ try {
244
+ if (forceDebug) console.log(`[debug] [browser] Attempting force closure of browser process...`);
245
+
246
+ const browserProcess = browser.process();
247
+ if (!browserProcess || !browserProcess.pid) {
248
+ if (forceDebug) console.log(`[debug] [browser] No browser process available`);
249
+ return;
250
+ }
251
+
252
+ const mainPid = browserProcess.pid;
253
+ if (forceDebug) console.log(`[debug] [browser] Main Chrome PID: ${mainPid}`);
254
+
255
+ // Find and kill ALL related Chrome processes
256
+ const { execSync } = require('child_process');
257
+
258
+
259
+ try {
260
+ // Find all Chrome processes with puppeteer in command line
261
+ const psCmd = `ps -eo pid,cmd | grep "puppeteer.*chrome" | grep -v grep`;
262
+ const psOutput = execSync(psCmd, { encoding: 'utf8', timeout: 5000 });
263
+ const lines = psOutput.trim().split('\n').filter(line => line.trim());
264
+
265
+ const pidsToKill = [];
266
+
267
+ for (const line of lines) {
268
+ const match = line.trim().match(/^\s*(\d+)/);
269
+ if (match) {
270
+ const pid = parseInt(match[1]);
271
+ if (!isNaN(pid)) {
272
+ pidsToKill.push(pid);
273
+ }
274
+ }
275
+ }
276
+
277
+ if (forceDebug) {
278
+ console.log(`[debug] [browser] Found ${pidsToKill.length} Chrome processes to kill: [${pidsToKill.join(', ')}]`);
279
+ }
280
+
281
+ // Kill all processes with SIGTERM first (graceful)
282
+ for (const pid of pidsToKill) {
283
+ try {
284
+ process.kill(pid, 'SIGTERM');
285
+ if (forceDebug) console.log(`[debug] [browser] Sent SIGTERM to PID ${pid}`);
286
+ } catch (killErr) {
287
+ if (forceDebug) console.log(`[debug] [browser] Failed to send SIGTERM to PID ${pid}: ${killErr.message}`);
288
+ }
289
+ }
290
+
291
+ // Wait for graceful termination
292
+ await new Promise(resolve => setTimeout(resolve, 3000));
293
+
294
+ // Force kill any remaining processes with SIGKILL
295
+ for (const pid of pidsToKill) {
296
+ try {
297
+ // Check if process still exists using signal 0
298
+ process.kill(pid, 0);
299
+ // If we reach here, process still exists - force kill it
300
+ process.kill(pid, 'SIGKILL');
301
+ if (forceDebug) console.log(`[debug] [browser] Force killed PID ${pid} with SIGKILL`);
302
+ } catch (checkErr) {
303
+ // Process already dead (ESRCH error is expected and good)
304
+ if (forceDebug && checkErr.code !== 'ESRCH') {
305
+ console.log(`[debug] [browser] Error checking/killing PID ${pid}: ${checkErr.message}`);
306
+ }
307
+ }
308
+ }
309
+
310
+ // Final verification - check if any processes are still alive
311
+ if (forceDebug) {
312
+ try {
313
+ const verifyCmd = `ps -eo pid,cmd | grep "puppeteer.*chrome" | grep -v grep | wc -l`;
314
+ const remainingCount = execSync(verifyCmd, { encoding: 'utf8', timeout: 2000 }).trim();
315
+ console.log(`[debug] [browser] Remaining Chrome processes after cleanup: ${remainingCount}`);
316
+ } catch (verifyErr) {
317
+ console.log(`[debug] [browser] Could not verify process cleanup: ${verifyErr.message}`);
318
+ }
319
+ }
320
+
321
+ } catch (psErr) {
322
+ // Fallback to original method if ps command fails
323
+ if (forceDebug) console.log(`[debug] [browser] ps command failed, using fallback method: ${psErr.message}`);
324
+
325
+ try {
326
+ browserProcess.kill('SIGTERM');
327
+ await new Promise(resolve => setTimeout(resolve, 2000));
328
+
329
+ // Check if main process still exists and force kill if needed
330
+ try {
331
+ process.kill(mainPid, 0); // Check existence
332
+ browserProcess.kill('SIGKILL'); // Force kill if still alive
333
+ if (forceDebug) console.log(`[debug] [browser] Fallback: Force killed main PID ${mainPid}`);
334
+ } catch (checkErr) {
335
+ if (forceDebug && checkErr.code !== 'ESRCH') {
336
+ console.log(`[debug] [browser] Fallback check error for PID ${mainPid}: ${checkErr.message}`);
337
+ }
338
+ }
339
+ } catch (fallbackErr) {
340
+ if (forceDebug) console.log(`[debug] [browser] Fallback kill failed: ${fallbackErr.message}`);
341
+ }
342
+ }
343
+
344
+ } catch (forceKillErr) {
345
+ console.error(`[error] [browser] Failed to force kill browser: ${forceKillErr.message}`);
346
+ }
347
+
348
+ try {
349
+ if (browser.isConnected()) {
350
+ browser.disconnect();
351
+ if (forceDebug) console.log(`[debug] [browser] Browser connection disconnected`);
352
+ }
353
+ } catch (disconnectErr) {
354
+ if (forceDebug) console.log(`[debug] [browser] Failed to disconnect browser: ${disconnectErr.message}`);
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Kill all Chrome processes by command line pattern (nuclear option)
360
+ * @param {boolean} forceDebug - Whether to output debug logs
361
+ * @returns {Promise<void>}
362
+ */
363
+ async function killAllPuppeteerChrome(forceDebug = false) {
364
+ try {
365
+ const { execSync } = require('child_process');
366
+
367
+ if (forceDebug) console.log(`[debug] [browser] Nuclear option: killing all puppeteer Chrome processes...`);
368
+
369
+ try {
370
+ execSync(`pkill -f "puppeteer.*chrome"`, { stdio: 'ignore', timeout: 5000 });
371
+ if (forceDebug) console.log(`[debug] [browser] pkill completed`);
372
+ } catch (pkillErr) {
373
+ if (forceDebug && pkillErr.status !== 1) {
374
+ console.log(`[debug] [browser] pkill failed with status ${pkillErr.status}: ${pkillErr.message}`);
375
+ }
376
+ }
377
+
378
+ await new Promise(resolve => setTimeout(resolve, 2000));
379
+
380
+ } catch (nuclearErr) {
381
+ console.error(`[error] [browser] Nuclear Chrome kill failed: ${nuclearErr.message}`);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Handles comprehensive browser cleanup including processes, temp files, and user data
387
+ * @param {import('puppeteer').Browser} browser - The Puppeteer browser instance
388
+ * @param {Object} options - Cleanup options
389
+ * @param {boolean} options.forceDebug - Whether to output debug logs
390
+ * @param {number} options.timeout - Timeout in milliseconds before force closure (default: 10000)
391
+ * @param {boolean} options.exitOnFailure - Whether to exit process on cleanup failure (default: true)
392
+ * @param {boolean} options.cleanTempFiles - Whether to clean standard temp files (default: true)
393
+ * @param {boolean} options.comprehensiveCleanup - Whether to perform comprehensive temp file cleanup (default: false)
394
+ * @param {string} options.userDataDir - User data directory to clean (optional)
395
+ * @param {boolean} options.verbose - Whether to show verbose cleanup output (default: false)
396
+ * @returns {Promise<Object>} - Returns cleanup results object
397
+ */
398
+ async function handleBrowserExit(browser, options = {}) {
399
+ const {
400
+ forceDebug = false,
401
+ timeout = 10000,
402
+ exitOnFailure = true,
403
+ cleanTempFiles = true,
404
+ comprehensiveCleanup = false,
405
+ userDataDir = null,
406
+ verbose = false
407
+ } = options;
408
+
409
+ if (forceDebug) console.log(`[debug] [browser] Starting comprehensive browser cleanup...`);
410
+
411
+ const results = {
412
+ browserClosed: false,
413
+ tempFilescleaned: 0,
414
+ userDataCleaned: false,
415
+ success: false,
416
+ errors: []
417
+ };
418
+
419
+ try {
420
+ // Step 1: Browser process cleanup
421
+ try {
422
+ // Race cleanup against timeout
423
+ await Promise.race([
424
+ gracefulBrowserCleanup(browser, forceDebug),
425
+ new Promise((_, reject) =>
426
+ setTimeout(() => reject(new Error('Browser cleanup timeout')), timeout)
427
+ )
428
+ ]);
429
+
430
+ results.browserClosed = true;
431
+
432
+ } catch (browserCloseErr) {
433
+ results.errors.push(`Browser cleanup failed: ${browserCloseErr.message}`);
434
+
435
+ if (forceDebug || verbose) {
436
+ console.warn(`[warn] [browser] Browser cleanup had issues: ${browserCloseErr.message}`);
437
+ }
438
+
439
+ // Attempt force kill
440
+ await forceBrowserKill(browser, forceDebug);
441
+
442
+ // Nuclear option if force kill didn't work
443
+ if (forceDebug) console.log(`[debug] [browser] Attempting nuclear cleanup...`);
444
+ await killAllPuppeteerChrome(forceDebug);
445
+
446
+ results.browserClosed = true; // Assume success after nuclear option
447
+ }
448
+
449
+ // Step 2: User data directory cleanup
450
+ if (userDataDir) {
451
+ const userDataResult = await cleanupUserDataDir(userDataDir, forceDebug);
452
+ results.userDataCleaned = userDataResult.cleaned;
453
+ if (!userDataResult.success) {
454
+ results.errors.push(`User data cleanup failed: ${userDataResult.error}`);
455
+ }
456
+ }
457
+
458
+ // Step 3: Temp file cleanup
459
+ if (cleanTempFiles) {
460
+ if (comprehensiveCleanup) {
461
+ const tempResult = await comprehensiveChromeTempCleanup({ forceDebug, verbose });
462
+ results.tempFilesCleanedSuccess = tempResult.success;
463
+ results.tempFilesCleanedComprehensive = true;
464
+
465
+ if (tempResult.success) {
466
+ results.tempFilesCleanedCount = tempResult.itemsCleaned;
467
+ } else {
468
+ results.errors.push(`Comprehensive temp cleanup failed: ${tempResult.error}`);
469
+ }
470
+ } else {
471
+ const tempResult = await cleanupChromeTempFiles({
472
+ includeSnapTemp: true,
473
+ forceDebug,
474
+ comprehensive: false
475
+ });
476
+ results.tempFilesCleanedSuccess = tempResult.success;
477
+
478
+ if (tempResult.success) {
479
+ results.tempFilesCleanedCount = tempResult.itemsCleaned;
480
+ } else {
481
+ results.errors.push(`Standard temp cleanup failed: ${tempResult.error}`);
482
+ }
483
+ }
484
+ }
485
+
486
+ // Determine overall success
487
+ results.success = results.browserClosed &&
488
+ (results.errors.length === 0 || !exitOnFailure);
489
+
490
+ if (forceDebug) {
491
+ console.log(`[debug] [browser] Cleanup completed - Browser: ${results.browserClosed}, ` +
492
+ `Temp files: ${results.tempFilesCleanedCount || 0}, ` +
493
+ `User data: ${results.userDataCleaned}, ` +
494
+ `Errors: ${results.errors.length}`);
495
+ }
496
+
497
+ return results;
498
+
499
+ } catch (overallErr) {
500
+ results.errors.push(`Overall cleanup failed: ${overallErr.message}`);
501
+ results.success = false;
502
+
503
+ if (exitOnFailure) {
504
+ if (forceDebug) console.log(`[debug] [browser] Forcing process exit due to cleanup failure`);
505
+ process.exit(1);
506
+ }
507
+
508
+ return results;
509
+ }
510
+ }
511
+
512
+ module.exports = {
513
+ handleBrowserExit,
514
+ gracefulBrowserCleanup,
515
+ forceBrowserKill,
516
+ killAllPuppeteerChrome,
517
+ cleanupChromeTempFiles,
518
+ comprehensiveChromeTempCleanup,
519
+ cleanupUserDataDir,
520
+ CHROME_TEMP_PATHS,
521
+ CHROME_TEMP_PATTERNS
522
+ };