@fanboynz/network-scanner 2.0.2 → 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 +88 -20
- 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,6 @@
|
|
|
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
|
|
3
4
|
* Version: 2.5.0 - Fix Frame Lifecycle issue, Timing and Race condition
|
|
4
5
|
* Version: 2.4.1 - Bump timeout values
|
|
5
6
|
* Version: 2.4.0 - Fix possible endless loops with retry logic and loop detection
|
|
@@ -16,7 +17,7 @@ const { formatLogMessage } = require('./colorize');
|
|
|
16
17
|
/**
|
|
17
18
|
* Module version information
|
|
18
19
|
*/
|
|
19
|
-
const CLOUDFLARE_MODULE_VERSION = '2.
|
|
20
|
+
const CLOUDFLARE_MODULE_VERSION = '2.6.0';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Timeout constants for various operations (in milliseconds)
|
|
@@ -133,6 +134,8 @@ class CloudflareDetectionCache {
|
|
|
133
134
|
this.ttl = ttl;
|
|
134
135
|
this.hits = 0;
|
|
135
136
|
this.misses = 0;
|
|
137
|
+
// Prevent memory buildup in long-running processes
|
|
138
|
+
this.cleanupInterval = setInterval(() => this.cleanupExpired(), ttl / 10);
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
getCacheKey(url) {
|
|
@@ -175,6 +178,20 @@ class CloudflareDetectionCache {
|
|
|
175
178
|
}
|
|
176
179
|
}
|
|
177
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
|
+
|
|
178
195
|
clear() {
|
|
179
196
|
this.cache.clear();
|
|
180
197
|
this.hits = 0;
|
|
@@ -314,12 +331,19 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
314
331
|
throw new Error('Main frame is detached');
|
|
315
332
|
}
|
|
316
333
|
|
|
334
|
+
let timeoutId = null;
|
|
317
335
|
const result = await Promise.race([
|
|
318
336
|
page.evaluate(func),
|
|
319
|
-
new Promise((_, reject) =>
|
|
320
|
-
setTimeout(() => reject(new Error('Page evaluation timeout')), timeout)
|
|
321
|
-
)
|
|
337
|
+
new Promise((_, reject) => {
|
|
338
|
+
timeoutId = setTimeout(() => reject(new Error('Page evaluation timeout')), timeout);
|
|
339
|
+
})
|
|
322
340
|
]);
|
|
341
|
+
|
|
342
|
+
// Clear timeout if evaluation completed first
|
|
343
|
+
if (timeoutId) {
|
|
344
|
+
clearTimeout(timeoutId);
|
|
345
|
+
timeoutId = null;
|
|
346
|
+
}
|
|
323
347
|
|
|
324
348
|
if (forceDebug && attempt > 1) {
|
|
325
349
|
console.log(formatLogMessage('cloudflare', `Page evaluation succeeded on attempt ${attempt}`));
|
|
@@ -327,6 +351,12 @@ async function safePageEvaluate(page, func, timeout = TIMEOUTS.PAGE_EVALUATION_S
|
|
|
327
351
|
|
|
328
352
|
return result;
|
|
329
353
|
} catch (error) {
|
|
354
|
+
// Ensure timeout is cleared on any error
|
|
355
|
+
if (timeoutId) {
|
|
356
|
+
clearTimeout(timeoutId);
|
|
357
|
+
timeoutId = null;
|
|
358
|
+
}
|
|
359
|
+
|
|
330
360
|
lastError = error;
|
|
331
361
|
const errorType = categorizeError(error);
|
|
332
362
|
|
|
@@ -396,9 +426,10 @@ async function safeClick(page, selector, timeout = TIMEOUTS.CLICK_TIMEOUT) {
|
|
|
396
426
|
try {
|
|
397
427
|
return await Promise.race([
|
|
398
428
|
page.click(selector, { timeout: timeout }),
|
|
399
|
-
new Promise((_, reject) =>
|
|
400
|
-
setTimeout(() => reject(new Error('Click timeout')), timeout + TIMEOUTS.CLICK_TIMEOUT_BUFFER)
|
|
401
|
-
|
|
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
|
+
})
|
|
402
433
|
]);
|
|
403
434
|
} catch (error) {
|
|
404
435
|
throw new Error(`Click failed: ${error.message}`);
|
|
@@ -412,9 +443,10 @@ async function safeWaitForNavigation(page, timeout = TIMEOUTS.NAVIGATION_TIMEOUT
|
|
|
412
443
|
try {
|
|
413
444
|
return await Promise.race([
|
|
414
445
|
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: timeout }),
|
|
415
|
-
new Promise((_, reject) =>
|
|
416
|
-
setTimeout(() => reject(new Error('Navigation timeout')), timeout + TIMEOUTS.NAVIGATION_TIMEOUT_BUFFER)
|
|
417
|
-
|
|
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
|
+
})
|
|
418
450
|
]);
|
|
419
451
|
} catch (error) {
|
|
420
452
|
console.warn(formatLogMessage('cloudflare', `Navigation wait failed: ${error.message}`));
|
|
@@ -931,18 +963,30 @@ async function attemptChallengeSolveWithTimeout(page, currentUrl, challengeInfo,
|
|
|
931
963
|
const result = {
|
|
932
964
|
success: false,
|
|
933
965
|
error: null,
|
|
934
|
-
method: null
|
|
966
|
+
method: null,
|
|
967
|
+
_timeoutId: null
|
|
935
968
|
};
|
|
936
969
|
|
|
937
970
|
try {
|
|
971
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
972
|
+
result._timeoutId = setTimeout(() => reject(new Error('Challenge solving timeout')), FAST_TIMEOUTS.CHALLENGE_SOLVING);
|
|
973
|
+
});
|
|
938
974
|
// Reduced timeout for challenge solving
|
|
939
|
-
|
|
975
|
+
const finalResult = await Promise.race([
|
|
940
976
|
attemptChallengeSolve(page, currentUrl, challengeInfo, forceDebug),
|
|
941
|
-
|
|
942
|
-
setTimeout(() => reject(new Error('Challenge solving timeout')), FAST_TIMEOUTS.CHALLENGE_SOLVING)
|
|
943
|
-
)
|
|
977
|
+
timeoutPromise
|
|
944
978
|
]);
|
|
979
|
+
// Clear timeout if operation completed first
|
|
980
|
+
if (result._timeoutId) {
|
|
981
|
+
clearTimeout(result._timeoutId);
|
|
982
|
+
}
|
|
983
|
+
return finalResult;
|
|
984
|
+
|
|
945
985
|
} catch (error) {
|
|
986
|
+
// Clear timeout on error
|
|
987
|
+
if (result._timeoutId) {
|
|
988
|
+
clearTimeout(result._timeoutId);
|
|
989
|
+
}
|
|
946
990
|
result.error = `Challenge solving timed out: ${error.message}`;
|
|
947
991
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `Challenge solving timeout for ${currentUrl}`));
|
|
948
992
|
return result;
|
|
@@ -1161,11 +1205,16 @@ async function handleEmbeddedIframeChallenge(page, forceDebug = false) {
|
|
|
1161
1205
|
async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
1162
1206
|
const result = {
|
|
1163
1207
|
success: false,
|
|
1164
|
-
error: null
|
|
1208
|
+
error: null,
|
|
1209
|
+
_timeoutId: null
|
|
1165
1210
|
};
|
|
1166
1211
|
|
|
1167
1212
|
try {
|
|
1168
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
|
+
});
|
|
1169
1218
|
|
|
1170
1219
|
// Reduced timeout for JS challenge completion
|
|
1171
1220
|
await Promise.race([
|
|
@@ -1178,14 +1227,22 @@ async function waitForJSChallengeCompletion(page, forceDebug = false) {
|
|
|
1178
1227
|
},
|
|
1179
1228
|
{ timeout: FAST_TIMEOUTS.JS_CHALLENGE }
|
|
1180
1229
|
),
|
|
1181
|
-
|
|
1182
|
-
setTimeout(() => reject(new Error('JS challenge timeout')), TIMEOUTS.JS_CHALLENGE_BUFFER)
|
|
1183
|
-
)
|
|
1230
|
+
timeoutPromise
|
|
1184
1231
|
]);
|
|
1232
|
+
|
|
1233
|
+
// Clear timeout if completion detected first
|
|
1234
|
+
if (result._timeoutId) {
|
|
1235
|
+
clearTimeout(result._timeoutId);
|
|
1236
|
+
}
|
|
1185
1237
|
|
|
1186
1238
|
result.success = true;
|
|
1187
1239
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `JS challenge completed automatically`));
|
|
1188
1240
|
} catch (error) {
|
|
1241
|
+
// Clear timeout on error
|
|
1242
|
+
if (result._timeoutId) {
|
|
1243
|
+
clearTimeout(result._timeoutId);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1189
1246
|
result.error = `JS challenge timeout: ${error.message}`;
|
|
1190
1247
|
if (forceDebug) console.log(formatLogMessage('cloudflare', `JS challenge wait failed: ${error.message}`));
|
|
1191
1248
|
}
|
|
@@ -1752,6 +1809,15 @@ function clearDetectionCache() {
|
|
|
1752
1809
|
detectionCache.clear();
|
|
1753
1810
|
}
|
|
1754
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
|
+
|
|
1755
1821
|
module.exports = {
|
|
1756
1822
|
analyzeCloudflareChallenge,
|
|
1757
1823
|
handlePhishingWarning,
|
|
@@ -1775,5 +1841,7 @@ module.exports = {
|
|
|
1775
1841
|
ERROR_TYPES,
|
|
1776
1842
|
RETRY_CONFIG,
|
|
1777
1843
|
getRetryConfig,
|
|
1778
|
-
detectChallengeLoop
|
|
1844
|
+
detectChallengeLoop,
|
|
1845
|
+
// Memory management
|
|
1846
|
+
cleanup
|
|
1779
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": {
|