@fanboynz/network-scanner 2.0.30 → 2.0.31
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/lib/browserexit.js +33 -4
- package/lib/browserhealth.js +38 -13
- package/lib/fingerprint.js +54 -6
- package/lib/grep.js +44 -9
- package/lib/referrer.js +479 -0
- package/nwss.js +80 -30
- package/package.json +1 -1
package/lib/browserexit.js
CHANGED
|
@@ -211,14 +211,33 @@ async function cleanupUserDataDir(userDataDir, forceDebug = false) {
|
|
|
211
211
|
* @returns {Promise<void>}
|
|
212
212
|
*/
|
|
213
213
|
async function gracefulBrowserCleanup(browser, forceDebug = false) {
|
|
214
|
+
// FIX: Check browser connection before operations
|
|
215
|
+
if (!browser || !browser.isConnected()) {
|
|
216
|
+
if (forceDebug) console.log(`[debug] [browser] Browser not connected, skipping cleanup`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
214
219
|
if (forceDebug) console.log(`[debug] [browser] Getting all browser pages...`);
|
|
215
|
-
|
|
220
|
+
let pages;
|
|
221
|
+
try {
|
|
222
|
+
pages = await browser.pages();
|
|
223
|
+
} catch (pagesErr) {
|
|
224
|
+
if (forceDebug) console.log(`[debug] [browser] Failed to get pages: ${pagesErr.message}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
216
227
|
if (forceDebug) console.log(`[debug] [browser] Found ${pages.length} pages to close`);
|
|
217
228
|
|
|
218
229
|
await Promise.all(pages.map(async (page) => {
|
|
219
230
|
if (!page.isClosed()) {
|
|
220
231
|
try {
|
|
221
|
-
|
|
232
|
+
// FIX: Wrap page.url() in try-catch to handle race condition
|
|
233
|
+
let pageUrl = 'unknown';
|
|
234
|
+
try {
|
|
235
|
+
pageUrl = page.url();
|
|
236
|
+
} catch (urlErr) {
|
|
237
|
+
// Page closed between check and url call
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (forceDebug) console.log(`[debug] [browser] Closing page: ${pageUrl}`);
|
|
222
241
|
await page.close();
|
|
223
242
|
if (forceDebug) console.log(`[debug] [browser] Page closed successfully`);
|
|
224
243
|
} catch (err) {
|
|
@@ -229,8 +248,18 @@ async function gracefulBrowserCleanup(browser, forceDebug = false) {
|
|
|
229
248
|
}));
|
|
230
249
|
|
|
231
250
|
if (forceDebug) console.log(`[debug] [browser] All pages closed, closing browser...`);
|
|
232
|
-
|
|
233
|
-
|
|
251
|
+
|
|
252
|
+
// FIX: Check browser is still connected before closing
|
|
253
|
+
try {
|
|
254
|
+
if (browser.isConnected()) {
|
|
255
|
+
await browser.close();
|
|
256
|
+
if (forceDebug) console.log(`[debug] [browser] Browser closed successfully`);
|
|
257
|
+
} else {
|
|
258
|
+
if (forceDebug) console.log(`[debug] [browser] Browser already disconnected`);
|
|
259
|
+
}
|
|
260
|
+
} catch (closeErr) {
|
|
261
|
+
if (forceDebug) console.log(`[debug] [browser] Browser close failed: ${closeErr.message}`);
|
|
262
|
+
}
|
|
234
263
|
}
|
|
235
264
|
|
|
236
265
|
/**
|
package/lib/browserhealth.js
CHANGED
|
@@ -492,6 +492,10 @@ async function performRealtimeWindowCleanup(browserInstance, threshold = REALTIM
|
|
|
492
492
|
*/
|
|
493
493
|
async function isPageFromPreviousScan(page, forceDebug) {
|
|
494
494
|
try {
|
|
495
|
+
// FIX: Check page state first before any operations
|
|
496
|
+
if (page.isClosed()) {
|
|
497
|
+
return true; // Closed pages should be cleaned up
|
|
498
|
+
}
|
|
495
499
|
// Cache page.url() for all checks in this function
|
|
496
500
|
const pageUrl = page.url();
|
|
497
501
|
|
|
@@ -524,9 +528,13 @@ async function isPageFromPreviousScan(page, forceDebug) {
|
|
|
524
528
|
return false; // Conservative - don't close unless we're sure
|
|
525
529
|
} catch (err) {
|
|
526
530
|
if (forceDebug) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
531
|
+
try {
|
|
532
|
+
// Cache URL for error logging - wrap in try-catch as page might be closed
|
|
533
|
+
const pageUrl = page.url();
|
|
534
|
+
console.log(formatLogMessage('debug', `[isPageFromPreviousScan] Error evaluating page ${pageUrl}: ${err.message}`));
|
|
535
|
+
} catch (urlErr) {
|
|
536
|
+
console.log(formatLogMessage('debug', `[isPageFromPreviousScan] Error evaluating page: ${err.message}`));
|
|
537
|
+
}
|
|
530
538
|
}
|
|
531
539
|
return false; // Conservative - don't close if we can't evaluate
|
|
532
540
|
}
|
|
@@ -1104,16 +1112,25 @@ async function cleanupPageBeforeReload(page, forceDebug = false) {
|
|
|
1104
1112
|
// Wait a bit for navigation to stop
|
|
1105
1113
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1106
1114
|
|
|
1115
|
+
// FIX: Check if page is still open after delay before cleanup
|
|
1116
|
+
if (page.isClosed()) {
|
|
1117
|
+
if (forceDebug) {
|
|
1118
|
+
console.log(formatLogMessage('debug', 'Page closed during cleanup delay'));
|
|
1119
|
+
}
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1107
1123
|
// Now do the full cleanup
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
await page.evaluate(() => {
|
|
1126
|
+
// Stop all media elements
|
|
1127
|
+
document.querySelectorAll('video, audio').forEach(media => {
|
|
1128
|
+
try {
|
|
1129
|
+
media.pause();
|
|
1130
|
+
media.src = '';
|
|
1131
|
+
media.load();
|
|
1132
|
+
} catch(e) {}
|
|
1133
|
+
});
|
|
1117
1134
|
|
|
1118
1135
|
// Clear all timers and intervals
|
|
1119
1136
|
const highestId = setTimeout(() => {}, 0);
|
|
@@ -1144,7 +1161,15 @@ async function cleanupPageBeforeReload(page, forceDebug = false) {
|
|
|
1144
1161
|
|
|
1145
1162
|
// Force garbage collection if available
|
|
1146
1163
|
if (window.gc) window.gc();
|
|
1147
|
-
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
} catch (evalErr) {
|
|
1167
|
+
// Page closed during cleanup
|
|
1168
|
+
if (forceDebug) {
|
|
1169
|
+
console.log(formatLogMessage('debug', `Page cleanup evaluation failed: ${evalErr.message}`));
|
|
1170
|
+
}
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1148
1173
|
|
|
1149
1174
|
if (forceDebug) {
|
|
1150
1175
|
console.log(formatLogMessage('debug', 'Page resources cleaned before reload'));
|
package/lib/fingerprint.js
CHANGED
|
@@ -268,14 +268,19 @@ function generateCSIData() {
|
|
|
268
268
|
*/
|
|
269
269
|
async function validatePageForInjection(page, currentUrl, forceDebug) {
|
|
270
270
|
try {
|
|
271
|
-
if (page.isClosed()) return false;
|
|
271
|
+
if (!page || page.isClosed()) return false;
|
|
272
|
+
|
|
273
|
+
if (!page.browser().isConnected()) {
|
|
274
|
+
if (forceDebug) console.log(`[debug] Page validation failed - browser disconnected: ${currentUrl}`);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
272
277
|
await Promise.race([
|
|
273
278
|
page.evaluate(() => document.readyState || 'loading'),
|
|
274
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Page
|
|
279
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Page evaluation timeout')), 1500))
|
|
275
280
|
]);
|
|
276
281
|
return true;
|
|
277
282
|
} catch (validationErr) {
|
|
278
|
-
if (forceDebug) console.log(`[debug]
|
|
283
|
+
if (forceDebug) console.log(`[debug] Page validation failed - ${validationErr.message}: ${currentUrl}`);
|
|
279
284
|
return false;
|
|
280
285
|
}
|
|
281
286
|
}
|
|
@@ -362,7 +367,13 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
362
367
|
const ua = USER_AGENT_COLLECTIONS.get(siteConfig.userAgent.toLowerCase());
|
|
363
368
|
|
|
364
369
|
if (ua) {
|
|
365
|
-
|
|
370
|
+
// FIX: Wrap setUserAgent in try-catch to handle race condition
|
|
371
|
+
try {
|
|
372
|
+
await page.setUserAgent(ua);
|
|
373
|
+
} catch (uaErr) {
|
|
374
|
+
if (forceDebug) console.log(`[debug] Could not set user agent - page closed: ${currentUrl}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
366
377
|
|
|
367
378
|
if (forceDebug) console.log(`[debug] Applying stealth protection for ${currentUrl}`);
|
|
368
379
|
|
|
@@ -1317,6 +1328,20 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
1317
1328
|
}, 'enhanced mouse/pointer spoofing');
|
|
1318
1329
|
|
|
1319
1330
|
safeExecute(() => {
|
|
1331
|
+
// Filter DevTools/automation traces from console.debug
|
|
1332
|
+
const originalConsoleDebug = console.debug;
|
|
1333
|
+
console.debug = function(...args) {
|
|
1334
|
+
const message = args.join(' ');
|
|
1335
|
+
if (typeof message === 'string' && (
|
|
1336
|
+
message.includes('DevTools') ||
|
|
1337
|
+
message.includes('Runtime.evaluate') ||
|
|
1338
|
+
message.includes('Page.addScriptToEvaluateOnNewDocument') ||
|
|
1339
|
+
message.includes('Protocol error'))) {
|
|
1340
|
+
return; // Silently drop DevTools-related debug messages
|
|
1341
|
+
}
|
|
1342
|
+
return originalConsoleDebug.apply(this, args);
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1320
1345
|
const originalConsoleError = console.error;
|
|
1321
1346
|
console.error = function(...args) {
|
|
1322
1347
|
const message = args.join(' ');
|
|
@@ -1331,6 +1356,22 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
1331
1356
|
return originalConsoleError.apply(this, arguments);
|
|
1332
1357
|
};
|
|
1333
1358
|
}, 'console error suppression');
|
|
1359
|
+
|
|
1360
|
+
// Hide source URL indicators (data: URLs reveal script injection)
|
|
1361
|
+
safeExecute(() => {
|
|
1362
|
+
const originalLocation = window.location;
|
|
1363
|
+
Object.defineProperty(window, 'location', {
|
|
1364
|
+
value: new Proxy(originalLocation, {
|
|
1365
|
+
get: function(target, prop) {
|
|
1366
|
+
if (prop === 'href' && target[prop] && target[prop].includes('data:')) {
|
|
1367
|
+
return 'about:blank';
|
|
1368
|
+
}
|
|
1369
|
+
return target[prop];
|
|
1370
|
+
}
|
|
1371
|
+
}),
|
|
1372
|
+
configurable: false
|
|
1373
|
+
});
|
|
1374
|
+
}, 'location URL masking');
|
|
1334
1375
|
|
|
1335
1376
|
}, ua, forceDebug);
|
|
1336
1377
|
} catch (stealthErr) {
|
|
@@ -1422,8 +1463,15 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1422
1463
|
|
|
1423
1464
|
// Validate page state before injection
|
|
1424
1465
|
if (!(await validatePageForInjection(page, currentUrl, forceDebug))) return;
|
|
1425
|
-
|
|
1426
|
-
|
|
1466
|
+
|
|
1467
|
+
// FIX: Wrap page.evaluate in try-catch to handle race condition
|
|
1468
|
+
let currentUserAgent;
|
|
1469
|
+
try {
|
|
1470
|
+
currentUserAgent = await page.evaluate(() => navigator.userAgent);
|
|
1471
|
+
} catch (evalErr) {
|
|
1472
|
+
if (forceDebug) console.log(`[debug] Could not get user agent - page closed: ${currentUrl}`);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1427
1475
|
|
|
1428
1476
|
const spoof = fingerprintSetting === 'random' ? generateRealisticFingerprint(currentUserAgent) : {
|
|
1429
1477
|
deviceMemory: 8,
|
package/lib/grep.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const crypto = require('crypto');
|
|
6
7
|
const path = require('path');
|
|
7
8
|
const os = require('os');
|
|
8
9
|
const { colorize, colors, messageColors, tags, formatLogMessage } = require('./colorize');
|
|
@@ -21,23 +22,48 @@ const GREP_DEFAULTS = {
|
|
|
21
22
|
GREP_NOT_FOUND_STATUS: 1,
|
|
22
23
|
CURL_SUCCESS_STATUS: 0,
|
|
23
24
|
VERSION_LINE_INDEX: 0,
|
|
24
|
-
RANDOM_STRING_LENGTH: 9
|
|
25
|
+
RANDOM_STRING_LENGTH: 9,
|
|
26
|
+
TEMP_DIR_PREFIX: 'grep_search_'
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
* Creates a temporary file with content for grep processing
|
|
30
|
+
* Creates a temporary directory and file with content for grep processing
|
|
31
|
+
* Uses mkdtempSync to avoid race conditions from filename collisions
|
|
29
32
|
* @param {string} content - The content to write to temp file
|
|
30
33
|
* @param {string} prefix - Prefix for temp filename
|
|
31
|
-
* @returns {
|
|
34
|
+
* @returns {object} Object containing tempDir and tempFile paths
|
|
32
35
|
*/
|
|
33
36
|
function createTempFile(content, prefix = 'scanner_grep') {
|
|
34
37
|
const tempDir = os.tmpdir();
|
|
35
|
-
|
|
38
|
+
|
|
39
|
+
// Create a unique temporary directory to avoid race conditions
|
|
40
|
+
const uniqueTempDir = fs.mkdtempSync(path.join(tempDir, GREP_DEFAULTS.TEMP_DIR_PREFIX));
|
|
41
|
+
|
|
42
|
+
// Use cryptographically secure random ID for additional uniqueness
|
|
43
|
+
const uniqueId = crypto.randomBytes(8).toString('hex');
|
|
44
|
+
const tempFile = path.join(uniqueTempDir, `${prefix}_${uniqueId}.tmp`);
|
|
36
45
|
|
|
37
46
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
// Write atomically with error handling
|
|
48
|
+
fs.writeFileSync(tempFile, content, {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
mode: 0o600 // Restrict permissions for security
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return { tempDir: uniqueTempDir, tempFile };
|
|
40
54
|
} catch (error) {
|
|
55
|
+
// Clean up temp directory on write failure
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(tempFile)) {
|
|
58
|
+
fs.unlinkSync(tempFile);
|
|
59
|
+
}
|
|
60
|
+
if (fs.existsSync(uniqueTempDir)) {
|
|
61
|
+
fs.rmdirSync(uniqueTempDir);
|
|
62
|
+
}
|
|
63
|
+
} catch (cleanupErr) {
|
|
64
|
+
// Ignore cleanup errors, report original error
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
throw new Error(`Failed to create temp file: ${error.message}`);
|
|
42
68
|
}
|
|
43
69
|
}
|
|
@@ -64,8 +90,10 @@ async function grepContent(content, searchPatterns, options = {}) {
|
|
|
64
90
|
let tempFile = null;
|
|
65
91
|
|
|
66
92
|
try {
|
|
67
|
-
// Create temporary file with content
|
|
68
|
-
|
|
93
|
+
// Create temporary directory and file with content
|
|
94
|
+
const tempResult = createTempFile(content, 'grep_search');
|
|
95
|
+
tempDir = tempResult.tempDir;
|
|
96
|
+
tempFile = tempResult.tempFile;
|
|
69
97
|
|
|
70
98
|
const allMatches = [];
|
|
71
99
|
let firstMatch = null;
|
|
@@ -119,7 +147,7 @@ async function grepContent(content, searchPatterns, options = {}) {
|
|
|
119
147
|
} catch (error) {
|
|
120
148
|
throw new Error(`Grep search failed: ${error.message}`);
|
|
121
149
|
} finally {
|
|
122
|
-
// Clean up temporary file
|
|
150
|
+
// Clean up temporary file and directory
|
|
123
151
|
if (tempFile) {
|
|
124
152
|
try {
|
|
125
153
|
fs.unlinkSync(tempFile);
|
|
@@ -127,6 +155,13 @@ async function grepContent(content, searchPatterns, options = {}) {
|
|
|
127
155
|
console.warn(formatLogMessage('warn', `[grep] Failed to cleanup temp file ${tempFile}: ${cleanupErr.message}`));
|
|
128
156
|
}
|
|
129
157
|
}
|
|
158
|
+
if (tempDir) {
|
|
159
|
+
try {
|
|
160
|
+
fs.rmdirSync(tempDir);
|
|
161
|
+
} catch (cleanupErr) {
|
|
162
|
+
console.warn(formatLogMessage('warn', `[grep] Failed to cleanup temp directory ${tempDir}: ${cleanupErr.message}`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
130
165
|
}
|
|
131
166
|
}
|
|
132
167
|
|
package/lib/referrer.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// === Referrer Header Generation Module ===
|
|
2
|
+
// This module handles generation of referrer headers for different traffic simulation modes
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Referrer URL collections for different modes
|
|
6
|
+
*/
|
|
7
|
+
const REFERRER_COLLECTIONS = Object.freeze({
|
|
8
|
+
SEARCH_ENGINES: [
|
|
9
|
+
'https://www.google.com/search?q=',
|
|
10
|
+
'https://www.bing.com/search?q=',
|
|
11
|
+
'https://duckduckgo.com/?q=',
|
|
12
|
+
'https://search.yahoo.com/search?p=',
|
|
13
|
+
'https://yandex.com/search/?text=',
|
|
14
|
+
'https://www.baidu.com/s?wd=',
|
|
15
|
+
'https://www.startpage.com/sp/search?query=',
|
|
16
|
+
'https://search.brave.com/search?q='
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
SOCIAL_MEDIA: [
|
|
20
|
+
'https://www.facebook.com/',
|
|
21
|
+
'https://twitter.com/',
|
|
22
|
+
'https://www.linkedin.com/',
|
|
23
|
+
'https://www.reddit.com/',
|
|
24
|
+
'https://www.instagram.com/',
|
|
25
|
+
'https://www.pinterest.com/',
|
|
26
|
+
'https://www.tiktok.com/',
|
|
27
|
+
'https://www.youtube.com/',
|
|
28
|
+
'https://discord.com/channels/',
|
|
29
|
+
'https://t.me/',
|
|
30
|
+
'https://www.snapchat.com/',
|
|
31
|
+
'https://www.tumblr.com/',
|
|
32
|
+
'https://www.threads.net/',
|
|
33
|
+
'https://mastodon.social/'
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
NEWS_SITES: [
|
|
37
|
+
'https://news.google.com/',
|
|
38
|
+
'https://www.reddit.com/r/news/',
|
|
39
|
+
'https://news.ycombinator.com/',
|
|
40
|
+
'https://www.bbc.com/news',
|
|
41
|
+
'https://www.cnn.com/',
|
|
42
|
+
'https://techcrunch.com/',
|
|
43
|
+
'https://www.theverge.com/'
|
|
44
|
+
],
|
|
45
|
+
|
|
46
|
+
DEFAULT_SEARCH_TERMS: [
|
|
47
|
+
'reviews', 'deals', 'discount', 'price', 'buy', 'shop', 'store',
|
|
48
|
+
'compare', 'best', 'top', 'guide', 'how to', 'tutorial', 'tips',
|
|
49
|
+
'news', 'update', 'latest', 'new', 'trending', 'popular', 'cheap',
|
|
50
|
+
'free', 'download', 'online', 'service', 'product', 'website'
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
ECOMMERCE_TERMS: [
|
|
54
|
+
'buy online', 'shopping', 'store', 'sale', 'discount', 'coupon',
|
|
55
|
+
'free shipping', 'best price', 'deals', 'outlet', 'marketplace'
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
TECH_TERMS: [
|
|
59
|
+
'software', 'app', 'download', 'tutorial', 'guide', 'review',
|
|
60
|
+
'comparison', 'features', 'specs', 'performance', 'benchmark'
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generates a random search term based on context or defaults
|
|
66
|
+
* @param {Array} customTerms - Custom search terms provided by user
|
|
67
|
+
* @param {string} context - Context hint for term selection (e.g., 'ecommerce', 'tech')
|
|
68
|
+
* @returns {string} Selected search term
|
|
69
|
+
*/
|
|
70
|
+
function generateSearchTerm(customTerms, context = null) {
|
|
71
|
+
if (customTerms && customTerms.length > 0) {
|
|
72
|
+
return customTerms[Math.floor(Math.random() * customTerms.length)];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Use context-specific terms if available
|
|
76
|
+
let termCollection = REFERRER_COLLECTIONS.DEFAULT_SEARCH_TERMS;
|
|
77
|
+
if (context === 'ecommerce') {
|
|
78
|
+
termCollection = REFERRER_COLLECTIONS.ECOMMERCE_TERMS;
|
|
79
|
+
} else if (context === 'tech') {
|
|
80
|
+
termCollection = REFERRER_COLLECTIONS.TECH_TERMS;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return termCollection[Math.floor(Math.random() * termCollection.length)];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generates a search engine referrer URL
|
|
88
|
+
* @param {Array} searchTerms - Custom search terms
|
|
89
|
+
* @param {string} context - Context for term selection
|
|
90
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
91
|
+
* @returns {string} Generated search engine referrer URL
|
|
92
|
+
*/
|
|
93
|
+
function generateSearchReferrer(searchTerms, context, forceDebug) {
|
|
94
|
+
const randomEngine = REFERRER_COLLECTIONS.SEARCH_ENGINES[
|
|
95
|
+
Math.floor(Math.random() * REFERRER_COLLECTIONS.SEARCH_ENGINES.length)
|
|
96
|
+
];
|
|
97
|
+
const searchTerm = generateSearchTerm(searchTerms, context);
|
|
98
|
+
const referrerUrl = randomEngine + encodeURIComponent(searchTerm);
|
|
99
|
+
|
|
100
|
+
if (forceDebug) {
|
|
101
|
+
console.log(`[debug] Generated search referrer: ${referrerUrl} (engine: ${randomEngine.split('//')[1].split('/')[0]}, term: "${searchTerm}")`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return referrerUrl;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generates a social media referrer URL
|
|
109
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
110
|
+
* @returns {string} Generated social media referrer URL
|
|
111
|
+
*/
|
|
112
|
+
function generateSocialMediaReferrer(forceDebug) {
|
|
113
|
+
const randomSocial = REFERRER_COLLECTIONS.SOCIAL_MEDIA[
|
|
114
|
+
Math.floor(Math.random() * REFERRER_COLLECTIONS.SOCIAL_MEDIA.length)
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
if (forceDebug) {
|
|
118
|
+
console.log(`[debug] Generated social media referrer: ${randomSocial}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return randomSocial;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generates a news site referrer URL
|
|
126
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
127
|
+
* @returns {string} Generated news site referrer URL
|
|
128
|
+
*/
|
|
129
|
+
function generateNewsReferrer(forceDebug) {
|
|
130
|
+
const randomNews = REFERRER_COLLECTIONS.NEWS_SITES[
|
|
131
|
+
Math.floor(Math.random() * REFERRER_COLLECTIONS.NEWS_SITES.length)
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
if (forceDebug) {
|
|
135
|
+
console.log(`[debug] Generated news referrer: ${randomNews}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return randomNews;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validates a URL string
|
|
143
|
+
* @param {string} url - URL to validate
|
|
144
|
+
* @returns {boolean} True if valid HTTP/HTTPS URL
|
|
145
|
+
*/
|
|
146
|
+
function isValidUrl(url) {
|
|
147
|
+
return typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Checks if a URL should have its referrer disabled
|
|
152
|
+
* @param {string} targetUrl - The URL being visited
|
|
153
|
+
* @param {Array} disableList - Array of URLs/patterns that should have no referrer
|
|
154
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
155
|
+
* @returns {boolean} True if referrer should be disabled for this URL
|
|
156
|
+
*/
|
|
157
|
+
function shouldDisableReferrer(targetUrl, disableList, forceDebug = false) {
|
|
158
|
+
if (!disableList || !Array.isArray(disableList) || disableList.length === 0) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!targetUrl || typeof targetUrl !== 'string') {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const disablePattern of disableList) {
|
|
167
|
+
if (typeof disablePattern !== 'string') continue;
|
|
168
|
+
|
|
169
|
+
// Exact URL match
|
|
170
|
+
if (targetUrl === disablePattern) {
|
|
171
|
+
if (forceDebug) console.log(`[debug] Referrer disabled for exact URL match: ${targetUrl}`);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Domain/hostname match
|
|
176
|
+
try {
|
|
177
|
+
const targetHostname = new URL(targetUrl).hostname;
|
|
178
|
+
const disableHostname = new URL(disablePattern).hostname;
|
|
179
|
+
if (targetHostname === disableHostname) {
|
|
180
|
+
if (forceDebug) console.log(`[debug] Referrer disabled for domain match: ${targetHostname}`);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// If pattern is not a valid URL, try simple string matching
|
|
185
|
+
if (targetUrl.includes(disablePattern)) {
|
|
186
|
+
if (forceDebug) console.log(`[debug] Referrer disabled for pattern match: ${disablePattern} in ${targetUrl}`);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generates a referrer URL based on the specified mode and options
|
|
197
|
+
* @param {Object|string|Array} referrerConfig - Referrer configuration
|
|
198
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
199
|
+
* @returns {string} Generated referrer URL or empty string
|
|
200
|
+
*/
|
|
201
|
+
function generateReferrerUrl(referrerConfig, forceDebug = false) {
|
|
202
|
+
try {
|
|
203
|
+
// Handle simple string URLs
|
|
204
|
+
if (typeof referrerConfig === 'string') {
|
|
205
|
+
const url = isValidUrl(referrerConfig) ? referrerConfig : '';
|
|
206
|
+
if (forceDebug && url) {
|
|
207
|
+
console.log(`[debug] Using direct referrer URL: ${url}`);
|
|
208
|
+
} else if (forceDebug && !url) {
|
|
209
|
+
console.log(`[debug] Invalid referrer URL provided: ${referrerConfig}`);
|
|
210
|
+
}
|
|
211
|
+
return url;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Handle arrays - pick random URL
|
|
215
|
+
if (Array.isArray(referrerConfig)) {
|
|
216
|
+
if (referrerConfig.length === 0) {
|
|
217
|
+
if (forceDebug) console.log(`[debug] Empty referrer array provided`);
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const randomUrl = referrerConfig[Math.floor(Math.random() * referrerConfig.length)];
|
|
222
|
+
const url = isValidUrl(randomUrl) ? randomUrl : '';
|
|
223
|
+
|
|
224
|
+
if (forceDebug) {
|
|
225
|
+
console.log(`[debug] Selected referrer from array (${referrerConfig.length} options): ${url || 'invalid URL'}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return url;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Handle object modes
|
|
232
|
+
if (typeof referrerConfig === 'object' && referrerConfig !== null && referrerConfig.mode) {
|
|
233
|
+
switch (referrerConfig.mode) {
|
|
234
|
+
case 'random_search': {
|
|
235
|
+
const searchTerms = referrerConfig.search_terms;
|
|
236
|
+
const context = referrerConfig.context; // Optional context hint
|
|
237
|
+
return generateSearchReferrer(searchTerms, context, forceDebug);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'social_media': {
|
|
241
|
+
return generateSocialMediaReferrer(forceDebug);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case 'news_sites': {
|
|
245
|
+
return generateNewsReferrer(forceDebug);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'direct_navigation': {
|
|
249
|
+
if (forceDebug) console.log(`[debug] Using direct navigation (no referrer)`);
|
|
250
|
+
return '';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case 'custom': {
|
|
254
|
+
const url = isValidUrl(referrerConfig.url) ? referrerConfig.url : '';
|
|
255
|
+
if (forceDebug) {
|
|
256
|
+
console.log(`[debug] Using custom referrer URL: ${url || 'invalid URL provided'}`);
|
|
257
|
+
}
|
|
258
|
+
return url;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'mixed': {
|
|
262
|
+
// Randomly choose between different referrer types
|
|
263
|
+
const modes = ['random_search', 'social_media', 'news_sites'];
|
|
264
|
+
const randomMode = modes[Math.floor(Math.random() * modes.length)];
|
|
265
|
+
|
|
266
|
+
if (forceDebug) console.log(`[debug] Mixed mode selected: ${randomMode}`);
|
|
267
|
+
|
|
268
|
+
const mixedConfig = { mode: randomMode };
|
|
269
|
+
if (randomMode === 'random_search' && referrerConfig.search_terms) {
|
|
270
|
+
mixedConfig.search_terms = referrerConfig.search_terms;
|
|
271
|
+
mixedConfig.context = referrerConfig.context;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return generateReferrerUrl(mixedConfig, forceDebug);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
default: {
|
|
278
|
+
if (forceDebug) console.log(`[debug] Unknown referrer mode: ${referrerConfig.mode}`);
|
|
279
|
+
return '';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (forceDebug) console.log(`[debug] Invalid referrer configuration type: ${typeof referrerConfig}`);
|
|
285
|
+
return '';
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (forceDebug) console.log(`[debug] Referrer generation failed: ${err.message}`);
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Main function to determine referrer for a specific URL
|
|
294
|
+
* Handles both referrer generation and referrer_disable functionality
|
|
295
|
+
* @param {string} targetUrl - The URL being visited
|
|
296
|
+
* @param {Object|string|Array} referrerConfig - Referrer configuration
|
|
297
|
+
* @param {Array} referrerDisable - Array of URLs that should have no referrer
|
|
298
|
+
* @param {boolean} forceDebug - Debug logging flag
|
|
299
|
+
* @returns {string} Generated referrer URL or empty string if disabled/none
|
|
300
|
+
*/
|
|
301
|
+
function getReferrerForUrl(targetUrl, referrerConfig, referrerDisable, forceDebug = false) {
|
|
302
|
+
// Check if referrer should be disabled for this specific URL
|
|
303
|
+
if (shouldDisableReferrer(targetUrl, referrerDisable, forceDebug)) {
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Generate referrer normally if not disabled
|
|
308
|
+
return generateReferrerUrl(referrerConfig, forceDebug);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Validates referrer configuration
|
|
313
|
+
* @param {Object|string|Array} referrerConfig - Referrer configuration to validate
|
|
314
|
+
* @returns {Object} Validation result with isValid flag and error messages
|
|
315
|
+
*/
|
|
316
|
+
function validateReferrerConfig(referrerConfig) {
|
|
317
|
+
const result = { isValid: true, errors: [], warnings: [] };
|
|
318
|
+
|
|
319
|
+
if (!referrerConfig) {
|
|
320
|
+
result.isValid = false;
|
|
321
|
+
result.errors.push('Referrer configuration is required');
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Validate string URLs
|
|
326
|
+
if (typeof referrerConfig === 'string') {
|
|
327
|
+
if (!isValidUrl(referrerConfig)) {
|
|
328
|
+
result.isValid = false;
|
|
329
|
+
result.errors.push('String referrer must be a valid HTTP/HTTPS URL');
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Validate arrays
|
|
335
|
+
if (Array.isArray(referrerConfig)) {
|
|
336
|
+
if (referrerConfig.length === 0) {
|
|
337
|
+
result.warnings.push('Empty referrer array will result in no referrer');
|
|
338
|
+
} else {
|
|
339
|
+
referrerConfig.forEach((url, index) => {
|
|
340
|
+
if (!isValidUrl(url)) {
|
|
341
|
+
result.errors.push(`Array item ${index} is not a valid HTTP/HTTPS URL: ${url}`);
|
|
342
|
+
result.isValid = false;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Validate object modes
|
|
350
|
+
if (typeof referrerConfig === 'object') {
|
|
351
|
+
const validModes = ['random_search', 'social_media', 'news_sites', 'direct_navigation', 'custom', 'mixed'];
|
|
352
|
+
|
|
353
|
+
if (!referrerConfig.mode) {
|
|
354
|
+
result.isValid = false;
|
|
355
|
+
result.errors.push('Object referrer configuration must have a "mode" property');
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!validModes.includes(referrerConfig.mode)) {
|
|
360
|
+
result.isValid = false;
|
|
361
|
+
result.errors.push(`Invalid referrer mode: ${referrerConfig.mode}. Valid modes: ${validModes.join(', ')}`);
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Mode-specific validation
|
|
366
|
+
switch (referrerConfig.mode) {
|
|
367
|
+
case 'custom':
|
|
368
|
+
if (!referrerConfig.url) {
|
|
369
|
+
result.isValid = false;
|
|
370
|
+
result.errors.push('Custom mode requires a "url" property');
|
|
371
|
+
} else if (!isValidUrl(referrerConfig.url)) {
|
|
372
|
+
result.isValid = false;
|
|
373
|
+
result.errors.push('Custom mode URL must be a valid HTTP/HTTPS URL');
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'random_search':
|
|
378
|
+
if (referrerConfig.search_terms && !Array.isArray(referrerConfig.search_terms)) {
|
|
379
|
+
result.warnings.push('search_terms should be an array of strings');
|
|
380
|
+
}
|
|
381
|
+
if (referrerConfig.search_terms && referrerConfig.search_terms.length === 0) {
|
|
382
|
+
result.warnings.push('Empty search_terms array will use default terms');
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
result.isValid = false;
|
|
391
|
+
result.errors.push('Referrer configuration must be a string, array, or object');
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Validates referrer_disable configuration
|
|
397
|
+
* @param {Array} referrerDisable - Array of URLs/patterns to disable referrer for
|
|
398
|
+
* @returns {Object} Validation result with isValid flag and error messages
|
|
399
|
+
*/
|
|
400
|
+
function validateReferrerDisable(referrerDisable) {
|
|
401
|
+
const result = { isValid: true, errors: [], warnings: [] };
|
|
402
|
+
|
|
403
|
+
if (!referrerDisable) {
|
|
404
|
+
return result; // referrer_disable is optional
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!Array.isArray(referrerDisable)) {
|
|
408
|
+
result.isValid = false;
|
|
409
|
+
result.errors.push('referrer_disable must be an array of URLs/patterns');
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (referrerDisable.length === 0) {
|
|
414
|
+
result.warnings.push('Empty referrer_disable array has no effect');
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
referrerDisable.forEach((pattern, index) => {
|
|
419
|
+
if (typeof pattern !== 'string') {
|
|
420
|
+
result.errors.push(`referrer_disable item ${index} must be a string (got ${typeof pattern})`);
|
|
421
|
+
result.isValid = false;
|
|
422
|
+
} else if (pattern.trim() === '') {
|
|
423
|
+
result.warnings.push(`referrer_disable item ${index} is empty string`);
|
|
424
|
+
} else if (!pattern.includes('.') && !pattern.includes('/')) {
|
|
425
|
+
result.warnings.push(`referrer_disable item ${index} "${pattern}" might be too broad - consider using full URLs or hostnames`);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (referrerDisable.length > 100) {
|
|
430
|
+
result.warnings.push('Large referrer_disable list (>100 items) may impact performance');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Gets available referrer modes and their descriptions
|
|
438
|
+
* @returns {Object} Object containing mode descriptions
|
|
439
|
+
*/
|
|
440
|
+
function getReferrerModes() {
|
|
441
|
+
return {
|
|
442
|
+
'random_search': 'Generate random search engine referrers with customizable search terms',
|
|
443
|
+
'social_media': 'Use random social media platform referrers',
|
|
444
|
+
'news_sites': 'Use random news website referrers',
|
|
445
|
+
'direct_navigation': 'No referrer (simulates direct URL entry)',
|
|
446
|
+
'custom': 'Use a specific custom referrer URL',
|
|
447
|
+
'mixed': 'Randomly mix different referrer types for varied traffic simulation'
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Gets statistics about available referrer collections
|
|
453
|
+
* @returns {Object} Statistics about referrer collections
|
|
454
|
+
*/
|
|
455
|
+
function getReferrerStats() {
|
|
456
|
+
return {
|
|
457
|
+
searchEngines: REFERRER_COLLECTIONS.SEARCH_ENGINES.length,
|
|
458
|
+
socialMedia: REFERRER_COLLECTIONS.SOCIAL_MEDIA.length,
|
|
459
|
+
newsSites: REFERRER_COLLECTIONS.NEWS_SITES.length,
|
|
460
|
+
defaultSearchTerms: REFERRER_COLLECTIONS.DEFAULT_SEARCH_TERMS.length,
|
|
461
|
+
ecommerceTerms: REFERRER_COLLECTIONS.ECOMMERCE_TERMS.length,
|
|
462
|
+
techTerms: REFERRER_COLLECTIONS.TECH_TERMS.length
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
module.exports = {
|
|
467
|
+
generateReferrerUrl,
|
|
468
|
+
getReferrerForUrl,
|
|
469
|
+
shouldDisableReferrer,
|
|
470
|
+
validateReferrerConfig,
|
|
471
|
+
validateReferrerDisable,
|
|
472
|
+
getReferrerModes,
|
|
473
|
+
getReferrerStats,
|
|
474
|
+
generateSearchReferrer,
|
|
475
|
+
generateSocialMediaReferrer,
|
|
476
|
+
generateNewsReferrer,
|
|
477
|
+
isValidUrl,
|
|
478
|
+
REFERRER_COLLECTIONS
|
|
479
|
+
};
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v2.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v2.0.31 ===
|
|
2
2
|
|
|
3
3
|
// puppeteer for browser automation, fs for file system operations, psl for domain parsing.
|
|
4
4
|
// const pLimit = require('p-limit'); // Will be dynamically imported
|
|
@@ -48,6 +48,8 @@ const { clearPersistentCache } = require('./lib/smart-cache');
|
|
|
48
48
|
const { initializeDryRunCollections, addDryRunMatch, addDryRunNetTools, processDryRunResults, writeDryRunOutput } = require('./lib/dry-run');
|
|
49
49
|
// Enhanced site data clearing functionality
|
|
50
50
|
const { clearSiteData } = require('./lib/clear_sitedata');
|
|
51
|
+
// Referrer header generation
|
|
52
|
+
const { getReferrerForUrl, validateReferrerConfig, validateReferrerDisable } = require('./lib/referrer');
|
|
51
53
|
|
|
52
54
|
// Fast setTimeout helper for Puppeteer 22.x compatibility
|
|
53
55
|
// Uses standard Promise constructor for better performance than node:timers/promises
|
|
@@ -143,7 +145,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
|
|
|
143
145
|
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage, cleanupPageBeforeReload } = require('./lib/browserhealth');
|
|
144
146
|
|
|
145
147
|
// --- Script Configuration & Constants ---
|
|
146
|
-
const VERSION = '2.0.
|
|
148
|
+
const VERSION = '2.0.31'; // Script version
|
|
147
149
|
|
|
148
150
|
// get startTime
|
|
149
151
|
const startTime = Date.now();
|
|
@@ -368,9 +370,22 @@ if (validateConfig) {
|
|
|
368
370
|
// Validate referrer_headers format
|
|
369
371
|
for (const site of sites) {
|
|
370
372
|
if (site.referrer_headers && typeof site.referrer_headers === 'object' && !Array.isArray(site.referrer_headers)) {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
console.warn(`⚠ Invalid referrer_headers
|
|
373
|
+
const validation = validateReferrerConfig(site.referrer_headers);
|
|
374
|
+
if (!validation.isValid) {
|
|
375
|
+
console.warn(`⚠ Invalid referrer_headers configuration: ${validation.errors.join(', ')}`);
|
|
376
|
+
}
|
|
377
|
+
if (validation.warnings.length > 0) {
|
|
378
|
+
console.warn(`⚠ Referrer warnings: ${validation.warnings.join(', ')}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Validate referrer_disable format
|
|
382
|
+
if (site.referrer_disable) {
|
|
383
|
+
const disableValidation = validateReferrerDisable(site.referrer_disable);
|
|
384
|
+
if (!disableValidation.isValid) {
|
|
385
|
+
console.warn(`⚠ Invalid referrer_disable configuration: ${disableValidation.errors.join(', ')}`);
|
|
386
|
+
}
|
|
387
|
+
if (disableValidation.warnings.length > 0) {
|
|
388
|
+
console.warn(`⚠ Referrer disable warnings: ${disableValidation.warnings.join(', ')}`);
|
|
374
389
|
}
|
|
375
390
|
}
|
|
376
391
|
}
|
|
@@ -551,6 +566,7 @@ Redirect Handling Options:
|
|
|
551
566
|
bypass_cache: true/false Skip all caching for this site's URLs (default: false)
|
|
552
567
|
referrer_headers: "url" or ["url1", "url2"] Set referrer header for realistic traffic sources
|
|
553
568
|
custom_headers: {"Header": "value"} Add custom HTTP headers to requests
|
|
569
|
+
referrer_disable: ["url1", "url2"] Disable referrer headers for specific URLs
|
|
554
570
|
|
|
555
571
|
Cloudflare Protection Options:
|
|
556
572
|
cloudflare_phish: true/false Auto-click through Cloudflare phishing warnings (default: false)
|
|
@@ -606,6 +622,10 @@ Referrer Header Options:
|
|
|
606
622
|
referrer_headers: {"mode": "random_search", "search_terms": ["term1"]} Smart search engine traffic
|
|
607
623
|
referrer_headers: {"mode": "social_media"} Random social media referrers
|
|
608
624
|
referrer_headers: {"mode": "direct_navigation"} No referrer (direct access)
|
|
625
|
+
referrer_headers: {"mode": "news_sites"} Random news website referrers
|
|
626
|
+
referrer_headers: {"mode": "custom", "url": "https://example.com"} Custom referrer URL
|
|
627
|
+
referrer_headers: {"mode": "mixed"} Mixed referrer types for varied traffic
|
|
628
|
+
referrer_disable: ["https://example.com/no-ref", "sensitive-site.com"] Disable referrer for specific URLs
|
|
609
629
|
custom_headers: {"Header": "Value"} Additional HTTP headers
|
|
610
630
|
`);
|
|
611
631
|
process.exit(0);
|
|
@@ -1808,7 +1828,15 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1808
1828
|
throw new Error('Page is closed');
|
|
1809
1829
|
}
|
|
1810
1830
|
|
|
1811
|
-
|
|
1831
|
+
// FIX: Properly wrap page.url() in try-catch to handle race condition
|
|
1832
|
+
let pageUrl;
|
|
1833
|
+
try {
|
|
1834
|
+
pageUrl = await page.url();
|
|
1835
|
+
} catch (urlErr) {
|
|
1836
|
+
// Page closed between isClosed check and url call
|
|
1837
|
+
throw new Error('Page closed while getting URL');
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1812
1840
|
if (pageUrl === 'about:blank') {
|
|
1813
1841
|
throw new Error('Cannot inject on about:blank');
|
|
1814
1842
|
}
|
|
@@ -2848,20 +2876,23 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2848
2876
|
// --- Runtime CSS Element Blocking (Fallback) ---
|
|
2849
2877
|
// Apply CSS blocking after page load as a fallback in case evaluateOnNewDocument didn't work
|
|
2850
2878
|
if (cssBlockedSelectors && Array.isArray(cssBlockedSelectors) && cssBlockedSelectors.length > 0) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2879
|
+
// FIX: Check page state before evaluation
|
|
2880
|
+
if (page && !page.isClosed()) {
|
|
2881
|
+
try {
|
|
2882
|
+
await page.evaluate((selectors) => {
|
|
2883
|
+
const existingStyle = document.querySelector('#css-blocker-runtime');
|
|
2884
|
+
if (!existingStyle) {
|
|
2885
|
+
const style = document.createElement('style');
|
|
2886
|
+
style.id = 'css-blocker-runtime';
|
|
2887
|
+
style.type = 'text/css';
|
|
2888
|
+
const cssRules = selectors.map(selector => `${selector} { display: none !important; visibility: hidden !important; }`).join('\n');
|
|
2889
|
+
style.innerHTML = cssRules;
|
|
2890
|
+
document.head.appendChild(style);
|
|
2891
|
+
}
|
|
2892
|
+
}, cssBlockedSelectors);
|
|
2893
|
+
} catch (cssRuntimeErr) {
|
|
2894
|
+
console.warn(formatLogMessage('warn', `[css_blocked] Failed to apply runtime CSS blocking for ${currentUrl}: ${cssRuntimeErr.message}`));
|
|
2895
|
+
}
|
|
2865
2896
|
}
|
|
2866
2897
|
}
|
|
2867
2898
|
|
|
@@ -2885,11 +2916,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2885
2916
|
timeout: Math.min(timeout, TIMEOUTS.DEFAULT_PAGE), // Cap at default page timeout
|
|
2886
2917
|
// Puppeteer 23.x: Fixed referrer header handling
|
|
2887
2918
|
...(siteConfig.referrer_headers && (() => {
|
|
2888
|
-
const referrerUrl =
|
|
2889
|
-
|
|
2890
|
-
: siteConfig.referrer_headers;
|
|
2891
|
-
// Ensure referrer is a valid string URL, not an object
|
|
2892
|
-
return typeof referrerUrl === 'string' && referrerUrl.startsWith('http')
|
|
2919
|
+
const referrerUrl = getReferrerForUrl(currentUrl, siteConfig.referrer_headers, siteConfig.referrer_disable, forceDebug);
|
|
2920
|
+
return referrerUrl
|
|
2893
2921
|
? { referer: referrerUrl }
|
|
2894
2922
|
: {};
|
|
2895
2923
|
})())
|
|
@@ -3069,7 +3097,15 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3069
3097
|
}
|
|
3070
3098
|
|
|
3071
3099
|
console.log(formatLogMessage('info', `${messageColors.loaded('Loaded:')} (${siteCounter}/${totalUrls}) ${currentUrl}`));
|
|
3072
|
-
|
|
3100
|
+
|
|
3101
|
+
// FIX: Check page state before evaluation
|
|
3102
|
+
if (page && !page.isClosed()) {
|
|
3103
|
+
try {
|
|
3104
|
+
await page.evaluate(() => { console.log('Safe to evaluate on loaded page.'); });
|
|
3105
|
+
} catch (evalErr) {
|
|
3106
|
+
// Page closed during evaluation - safe to ignore
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3073
3109
|
|
|
3074
3110
|
// Mark page as processing frames
|
|
3075
3111
|
updatePageUsage(page, true);
|
|
@@ -3146,10 +3182,19 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3146
3182
|
const networkIdleTimeout = Math.min(timeout / 2, TIMEOUTS.NETWORK_IDLE_MAX); // Balanced: 10s timeout
|
|
3147
3183
|
const actualDelay = Math.min(delayMs, TIMEOUTS.NETWORK_IDLE); // Balanced: 2s delay for stability
|
|
3148
3184
|
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3185
|
+
// FIX: Check page state before waiting for network idle
|
|
3186
|
+
if (page && !page.isClosed()) {
|
|
3187
|
+
try {
|
|
3188
|
+
await page.waitForNetworkIdle({
|
|
3189
|
+
idleTime: networkIdleTime,
|
|
3190
|
+
timeout: networkIdleTimeout
|
|
3191
|
+
});
|
|
3192
|
+
} catch (networkIdleErr) {
|
|
3193
|
+
// Page closed or network idle timeout - continue anyway
|
|
3194
|
+
if (forceDebug) console.log(formatLogMessage('debug', `Network idle wait failed: ${networkIdleErr.message}`));
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3153
3198
|
// Use fast timeout helper for Puppeteer 23.x compatibility with better performance
|
|
3154
3199
|
await fastTimeout(actualDelay);
|
|
3155
3200
|
|
|
@@ -3330,6 +3375,11 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
3330
3375
|
// Fallback to standard reload if force reload failed or wasn't attempted
|
|
3331
3376
|
if (!reloadSuccess) {
|
|
3332
3377
|
try {
|
|
3378
|
+
// FIX: Check page state before reload validation
|
|
3379
|
+
if (page.isClosed()) {
|
|
3380
|
+
throw new Error('Page closed before reload check');
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3333
3383
|
const canReload = await page.evaluate(() => {
|
|
3334
3384
|
return !!(document && document.body);
|
|
3335
3385
|
}).catch(() => false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.31",
|
|
4
4
|
"description": "A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.",
|
|
5
5
|
"main": "nwss.js",
|
|
6
6
|
"scripts": {
|