@fanboynz/network-scanner 2.0.1 → 2.0.3
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/browserhealth.js +9 -1
- package/lib/cloudflare.js +130 -22
- package/nwss.js +125 -18
- package/package.json +1 -1
package/lib/browserhealth.js
CHANGED
|
@@ -211,6 +211,14 @@ async function isPageSafeToClose(page, forceDebug) {
|
|
|
211
211
|
return true; // Already closed
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// EXTRA SAFETY: Never close pages that might be in injection process
|
|
215
|
+
try {
|
|
216
|
+
const url = page.url();
|
|
217
|
+
if (url && url !== 'about:blank' && Date.now() - (pageCreationTracker.get(page) || 0) < 30000) {
|
|
218
|
+
return false; // Don't close recently created pages (within 30 seconds)
|
|
219
|
+
}
|
|
220
|
+
} catch (err) { /* ignore */ }
|
|
221
|
+
|
|
214
222
|
const usage = pageUsageTracker.get(page);
|
|
215
223
|
if (!usage) {
|
|
216
224
|
// No usage data - assume safe if page exists for a while
|
|
@@ -991,4 +999,4 @@ if (originalPageClose) {
|
|
|
991
999
|
}
|
|
992
1000
|
return originalPageClose.apply(this, args);
|
|
993
1001
|
};
|
|
994
|
-
}
|
|
1002
|
+
}
|
package/lib/cloudflare.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cloudflare bypass and challenge handling module - Optimized with smart detection and adaptive timeouts
|
|
3
|
+
* Version: 2.6.0 - Memory leak fixes and timeout cleanup
|
|
4
|
+
* Version: 2.5.0 - Fix Frame Lifecycle issue, Timing and Race condition
|
|
3
5
|
* Version: 2.4.1 - Bump timeout values
|
|
4
6
|
* Version: 2.4.0 - Fix possible endless loops with retry logic and loop detection
|
|
5
7
|
* Version: 2.3.1 - Colorize CF
|
|
@@ -15,7 +17,7 @@ const { formatLogMessage } = require('./colorize');
|
|
|
15
17
|
/**
|
|
16
18
|
* Module version information
|
|
17
19
|
*/
|
|
18
|
-
const CLOUDFLARE_MODULE_VERSION = '2.
|
|
20
|
+
const CLOUDFLARE_MODULE_VERSION = '2.6.0';
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Timeout constants for various operations (in milliseconds)
|
|
@@ -67,6 +69,7 @@ const ERROR_TYPES = {
|
|
|
67
69
|
ELEMENT_NOT_FOUND: 'element_not_found',
|
|
68
70
|
EVALUATION_FAILED: 'evaluation_failed',
|
|
69
71
|
NAVIGATION_FAILED: 'navigation_failed',
|
|
72
|
+
DETACHED_FRAME: 'detached_frame',
|
|
70
73
|
UNKNOWN: 'unknown'
|
|
71
74
|
};
|
|
72
75
|
|
|
@@ -118,7 +121,7 @@ const RETRY_CONFIG = {
|
|
|
118
121
|
baseDelay: 1000,
|
|
119
122
|
maxDelay: 8000,
|
|
120
123
|
backoffMultiplier: 2,
|
|
121
|
-
retryableErrors: [ERROR_TYPES.NETWORK, ERROR_TYPES.TIMEOUT, ERROR_TYPES.ELEMENT_NOT_FOUND]
|
|
124
|
+
retryableErrors: [ERROR_TYPES.NETWORK, ERROR_TYPES.TIMEOUT, ERROR_TYPES.ELEMENT_NOT_FOUND, ERROR_TYPES.DETACHED_FRAME]
|
|
122
125
|
};
|
|
123
126
|
|
|
124
127
|
/**
|
|
@@ -131,6 +134,8 @@ class CloudflareDetectionCache {
|
|
|
131
134
|
this.ttl = ttl;
|
|
132
135
|
this.hits = 0;
|
|
133
136
|
this.misses = 0;
|
|
137
|
+
// Prevent memory buildup in long-running processes
|
|
138
|
+
this.cleanupInterval = setInterval(() => this.cleanupExpired(), ttl / 10);
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
getCacheKey(url) {
|
|
@@ -173,6 +178,20 @@ class CloudflareDetectionCache {
|
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
cleanupExpired() {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
for (const [key, value] of this.cache.entries()) {
|
|
184
|
+
if (now - value.timestamp >= this.ttl) {
|
|
185
|
+
this.cache.delete(key);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
destroy() {
|
|
191
|
+
if (this.cleanupInterval) clearInterval(this.cleanupInterval);
|
|
192
|
+
this.clear();
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
clear() {
|
|
177
196
|
this.cache.clear();
|
|
178
197
|
this.hits = 0;
|
|
@@ -260,6 +279,10 @@ async function waitForTimeout(page, timeout) {
|
|
|
260
279
|
*/
|
|
261
280
|
function categorizeError(error) {
|
|
262
281
|
const errorMessage = error.message || '';
|
|
282
|
+
|
|
283
|
+
if (errorMessage.includes('detached Frame') || errorMessage.includes('Attempted to use detached')) {
|
|
284
|
+
return ERROR_TYPES.DETACHED_FRAME;
|
|
285
|
+
}
|
|
263
286
|
|
|
264
287
|
if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
|
|
265
288
|
return ERROR_TYPES.TIMEOUT;
|
|
@@ -297,12 +320,30 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
297
320
|
|
|
298
321
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
299
322
|
try {
|
|
323
|
+
// Validate page/frame state before evaluation
|
|
324
|
+
if (page.isClosed()) {
|
|
325
|
+
throw new Error('Page is closed');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if main frame is still attached
|
|
329
|
+
const mainFrame = page.mainFrame();
|
|
330
|
+
if (mainFrame.isDetached()) {
|
|
331
|
+
throw new Error('Main frame is detached');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let timeoutId = null;
|
|
300
335
|
const result = await Promise.race([
|
|
301
336
|
page.evaluate(func),
|
|
302
|
-
new Promise((_, reject) =>
|
|
303
|
-
setTimeout(() => reject(new Error('Page evaluation timeout')), timeout)
|
|
304
|
-
)
|
|
337
|
+
new Promise((_, reject) => {
|
|
338
|
+
timeoutId = setTimeout(() => reject(new Error('Page evaluation timeout')), timeout);
|
|
339
|
+
})
|
|
305
340
|
]);
|
|
341
|
+
|
|
342
|
+
// Clear timeout if evaluation completed first
|
|
343
|
+
if (timeoutId) {
|
|
344
|
+
clearTimeout(timeoutId);
|
|
345
|
+
timeoutId = null;
|
|
346
|
+
}
|
|
306
347
|
|
|
307
348
|
if (forceDebug && attempt > 1) {
|
|
308
349
|
console.log(formatLogMessage('cloudflare', `Page evaluation succeeded on attempt ${attempt}`));
|
|
@@ -310,12 +351,28 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
310
351
|
|
|
311
352
|
return result;
|
|
312
353
|
} catch (error) {
|
|
354
|
+
// Ensure timeout is cleared on any error
|
|
355
|
+
if (timeoutId) {
|
|
356
|
+
clearTimeout(timeoutId);
|
|
357
|
+
timeoutId = null;
|
|
358
|
+
}
|
|
359
|
+
|
|
313
360
|
lastError = error;
|
|
314
361
|
const errorType = categorizeError(error);
|
|
315
362
|
|
|
316
363
|
if (forceDebug) {
|
|
317
364
|
console.warn(formatLogMessage('cloudflare', `Page evaluation failed (attempt ${attempt}/${maxRetries}): ${error.message} [${errorType}]`));
|
|
318
365
|
}
|
|
366
|
+
|
|
367
|
+
// Handle detached frame errors specifically
|
|
368
|
+
if (errorType === ERROR_TYPES.DETACHED_FRAME) {
|
|
369
|
+
if (forceDebug) {
|
|
370
|
+
console.warn(formatLogMessage('cloudflare', `Detached frame detected on attempt ${attempt}/${maxRetries} - using longer delay`));
|
|
371
|
+
}
|
|
372
|
+
// For detached frames, wait longer and don't retry as aggressively
|
|
373
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Longer delay
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
319
376
|
|
|
320
377
|
// Don't retry if error type is not retryable or if it's the last attempt
|
|
321
378
|
if (!RETRY_CONFIG.retryableErrors.includes(errorType) || attempt === maxRetries) {
|
|
@@ -336,6 +393,20 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
336
393
|
}
|
|
337
394
|
}
|
|
338
395
|
|
|
396
|
+
// If all retries failed due to detached frames, return safe defaults
|
|
397
|
+
if (lastError?.message.includes('detached Frame') || lastError?.message.includes('Attempted to use detached')) {
|
|
398
|
+
return {
|
|
399
|
+
isChallengePresent: false,
|
|
400
|
+
isPhishingWarning: false,
|
|
401
|
+
isTurnstile: false,
|
|
402
|
+
isJSChallenge: false,
|
|
403
|
+
isChallengeCompleted: false,
|
|
404
|
+
error: 'Frame detached - skipping evaluation',
|
|
405
|
+
errorType: ERROR_TYPES.DETACHED_FRAME,
|
|
406
|
+
attempts: maxRetries
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
339
410
|
return {
|
|
340
411
|
isChallengePresent: false,
|
|
341
412
|
isPhishingWarning: false,
|
|
@@ -355,9 +426,10 @@ async function safeClick(page, selector, timeout = TIMEOUTS.CLICK_TIMEOUT) {
|
|
|
355
426
|
try {
|
|
356
427
|
return await Promise.race([
|
|
357
428
|
page.click(selector, { timeout: timeout }),
|
|
358
|
-
new Promise((_, reject) =>
|
|
359
|
-
setTimeout(() => reject(new Error('Click timeout')), timeout + TIMEOUTS.CLICK_TIMEOUT_BUFFER)
|
|
360
|
-
|
|
429
|
+
new Promise((_, reject) => {
|
|
430
|
+
const timerId = setTimeout(() => reject(new Error('Click timeout')), timeout + TIMEOUTS.CLICK_TIMEOUT_BUFFER);
|
|
431
|
+
// Timer will be cleared when promise resolves/rejects
|
|
432
|
+
})
|
|
361
433
|
]);
|
|
362
434
|
} catch (error) {
|
|
363
435
|
throw new Error(`Click failed: ${error.message}`);
|
|
@@ -371,9 +443,10 @@ async function safeWaitForNavigation(page, timeout = TIMEOUTS.NAVIGATION_TIMEOUT
|
|
|
371
443
|
try {
|
|
372
444
|
return await Promise.race([
|
|
373
445
|
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: timeout }),
|
|
374
|
-
new Promise((_, reject) =>
|
|
375
|
-
setTimeout(() => reject(new Error('Navigation timeout')), timeout + TIMEOUTS.NAVIGATION_TIMEOUT_BUFFER)
|
|
376
|
-
|
|
446
|
+
new Promise((_, reject) => {
|
|
447
|
+
const timerId = setTimeout(() => reject(new Error('Navigation timeout')), timeout + TIMEOUTS.NAVIGATION_TIMEOUT_BUFFER);
|
|
448
|
+
// Timer will be cleared when promise resolves/rejects
|
|
449
|
+
})
|
|
377
450
|
]);
|
|
378
451
|
} catch (error) {
|
|
379
452
|
console.warn(formatLogMessage('cloudflare', `Navigation wait failed: ${error.message}`));
|
|
@@ -890,18 +963,30 @@ async function attemptChallengeSolveWithTimeout(page, currentUrl, challengeInfo,
|
|
|
890
963
|
const result = {
|
|
891
964
|
success: false,
|
|
892
965
|
error: null,
|
|
893
|
-
method: null
|
|
966
|
+
method: null,
|
|
967
|
+
_timeoutId: null
|
|
894
968
|
};
|
|
895
969
|
|
|
896
970
|
try {
|
|
971
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
972
|
+
result._timeoutId = setTimeout(() => reject(new Error('Challenge solving timeout')), FAST_TIMEOUTS.CHALLENGE_SOLVING);
|
|
973
|
+
});
|
|
897
974
|
// Reduced timeout for challenge solving
|
|
898
|
-
|
|
975
|
+
const finalResult = await Promise.race([
|
|
899
976
|
attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug),
|
|
900
|
-
|
|
901
|
-
setTimeout(() => reject(new Error('Challenge solving timeout')), FAST_TIMEOUTS.CHALLENGE_SOLVING)
|
|
902
|
-
)
|
|
977
|
+
timeoutPromise
|
|
903
978
|
]);
|
|
979
|
+
// Clear timeout if operation completed first
|
|
980
|
+
if (result._timeoutId) {
|
|
981
|
+
clearTimeout(result._timeoutId);
|
|
982
|
+
}
|
|
983
|
+
return finalResult;
|
|
984
|
+
|
|
904
985
|
} catch (error) {
|
|
986
|
+
// Clear timeout on error
|
|
987
|
+
if (result._timeoutId) {
|
|
988
|
+
clearTimeout(result._timeoutId);
|
|
989
|
+
}
|
|
905
990
|
result.error = `Challenge solving timed out: ${error.message}`;
|
|
906
991
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `Challenge solving timeout for ${currentUrl}`));
|
|
907
992
|
return result;
|
|
@@ -1120,11 +1205,16 @@ async function handleEmbeddedIframeChallenge(page, forceDebug = false) {
|
|
|
1120
1205
|
async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
1121
1206
|
const result = {
|
|
1122
1207
|
success: false,
|
|
1123
|
-
error: null
|
|
1208
|
+
error: null,
|
|
1209
|
+
_timeoutId: null
|
|
1124
1210
|
};
|
|
1125
1211
|
|
|
1126
1212
|
try {
|
|
1127
1213
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `Waiting for JS challenge completion`));
|
|
1214
|
+
|
|
1215
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1216
|
+
result._timeoutId = setTimeout(() => reject(new Error('JS challenge timeout')), TIMEOUTS.JS_CHALLENGE_BUFFER);
|
|
1217
|
+
});
|
|
1128
1218
|
|
|
1129
1219
|
// Reduced timeout for JS challenge completion
|
|
1130
1220
|
await Promise.race([
|
|
@@ -1137,14 +1227,22 @@ async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
|
1137
1227
|
},
|
|
1138
1228
|
{ timeout: FAST_TIMEOUTS.JS_CHALLENGE }
|
|
1139
1229
|
),
|
|
1140
|
-
|
|
1141
|
-
setTimeout(() => reject(new Error('JS challenge timeout')), TIMEOUTS.JS_CHALLENGE_BUFFER)
|
|
1142
|
-
)
|
|
1230
|
+
timeoutPromise
|
|
1143
1231
|
]);
|
|
1232
|
+
|
|
1233
|
+
// Clear timeout if completion detected first
|
|
1234
|
+
if (result._timeoutId) {
|
|
1235
|
+
clearTimeout(result._timeoutId);
|
|
1236
|
+
}
|
|
1144
1237
|
|
|
1145
1238
|
result.success = true;
|
|
1146
1239
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `JS challenge completed automatically`));
|
|
1147
1240
|
} catch (error) {
|
|
1241
|
+
// Clear timeout on error
|
|
1242
|
+
if (result._timeoutId) {
|
|
1243
|
+
clearTimeout(result._timeoutId);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1148
1246
|
result.error = `JS challenge timeout: ${error.message}`;
|
|
1149
1247
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `JS challenge wait failed: ${error.message}`));
|
|
1150
1248
|
}
|
|
@@ -1209,7 +1307,6 @@ async function handleTurnstileChallenge(page, forceDebug = false) {
|
|
|
1209
1307
|
|
|
1210
1308
|
const checkboxSelectors = [
|
|
1211
1309
|
'input[type="checkbox"].ctp-checkbox',
|
|
1212
|
-
'input[type="checkbox"]',
|
|
1213
1310
|
'.ctp-checkbox-label',
|
|
1214
1311
|
'.ctp-checkbox'
|
|
1215
1312
|
];
|
|
@@ -1712,6 +1809,15 @@ function clearDetectionCache() {
|
|
|
1712
1809
|
detectionCache.clear();
|
|
1713
1810
|
}
|
|
1714
1811
|
|
|
1812
|
+
/**
|
|
1813
|
+
* Cleanup function to prevent memory leaks in long-running processes
|
|
1814
|
+
*/
|
|
1815
|
+
function cleanup() {
|
|
1816
|
+
if (detectionCache) {
|
|
1817
|
+
detectionCache.destroy();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1715
1821
|
module.exports = {
|
|
1716
1822
|
analyzeCloudflareChallenge,
|
|
1717
1823
|
handlePhishingWarning,
|
|
@@ -1735,5 +1841,7 @@ module.exports = {
|
|
|
1735
1841
|
ERROR_TYPES,
|
|
1736
1842
|
RETRY_CONFIG,
|
|
1737
1843
|
getRetryConfig,
|
|
1738
|
-
detectChallengeLoop
|
|
1844
|
+
detectChallengeLoop,
|
|
1845
|
+
// Memory management
|
|
1846
|
+
cleanup
|
|
1739
1847
|
};
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v2.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v2.0.3 ===
|
|
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
|
|
@@ -127,7 +127,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
|
|
|
127
127
|
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage } = require('./lib/browserhealth');
|
|
128
128
|
|
|
129
129
|
// --- Script Configuration & Constants ---
|
|
130
|
-
const VERSION = '2.0.
|
|
130
|
+
const VERSION = '2.0.3'; // Script version
|
|
131
131
|
|
|
132
132
|
// get startTime
|
|
133
133
|
const startTime = Date.now();
|
|
@@ -1059,17 +1059,48 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
|
|
1061
1061
|
function setupFrameHandling(page, forceDebug) {
|
|
1062
|
+
// Track active frames and clear on navigation to prevent detached frame access
|
|
1063
|
+
let activeFrames = new Set();
|
|
1064
|
+
|
|
1065
|
+
// Clear frame tracking on navigation to prevent stale references
|
|
1066
|
+
page.on('framenavigated', (frame) => {
|
|
1067
|
+
if (frame === page.mainFrame()) {
|
|
1068
|
+
// Main frame navigated - clear all tracked frames
|
|
1069
|
+
activeFrames.clear();
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1062
1073
|
// Handle frame creation with error suppression
|
|
1063
1074
|
page.on('frameattached', async (frame) => {
|
|
1064
|
-
// Enhanced frame handling
|
|
1075
|
+
// Enhanced frame handling with detached frame protection
|
|
1065
1076
|
try {
|
|
1066
|
-
//
|
|
1067
|
-
if (frame
|
|
1077
|
+
// Multiple checks for frame validity to prevent detached frame errors
|
|
1078
|
+
if (!frame) {
|
|
1068
1079
|
if (forceDebug) {
|
|
1069
|
-
console.log(formatLogMessage('debug', `Skipping
|
|
1080
|
+
console.log(formatLogMessage('debug', `Skipping null frame`));
|
|
1070
1081
|
}
|
|
1071
1082
|
return;
|
|
1072
1083
|
}
|
|
1084
|
+
|
|
1085
|
+
// Enhanced frame validation with multiple safety checks
|
|
1086
|
+
let frameUrl;
|
|
1087
|
+
try {
|
|
1088
|
+
// Test frame accessibility first
|
|
1089
|
+
frameUrl = frame.url();
|
|
1090
|
+
|
|
1091
|
+
// Check if frame is detached (if method exists)
|
|
1092
|
+
if (frame.isDetached && frame.isDetached()) {
|
|
1093
|
+
if (forceDebug) {
|
|
1094
|
+
console.log(formatLogMessage('debug', `Skipping detached frame`));
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
} catch (frameAccessError) {
|
|
1099
|
+
// Frame is not accessible (likely detached)
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
activeFrames.add(frame);
|
|
1073
1104
|
} catch (detachError) {
|
|
1074
1105
|
// Frame state checking can throw in 23.x, handle gracefully
|
|
1075
1106
|
if (forceDebug) {
|
|
@@ -1079,9 +1110,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1079
1110
|
}
|
|
1080
1111
|
|
|
1081
1112
|
if (frame.parentFrame()) { // Only handle child frames, not main frame
|
|
1082
|
-
try {
|
|
1083
|
-
const frameUrl = frame.url();
|
|
1084
|
-
|
|
1113
|
+
try {
|
|
1085
1114
|
if (forceDebug) {
|
|
1086
1115
|
console.log(formatLogMessage('debug', `New frame attached: ${frameUrl || 'about:blank'}`));
|
|
1087
1116
|
}
|
|
@@ -1139,7 +1168,17 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1139
1168
|
});
|
|
1140
1169
|
// Handle frame navigations (keep this for monitoring)
|
|
1141
1170
|
page.on('framenavigated', (frame) => {
|
|
1142
|
-
|
|
1171
|
+
let frameUrl;
|
|
1172
|
+
|
|
1173
|
+
// Skip if frame is not in our active set
|
|
1174
|
+
if (!activeFrames.has(frame)) return;
|
|
1175
|
+
|
|
1176
|
+
try {
|
|
1177
|
+
frameUrl = frame.url();
|
|
1178
|
+
} catch (urlErr) {
|
|
1179
|
+
// Frame likely detached during navigation
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1143
1182
|
if (forceDebug &&
|
|
1144
1183
|
frameUrl &&
|
|
1145
1184
|
frameUrl !== 'about:blank' &&
|
|
@@ -1154,8 +1193,18 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1154
1193
|
|
|
1155
1194
|
// Optional: Handle frame detachment for cleanup
|
|
1156
1195
|
page.on('framedetached', (frame) => {
|
|
1196
|
+
// Remove from active tracking
|
|
1197
|
+
activeFrames.delete(frame);
|
|
1198
|
+
|
|
1199
|
+
// Skip logging if we can't access frame URL
|
|
1200
|
+
let frameUrl;
|
|
1157
1201
|
if (forceDebug) {
|
|
1158
|
-
|
|
1202
|
+
try {
|
|
1203
|
+
frameUrl = frame.url();
|
|
1204
|
+
} catch (urlErr) {
|
|
1205
|
+
// Frame already detached, can't get URL
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1159
1208
|
if (frameUrl &&
|
|
1160
1209
|
frameUrl !== 'about:blank' &&
|
|
1161
1210
|
frameUrl !== 'about:srcdoc' &&
|
|
@@ -1583,6 +1632,11 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1583
1632
|
const shouldInjectEvalForPage = siteConfig.evaluateOnNewDocument === true || globalEvalOnDoc;
|
|
1584
1633
|
let evalOnDocSuccess = false; // Track injection success for fallback logic
|
|
1585
1634
|
|
|
1635
|
+
// PREVENT realtime cleanup during injection to avoid "Session closed" errors
|
|
1636
|
+
if (shouldInjectEvalForPage && siteConfig.window_cleanup === "realtime") {
|
|
1637
|
+
updatePageUsage(page, true); // Mark page as actively processing BEFORE injection
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1586
1640
|
if (shouldInjectEvalForPage) {
|
|
1587
1641
|
if (forceDebug) {
|
|
1588
1642
|
if (globalEvalOnDoc) {
|
|
@@ -1595,8 +1649,13 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1595
1649
|
// Strategy 1: Try full injection with health check
|
|
1596
1650
|
let browserResponsive = false;
|
|
1597
1651
|
try {
|
|
1652
|
+
// Check if browser is still connected before attempting health check
|
|
1653
|
+
if (!browserInstance.isConnected()) {
|
|
1654
|
+
throw new Error('Browser not connected');
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1598
1657
|
await Promise.race([
|
|
1599
|
-
browserInstance.
|
|
1658
|
+
browserInstance.pages(), // Simple existence check that doesn't require active session
|
|
1600
1659
|
new Promise((_, reject) =>
|
|
1601
1660
|
setTimeout(() => reject(new Error('Browser health check timeout')), 3000)
|
|
1602
1661
|
)
|
|
@@ -1616,6 +1675,13 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1616
1675
|
await Promise.race([
|
|
1617
1676
|
// Main injection with all safety checks
|
|
1618
1677
|
page.evaluateOnNewDocument(() => {
|
|
1678
|
+
// Prevent duplicate injections
|
|
1679
|
+
if (window.__nwss_injection_applied) {
|
|
1680
|
+
console.log('[evalOnDoc] Already injected, skipping');
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
window.__nwss_injection_applied = true;
|
|
1684
|
+
|
|
1619
1685
|
// Wrap everything in try-catch to prevent page crashes
|
|
1620
1686
|
try {
|
|
1621
1687
|
// Add timeout check within the injection
|
|
@@ -1719,7 +1785,18 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1719
1785
|
// Strategy 3: Fallback - Try minimal injection (just fetch monitoring)
|
|
1720
1786
|
try {
|
|
1721
1787
|
await Promise.race([
|
|
1722
|
-
|
|
1788
|
+
(async () => {
|
|
1789
|
+
// Validate page state before minimal injection
|
|
1790
|
+
if (!page || page.isClosed()) {
|
|
1791
|
+
throw new Error('Page is closed');
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const pageUrl = await page.url().catch(() => 'about:blank');
|
|
1795
|
+
if (pageUrl === 'about:blank') {
|
|
1796
|
+
throw new Error('Cannot inject on about:blank');
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
return page.evaluateOnNewDocument(() => {
|
|
1723
1800
|
// Minimal injection - just fetch monitoring
|
|
1724
1801
|
if (window.fetch) {
|
|
1725
1802
|
const originalFetch = window.fetch;
|
|
@@ -1732,7 +1809,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1732
1809
|
}
|
|
1733
1810
|
};
|
|
1734
1811
|
}
|
|
1735
|
-
|
|
1812
|
+
});
|
|
1813
|
+
})(),
|
|
1736
1814
|
new Promise((_, reject) =>
|
|
1737
1815
|
setTimeout(() => reject(new Error('Minimal injection timeout')), 3000)
|
|
1738
1816
|
)
|
|
@@ -1760,6 +1838,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1760
1838
|
if (!evalOnDocSuccess) {
|
|
1761
1839
|
console.warn(formatLogMessage('warn', `[evalOnDoc] All injection strategies failed for ${currentUrl} - continuing with standard request monitoring only`));
|
|
1762
1840
|
}
|
|
1841
|
+
// Allow realtime cleanup to proceed after injection completes
|
|
1842
|
+
if (shouldInjectEvalForPage && siteConfig.window_cleanup === "realtime") {
|
|
1843
|
+
updatePageUsage(page, false); // Mark page as idle after injection
|
|
1844
|
+
}
|
|
1763
1845
|
}
|
|
1764
1846
|
// --- END: evaluateOnNewDocument for Fetch/XHR Interception ---
|
|
1765
1847
|
|
|
@@ -2161,8 +2243,15 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2161
2243
|
// This prevents redirect destinations from being marked as third-party
|
|
2162
2244
|
const isFirstParty = checkedRootDomain && firstPartyDomains.has(checkedRootDomain);
|
|
2163
2245
|
|
|
2164
|
-
// Block infinite iframe loops
|
|
2165
|
-
const frameUrl =
|
|
2246
|
+
// Block infinite iframe loops - safely access frame URL
|
|
2247
|
+
const frameUrl = (() => {
|
|
2248
|
+
try {
|
|
2249
|
+
const frame = request.frame();
|
|
2250
|
+
return frame ? frame.url() : '';
|
|
2251
|
+
} catch (err) {
|
|
2252
|
+
return '';
|
|
2253
|
+
}
|
|
2254
|
+
})();
|
|
2166
2255
|
if (frameUrl && frameUrl.includes('creative.dmzjmp.com') &&
|
|
2167
2256
|
request.url().includes('go.dmzjmp.com/api/models')) {
|
|
2168
2257
|
if (forceDebug) {
|
|
@@ -2174,8 +2263,18 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2174
2263
|
|
|
2175
2264
|
// Enhanced debug logging to show which frame the request came from
|
|
2176
2265
|
if (forceDebug) {
|
|
2177
|
-
|
|
2178
|
-
|
|
2266
|
+
let frameUrl = 'unknown-frame';
|
|
2267
|
+
let isMainFrame = false;
|
|
2268
|
+
|
|
2269
|
+
try {
|
|
2270
|
+
const frame = request.frame();
|
|
2271
|
+
if (frame) {
|
|
2272
|
+
frameUrl = frame.url();
|
|
2273
|
+
isMainFrame = frame === page.mainFrame();
|
|
2274
|
+
}
|
|
2275
|
+
} catch (frameErr) {
|
|
2276
|
+
frameUrl = 'detached-frame';
|
|
2277
|
+
}
|
|
2179
2278
|
console.log(formatLogMessage('debug', `${messageColors.highlight('[req]')}[frame: ${isMainFrame ? 'main' : 'iframe'}] ${frameUrl} → ${request.url()}`));
|
|
2180
2279
|
}
|
|
2181
2280
|
|
|
@@ -2997,6 +3096,14 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2997
3096
|
}
|
|
2998
3097
|
|
|
2999
3098
|
for (let i = 1; i <= totalReloads; i++) {
|
|
3099
|
+
// Clear any stale frame references before reload
|
|
3100
|
+
try {
|
|
3101
|
+
await page.evaluate(() => {
|
|
3102
|
+
// Force cleanup of any pending frame operations
|
|
3103
|
+
if (window.requestIdleCallback) window.requestIdleCallback(() => {});
|
|
3104
|
+
});
|
|
3105
|
+
} catch (cleanupErr) { /* ignore */ }
|
|
3106
|
+
|
|
3000
3107
|
if (siteConfig.clear_sitedata === true) {
|
|
3001
3108
|
try {
|
|
3002
3109
|
let reloadClearSession = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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": {
|