@fanboynz/network-scanner 2.0.23 → 2.0.25
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/fingerprint.js +213 -70
- package/lib/searchstring.js +76 -19
- package/nwss.js +52 -27
- package/package.json +1 -1
package/lib/fingerprint.js
CHANGED
|
@@ -7,6 +7,81 @@
|
|
|
7
7
|
const DEFAULT_PLATFORM = 'Win32';
|
|
8
8
|
const DEFAULT_TIMEZONE = 'America/New_York';
|
|
9
9
|
|
|
10
|
+
// Cached property descriptors for V8 optimization
|
|
11
|
+
const CACHED_DESCRIPTORS = {
|
|
12
|
+
readOnlyValue: (value) => ({ value, writable: false, enumerable: true, configurable: true }),
|
|
13
|
+
getter: (fn) => ({ get: fn, enumerable: true, configurable: true }),
|
|
14
|
+
hiddenValue: (value) => ({ value, writable: false, enumerable: false, configurable: true })
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Type-specific property spoofing functions for monomorphic optimization
|
|
18
|
+
function spoofNavigatorProperties(navigator, properties, options = {}) {
|
|
19
|
+
if (!navigator || typeof navigator !== 'object') return false;
|
|
20
|
+
|
|
21
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
22
|
+
if (!safeDefineProperty(navigator, prop, descriptor, options)) {
|
|
23
|
+
if (options.debug) console.log(`[fingerprint] Failed to spoof navigator.${prop}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function spoofScreenProperties(screen, properties, options = {}) {
|
|
30
|
+
if (!screen || typeof screen !== 'object') return false;
|
|
31
|
+
|
|
32
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
33
|
+
if (!safeDefineProperty(screen, prop, descriptor, options)) {
|
|
34
|
+
if (options.debug) console.log(`[fingerprint] Failed to spoof screen.${prop}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function spoofWindowProperties(window, properties, options = {}) {
|
|
41
|
+
if (!window || typeof window !== 'object') return false;
|
|
42
|
+
|
|
43
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
44
|
+
if (!safeDefineProperty(window, prop, descriptor, options)) {
|
|
45
|
+
if (options.debug) console.log(`[fingerprint] Failed to spoof window.${prop}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Pre-compiled mock objects for V8 optimization
|
|
52
|
+
const PRECOMPILED_MOCKS = Object.freeze({
|
|
53
|
+
chromeRuntime: Object.freeze({
|
|
54
|
+
onConnect: Object.freeze({ addListener: () => {}, removeListener: () => {} }),
|
|
55
|
+
onMessage: Object.freeze({ addListener: () => {}, removeListener: () => {} }),
|
|
56
|
+
sendMessage: () => {},
|
|
57
|
+
connect: () => Object.freeze({
|
|
58
|
+
onMessage: Object.freeze({ addListener: () => {}, removeListener: () => {} }),
|
|
59
|
+
postMessage: () => {},
|
|
60
|
+
disconnect: () => {}
|
|
61
|
+
}),
|
|
62
|
+
getManifest: () => Object.freeze({ name: "Chrome", version: "140.0.0.0" }),
|
|
63
|
+
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
64
|
+
id: undefined
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
fingerprintResult: Object.freeze({
|
|
68
|
+
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
69
|
+
confidence: Object.freeze({ score: 0.99 }),
|
|
70
|
+
components: Object.freeze({
|
|
71
|
+
screen: Object.freeze({ value: Object.freeze({ width: 1920, height: 1080 }) }),
|
|
72
|
+
timezone: Object.freeze({ value: 'America/New_York' }),
|
|
73
|
+
language: Object.freeze({ value: 'en-US' })
|
|
74
|
+
})
|
|
75
|
+
}),
|
|
76
|
+
|
|
77
|
+
loadTimes: Object.freeze({
|
|
78
|
+
commitLoadTime: performance.now() - Math.random() * 1000,
|
|
79
|
+
connectionInfo: 'http/1.1',
|
|
80
|
+
finishDocumentLoadTime: performance.now() - Math.random() * 500,
|
|
81
|
+
navigationType: 'Navigation'
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
|
|
10
85
|
// Built-in properties that should not be modified
|
|
11
86
|
const BUILT_IN_PROPERTIES = new Set([
|
|
12
87
|
'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash',
|
|
@@ -55,10 +130,7 @@ function safeDefineProperty(target, property, descriptor, options = {}) {
|
|
|
55
130
|
return false;
|
|
56
131
|
}
|
|
57
132
|
|
|
58
|
-
Object.defineProperty(target, property,
|
|
59
|
-
...descriptor,
|
|
60
|
-
configurable: true
|
|
61
|
-
});
|
|
133
|
+
Object.defineProperty(target, property, descriptor);
|
|
62
134
|
return true;
|
|
63
135
|
} catch (err) {
|
|
64
136
|
if (options.debug) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
@@ -99,11 +171,61 @@ function getRealisticScreenResolution() {
|
|
|
99
171
|
/**
|
|
100
172
|
* Generates randomized but realistic browser fingerprint values
|
|
101
173
|
*/
|
|
102
|
-
function
|
|
103
|
-
|
|
174
|
+
function generateRealisticFingerprint(userAgent) {
|
|
175
|
+
// Determine OS from user agent
|
|
176
|
+
let osType = 'windows';
|
|
177
|
+
if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS X')) {
|
|
178
|
+
osType = 'mac';
|
|
179
|
+
} else if (userAgent.includes('X11; Linux') || userAgent.includes('Ubuntu')) {
|
|
180
|
+
osType = 'linux';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Generate OS-appropriate hardware specs
|
|
184
|
+
const profiles = {
|
|
185
|
+
windows: {
|
|
186
|
+
deviceMemory: [8, 16], // Common Windows configurations
|
|
187
|
+
hardwareConcurrency: [4, 6, 8], // Typical consumer CPUs
|
|
188
|
+
platform: 'Win32',
|
|
189
|
+
timezone: 'America/New_York',
|
|
190
|
+
language: 'en-US',
|
|
191
|
+
resolutions: [
|
|
192
|
+
{ width: 1920, height: 1080 },
|
|
193
|
+
{ width: 2560, height: 1440 },
|
|
194
|
+
{ width: 1366, height: 768 }
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
mac: {
|
|
198
|
+
deviceMemory: [8, 16], // MacBook/iMac typical configs
|
|
199
|
+
hardwareConcurrency: [8, 10], // Apple Silicon M1/M2 cores
|
|
200
|
+
platform: 'MacIntel',
|
|
201
|
+
timezone: 'America/Los_Angeles',
|
|
202
|
+
language: 'en-US',
|
|
203
|
+
resolutions: [
|
|
204
|
+
{ width: 2560, height: 1600 }, // MacBook Pro
|
|
205
|
+
{ width: 3840, height: 2160 }, // iMac 4K
|
|
206
|
+
{ width: 1440, height: 900 } // MacBook Air
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
linux: {
|
|
210
|
+
deviceMemory: [8, 16],
|
|
211
|
+
hardwareConcurrency: [4, 8, 12], // Wide variety on Linux
|
|
212
|
+
platform: 'Linux x86_64',
|
|
213
|
+
timezone: 'America/New_York',
|
|
214
|
+
language: 'en-US',
|
|
215
|
+
resolutions: [
|
|
216
|
+
{ width: 1920, height: 1080 },
|
|
217
|
+
{ width: 2560, height: 1440 },
|
|
218
|
+
{ width: 1600, height: 900 }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const profile = profiles[osType];
|
|
224
|
+
const resolution = profile.resolutions[Math.floor(Math.random() * profile.resolutions.length)];
|
|
225
|
+
|
|
104
226
|
return {
|
|
105
|
-
deviceMemory: [
|
|
106
|
-
hardwareConcurrency: [
|
|
227
|
+
deviceMemory: profile.deviceMemory[Math.floor(Math.random() * profile.deviceMemory.length)],
|
|
228
|
+
hardwareConcurrency: profile.hardwareConcurrency[Math.floor(Math.random() * profile.hardwareConcurrency.length)],
|
|
107
229
|
screen: {
|
|
108
230
|
width: resolution.width,
|
|
109
231
|
height: resolution.height,
|
|
@@ -112,11 +234,11 @@ function getRandomFingerprint() {
|
|
|
112
234
|
colorDepth: 24,
|
|
113
235
|
pixelDepth: 24
|
|
114
236
|
},
|
|
115
|
-
platform:
|
|
116
|
-
timezone:
|
|
117
|
-
language:
|
|
237
|
+
platform: profile.platform,
|
|
238
|
+
timezone: profile.timezone,
|
|
239
|
+
language: profile.language,
|
|
118
240
|
cookieEnabled: true,
|
|
119
|
-
doNotTrack:
|
|
241
|
+
doNotTrack: null // Most users don't enable DNT
|
|
120
242
|
};
|
|
121
243
|
}
|
|
122
244
|
|
|
@@ -124,19 +246,7 @@ function getRandomFingerprint() {
|
|
|
124
246
|
* Creates mock Chrome runtime objects
|
|
125
247
|
*/
|
|
126
248
|
function createMockChromeRuntime() {
|
|
127
|
-
return
|
|
128
|
-
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
129
|
-
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
130
|
-
sendMessage: () => {},
|
|
131
|
-
connect: () => ({
|
|
132
|
-
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
133
|
-
postMessage: () => {},
|
|
134
|
-
disconnect: () => {}
|
|
135
|
-
}),
|
|
136
|
-
getManifest: () => ({ name: "Chrome", version: "131.0.0.0" }),
|
|
137
|
-
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
138
|
-
id: undefined
|
|
139
|
-
};
|
|
249
|
+
return PRECOMPILED_MOCKS.chromeRuntime;
|
|
140
250
|
}
|
|
141
251
|
|
|
142
252
|
/**
|
|
@@ -144,21 +254,8 @@ function createMockChromeRuntime() {
|
|
|
144
254
|
*/
|
|
145
255
|
function generateRealisticLoadTimes() {
|
|
146
256
|
const now = performance.now();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
connectionInfo: 'http/1.1',
|
|
150
|
-
finishDocumentLoadTime: now - Math.random() * 500,
|
|
151
|
-
finishLoadTime: now - Math.random() * 100,
|
|
152
|
-
firstPaintAfterLoadTime: now - Math.random() * 50,
|
|
153
|
-
firstPaintTime: now - Math.random() * 200,
|
|
154
|
-
navigationType: 'Navigation',
|
|
155
|
-
npnNegotiatedProtocol: 'unknown',
|
|
156
|
-
requestTime: now - Math.random() * 2000,
|
|
157
|
-
startLoadTime: now - Math.random() * 1500,
|
|
158
|
-
wasAlternateProtocolAvailable: false,
|
|
159
|
-
wasFetchedViaSpdy: false,
|
|
160
|
-
wasNpnNegotiated: false
|
|
161
|
-
};
|
|
257
|
+
// Return a copy with updated timing values
|
|
258
|
+
return { ...PRECOMPILED_MOCKS.loadTimes, commitLoadTime: now - Math.random() * 1000 };
|
|
162
259
|
}
|
|
163
260
|
|
|
164
261
|
/**
|
|
@@ -194,22 +291,14 @@ async function validatePageForInjection(page, currentUrl, forceDebug) {
|
|
|
194
291
|
* Creates mock fingerprinting objects
|
|
195
292
|
*/
|
|
196
293
|
function createFingerprintMocks() {
|
|
197
|
-
const mockResult =
|
|
198
|
-
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
199
|
-
confidence: { score: 0.99 },
|
|
200
|
-
components: {
|
|
201
|
-
screen: { value: { width: 1920, height: 1080 } },
|
|
202
|
-
timezone: { value: DEFAULT_TIMEZONE },
|
|
203
|
-
language: { value: 'en-US' }
|
|
204
|
-
}
|
|
205
|
-
};
|
|
294
|
+
const mockResult = PRECOMPILED_MOCKS.fingerprintResult;
|
|
206
295
|
|
|
207
296
|
return {
|
|
208
297
|
fp: {
|
|
209
298
|
getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult,
|
|
210
299
|
get: (callback) => Promise.resolve(mockResult),
|
|
211
300
|
load: () => Promise.resolve(window.fp),
|
|
212
|
-
components:
|
|
301
|
+
components: mockResult.components,
|
|
213
302
|
x64hash128: () => 'mock_hash',
|
|
214
303
|
tz: DEFAULT_TIMEZONE,
|
|
215
304
|
timezone: DEFAULT_TIMEZONE
|
|
@@ -351,7 +440,7 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
351
440
|
// Create safe property definition helper
|
|
352
441
|
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
353
442
|
const builtInProps = new Set(['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace']);
|
|
354
|
-
|
|
443
|
+
|
|
355
444
|
if (builtInProps.has(property)) {
|
|
356
445
|
if (debugEnabled) console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
357
446
|
return false;
|
|
@@ -374,6 +463,24 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
374
463
|
return false;
|
|
375
464
|
}
|
|
376
465
|
}
|
|
466
|
+
|
|
467
|
+
// Add cached descriptors helper for page context
|
|
468
|
+
const CACHED_DESCRIPTORS = {
|
|
469
|
+
getter: (fn) => ({ get: fn, enumerable: true, configurable: true })
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Add monomorphic spoofing functions for page context
|
|
473
|
+
function spoofNavigatorProperties(navigator, properties) {
|
|
474
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
475
|
+
safeDefinePropertyLocal(navigator, prop, descriptor);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function spoofScreenProperties(screen, properties) {
|
|
480
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
481
|
+
safeDefinePropertyLocal(screen, prop, descriptor);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
377
484
|
|
|
378
485
|
// Safe execution wrapper
|
|
379
486
|
function safeExecute(fn, description) {
|
|
@@ -428,7 +535,7 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
428
535
|
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
429
536
|
sendMessage: () => {},
|
|
430
537
|
connect: () => ({ onMessage: { addListener: () => {}, removeListener: () => {} }, postMessage: () => {}, disconnect: () => {} }),
|
|
431
|
-
getManifest: () => ({ name: "Chrome", version: "
|
|
538
|
+
getManifest: () => ({ name: "Chrome", version: "140.0.0.0" }),
|
|
432
539
|
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
433
540
|
id: undefined
|
|
434
541
|
},
|
|
@@ -507,8 +614,11 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
507
614
|
//
|
|
508
615
|
safeExecute(() => {
|
|
509
616
|
const languages = ['en-US', 'en'];
|
|
510
|
-
|
|
511
|
-
|
|
617
|
+
const languageProps = {
|
|
618
|
+
languages: { get: () => languages },
|
|
619
|
+
language: { get: () => languages[0] }
|
|
620
|
+
};
|
|
621
|
+
spoofNavigatorProperties(navigator, languageProps);
|
|
512
622
|
}, 'language spoofing');
|
|
513
623
|
|
|
514
624
|
// Spoof vendor information
|
|
@@ -523,8 +633,11 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
523
633
|
vendor = 'Apple Computer, Inc.';
|
|
524
634
|
}
|
|
525
635
|
|
|
526
|
-
|
|
527
|
-
|
|
636
|
+
const vendorProps = {
|
|
637
|
+
vendor: { get: () => vendor },
|
|
638
|
+
product: { get: () => product }
|
|
639
|
+
};
|
|
640
|
+
spoofNavigatorProperties(navigator, vendorProps);
|
|
528
641
|
}, 'vendor/product spoofing');
|
|
529
642
|
|
|
530
643
|
// Enhanced OS fingerprinting protection based on actual user agent content
|
|
@@ -574,7 +687,10 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
574
687
|
// Hardware concurrency spoofing (universal coverage)
|
|
575
688
|
//
|
|
576
689
|
safeExecute(() => {
|
|
577
|
-
|
|
690
|
+
const hardwareProps = {
|
|
691
|
+
hardwareConcurrency: { get: () => [4, 6, 8, 12][Math.floor(Math.random() * 4)] }
|
|
692
|
+
};
|
|
693
|
+
spoofNavigatorProperties(navigator, hardwareProps);
|
|
578
694
|
}, 'hardware concurrency spoofing');
|
|
579
695
|
|
|
580
696
|
|
|
@@ -593,10 +709,13 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
593
709
|
];
|
|
594
710
|
const resolution = commonResolutions[Math.floor(Math.random() * commonResolutions.length)];
|
|
595
711
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
712
|
+
const screenProps = {
|
|
713
|
+
width: { get: () => resolution.width },
|
|
714
|
+
height: { get: () => resolution.height },
|
|
715
|
+
availWidth: { get: () => resolution.width },
|
|
716
|
+
availHeight: { get: () => resolution.height - 40 }
|
|
717
|
+
};
|
|
718
|
+
spoofScreenProperties(window.screen, screenProps);
|
|
600
719
|
}, 'screen resolution protection');
|
|
601
720
|
|
|
602
721
|
// Spoof MIME types
|
|
@@ -1213,7 +1332,9 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1213
1332
|
// Validate page state before injection
|
|
1214
1333
|
if (!(await validatePageForInjection(page, currentUrl, forceDebug))) return;
|
|
1215
1334
|
|
|
1216
|
-
|
|
1335
|
+
const currentUserAgent = await page.evaluate(() => navigator.userAgent);
|
|
1336
|
+
|
|
1337
|
+
const spoof = fingerprintSetting === 'random' ? generateRealisticFingerprint(currentUserAgent) : {
|
|
1217
1338
|
deviceMemory: 8,
|
|
1218
1339
|
hardwareConcurrency: 4,
|
|
1219
1340
|
screen: { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040, colorDepth: 24, pixelDepth: 24 },
|
|
@@ -1226,6 +1347,19 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1226
1347
|
|
|
1227
1348
|
try {
|
|
1228
1349
|
await page.evaluateOnNewDocument(({ spoof, debugEnabled }) => {
|
|
1350
|
+
|
|
1351
|
+
// Define helper functions FIRST in this context
|
|
1352
|
+
function spoofNavigatorProperties(navigator, properties) {
|
|
1353
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
1354
|
+
safeDefinePropertyLocal(navigator, prop, descriptor);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function spoofScreenProperties(screen, properties) {
|
|
1359
|
+
for (const [prop, descriptor] of Object.entries(properties)) {
|
|
1360
|
+
safeDefinePropertyLocal(screen, prop, descriptor);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1229
1363
|
|
|
1230
1364
|
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
1231
1365
|
try {
|
|
@@ -1244,11 +1378,15 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1244
1378
|
}
|
|
1245
1379
|
|
|
1246
1380
|
// Platform spoofing
|
|
1247
|
-
|
|
1381
|
+
const navigatorProps = {
|
|
1382
|
+
platform: { get: () => spoof.platform },
|
|
1383
|
+
deviceMemory: { get: () => spoof.deviceMemory },
|
|
1384
|
+
hardwareConcurrency: { get: () => spoof.hardwareConcurrency }
|
|
1385
|
+
};
|
|
1386
|
+
spoofNavigatorProperties(navigator, navigatorProps);
|
|
1248
1387
|
|
|
1249
|
-
//
|
|
1250
|
-
|
|
1251
|
-
safeDefinePropertyLocal(navigator, 'hardwareConcurrency', { get: () => spoof.hardwareConcurrency });
|
|
1388
|
+
// Platform, memory, and hardware spoofing combined for better V8 optimization
|
|
1389
|
+
// (moved into navigatorProps above);
|
|
1252
1390
|
|
|
1253
1391
|
// Connection type spoofing
|
|
1254
1392
|
safeDefinePropertyLocal(navigator, 'connection', {
|
|
@@ -1262,16 +1400,21 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1262
1400
|
});
|
|
1263
1401
|
|
|
1264
1402
|
// Screen properties spoofing
|
|
1403
|
+
const screenSpoofProps = {};
|
|
1265
1404
|
['width', 'height', 'availWidth', 'availHeight', 'colorDepth', 'pixelDepth'].forEach(prop => {
|
|
1266
1405
|
if (spoof.screen[prop] !== undefined) {
|
|
1267
|
-
|
|
1406
|
+
screenSpoofProps[prop] = { get: () => spoof.screen[prop] };
|
|
1268
1407
|
}
|
|
1269
1408
|
});
|
|
1409
|
+
spoofScreenProperties(window.screen, screenSpoofProps);
|
|
1270
1410
|
|
|
1271
1411
|
// Language spoofing
|
|
1272
1412
|
const languages = Array.isArray(spoof.language) ? spoof.language : [spoof.language, spoof.language.split('-')[0]];
|
|
1273
|
-
|
|
1274
|
-
|
|
1413
|
+
const languageSpoofProps = {
|
|
1414
|
+
languages: { get: () => languages },
|
|
1415
|
+
language: { get: () => languages[0] }
|
|
1416
|
+
};
|
|
1417
|
+
spoofNavigatorProperties(navigator, languageSpoofProps);
|
|
1275
1418
|
|
|
1276
1419
|
// Timezone spoofing
|
|
1277
1420
|
if (spoof.timezone && window.Intl?.DateTimeFormat) {
|
|
@@ -1480,7 +1623,7 @@ function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
|
|
|
1480
1623
|
|
|
1481
1624
|
|
|
1482
1625
|
module.exports = {
|
|
1483
|
-
|
|
1626
|
+
generateRealisticFingerprint,
|
|
1484
1627
|
getRealisticScreenResolution,
|
|
1485
1628
|
applyUserAgentSpoofing,
|
|
1486
1629
|
applyBraveSpoofing,
|
package/lib/searchstring.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const { grepContent } = require('./grep');
|
|
6
7
|
|
|
7
8
|
// Configuration constants for search logic
|
|
8
9
|
const SEARCH_CONFIG = {
|
|
@@ -51,11 +52,12 @@ function parseSearchStrings(searchstring, searchstringAnd) {
|
|
|
51
52
|
* @param {Function} addMatchedDomain - Optional helper function for adding domains
|
|
52
53
|
* @param {string} domain - Domain to add
|
|
53
54
|
* @param {string} resourceType - Resource type (for --adblock-rules mode)
|
|
55
|
+
* @param {string} fullSubdomain - Full subdomain for cache tracking (optional)
|
|
54
56
|
*/
|
|
55
|
-
function addDomainToCollection(matchedDomains, addMatchedDomain, domain, resourceType = null) {
|
|
57
|
+
function addDomainToCollection(matchedDomains, addMatchedDomain, domain, resourceType = null, fullSubdomain = null) {
|
|
56
58
|
// Use helper function if provided (preferred method)
|
|
57
59
|
if (typeof addMatchedDomain === 'function') {
|
|
58
|
-
addMatchedDomain(domain, resourceType);
|
|
60
|
+
addMatchedDomain(domain, resourceType, fullSubdomain);
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -575,6 +577,7 @@ function createResponseHandler(config) {
|
|
|
575
577
|
siteConfig,
|
|
576
578
|
dumpUrls,
|
|
577
579
|
matchedUrlsLogFile,
|
|
580
|
+
useGrep = false,
|
|
578
581
|
forceDebug,
|
|
579
582
|
resourceType // Will be null for response handler
|
|
580
583
|
} = config;
|
|
@@ -584,22 +587,16 @@ function createResponseHandler(config) {
|
|
|
584
587
|
const respDomain = perSiteSubDomains ? (new URL(respUrl)).hostname : getRootDomain(respUrl);
|
|
585
588
|
|
|
586
589
|
// Only process responses that match our regex patterns
|
|
587
|
-
const
|
|
588
|
-
if (!matchesRegex) return;
|
|
590
|
+
const fullSubdomain = (new URL(respUrl)).hostname; // Always get full subdomain for cache tracking
|
|
589
591
|
|
|
590
|
-
//
|
|
591
|
-
if (typeof config.isDomainAlreadyDetected === 'function' && config.isDomainAlreadyDetected(
|
|
592
|
-
if (forceDebug) {
|
|
593
|
-
console.log(`[debug] Skipping response analysis for already detected domain: ${respDomain}`);
|
|
594
|
-
}
|
|
592
|
+
// Skip if already detected to avoid duplicates
|
|
593
|
+
if (typeof config.isDomainAlreadyDetected === 'function' && config.isDomainAlreadyDetected(fullSubdomain)) {
|
|
595
594
|
return;
|
|
596
595
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const currentUrlHostname = new URL(currentUrl).hostname;
|
|
600
|
-
const responseHostname = new URL(respUrl).hostname;
|
|
601
|
-
const isFirstParty = currentUrlHostname === responseHostname;
|
|
596
|
+
const matchesRegex = regexes.some(re => re.test(respUrl));
|
|
597
|
+
if (!matchesRegex) return;
|
|
602
598
|
|
|
599
|
+
// Extract domain and check if already detected (skip expensive operations)
|
|
603
600
|
// The main request handler already filtered first-party/third-party requests
|
|
604
601
|
// This response handler only runs for requests that passed that filter
|
|
605
602
|
// However, we need to apply the same first-party/third-party logic here for searchstring analysis
|
|
@@ -607,6 +604,10 @@ function createResponseHandler(config) {
|
|
|
607
604
|
|
|
608
605
|
// Apply first-party/third-party filtering for searchstring analysis
|
|
609
606
|
// Use the exact same logic as the main request handler
|
|
607
|
+
|
|
608
|
+
const currentUrlHostname = new URL(currentUrl).hostname;
|
|
609
|
+
const responseHostname = new URL(respUrl).hostname;
|
|
610
|
+
const isFirstParty = currentUrlHostname === responseHostname;
|
|
610
611
|
if (isFirstParty && siteConfig.firstParty === false) {
|
|
611
612
|
if (forceDebug) {
|
|
612
613
|
console.log(`[debug] Skipping first-party response for searchstring analysis (firstParty=false): ${respUrl}`);
|
|
@@ -632,9 +633,61 @@ function createResponseHandler(config) {
|
|
|
632
633
|
}
|
|
633
634
|
|
|
634
635
|
const content = await response.text();
|
|
636
|
+
|
|
637
|
+
// Cache the fetched content if callback provided
|
|
638
|
+
if (config.onContentFetched) {
|
|
639
|
+
try {
|
|
640
|
+
config.onContentFetched(respUrl, content);
|
|
641
|
+
} catch (cacheErr) {
|
|
642
|
+
if (forceDebug) {
|
|
643
|
+
console.log(`[debug] Content caching failed: ${cacheErr.message}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
635
647
|
|
|
636
648
|
// Check if content contains search strings (OR or AND logic)
|
|
637
|
-
|
|
649
|
+
let searchResult;
|
|
650
|
+
|
|
651
|
+
if (useGrep && (searchStrings.length > 0 || searchStringsAnd.length > 0)) {
|
|
652
|
+
// Use grep for pattern matching
|
|
653
|
+
try {
|
|
654
|
+
const allPatterns = [...(searchStrings || []), ...(searchStringsAnd || [])];
|
|
655
|
+
const grepResult = await grepContent(content, allPatterns, {
|
|
656
|
+
ignoreCase: true,
|
|
657
|
+
wholeWord: false,
|
|
658
|
+
regex: false
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (hasSearchStringAnd && searchStringsAnd.length > 0) {
|
|
662
|
+
// For AND logic, check that all patterns were found
|
|
663
|
+
const foundPatterns = grepResult.allMatches.map(match => match.pattern);
|
|
664
|
+
const allFound = searchStringsAnd.every(pattern => foundPatterns.includes(pattern));
|
|
665
|
+
searchResult = {
|
|
666
|
+
found: allFound,
|
|
667
|
+
matchedString: allFound ? foundPatterns.join(' AND ') : null,
|
|
668
|
+
logicType: 'AND'
|
|
669
|
+
};
|
|
670
|
+
} else {
|
|
671
|
+
// For OR logic, any match is sufficient
|
|
672
|
+
searchResult = {
|
|
673
|
+
found: grepResult.found,
|
|
674
|
+
matchedString: grepResult.matchedPattern,
|
|
675
|
+
logicType: 'OR'
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
} catch (grepErr) {
|
|
679
|
+
if (forceDebug) {
|
|
680
|
+
console.log(`[debug] Grep failed for ${respUrl}, falling back to JavaScript: ${grepErr.message}`);
|
|
681
|
+
}
|
|
682
|
+
// Fallback to JavaScript search
|
|
683
|
+
searchResult = searchContent(content, searchStrings, searchStringsAnd, contentType, respUrl);
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
// Use JavaScript search
|
|
687
|
+
searchResult = searchContent(content, searchStrings, searchStringsAnd, contentType, respUrl);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const { found, matchedString, logicType, error } = searchResult;
|
|
638
691
|
|
|
639
692
|
if (found) {
|
|
640
693
|
if (!respDomain || matchesIgnoreDomain(respDomain, ignoreDomains)) {
|
|
@@ -642,27 +695,31 @@ function createResponseHandler(config) {
|
|
|
642
695
|
}
|
|
643
696
|
|
|
644
697
|
// Response handler doesn't have access to specific resource type
|
|
645
|
-
|
|
698
|
+
// Use the addMatchedDomain helper which handles fullSubdomain properly
|
|
699
|
+
addMatchedDomain(respDomain, null, fullSubdomain);
|
|
646
700
|
const simplifiedUrl = getRootDomain(currentUrl);
|
|
647
701
|
|
|
648
702
|
if (siteConfig.verbose === 1) {
|
|
649
703
|
const partyType = isFirstParty ? 'first-party' : 'third-party';
|
|
650
|
-
|
|
704
|
+
const searchMethod = useGrep ? 'grep' : 'js';
|
|
705
|
+
console.log(`[match][${simplifiedUrl}] ${respUrl} (${partyType}, ${searchMethod}) contains searchstring (${logicType}): "${matchedString}"`);
|
|
651
706
|
}
|
|
652
707
|
|
|
653
708
|
if (dumpUrls) {
|
|
654
709
|
const timestamp = new Date().toISOString();
|
|
655
710
|
const partyType = isFirstParty ? 'first-party' : 'third-party';
|
|
711
|
+
const searchMethod = useGrep ? 'grep' : 'js';
|
|
656
712
|
try {
|
|
657
713
|
fs.appendFileSync(matchedUrlsLogFile,
|
|
658
|
-
`${timestamp} [match][${simplifiedUrl}] ${respUrl} (${partyType}, searchstring (${logicType}): "${matchedString}")\n`);
|
|
714
|
+
`${timestamp} [match][${simplifiedUrl}] ${respUrl} (${partyType}, ${searchMethod}, searchstring (${logicType}): "${matchedString}")\n`);
|
|
659
715
|
} catch (logErr) {
|
|
660
716
|
console.warn(`[warn] Failed to write to matched URLs log: ${logErr.message}`);
|
|
661
717
|
}
|
|
662
718
|
}
|
|
663
719
|
} else if (forceDebug) {
|
|
664
720
|
const partyType = isFirstParty ? 'first-party' : 'third-party';
|
|
665
|
-
|
|
721
|
+
const searchMethod = useGrep ? 'grep' : 'js';
|
|
722
|
+
console.log(`[debug] ${respUrl} (${partyType}, ${searchMethod}) matched regex but no searchstring found`);
|
|
666
723
|
if (error) {
|
|
667
724
|
console.log(`[debug] Search error: ${error}`);
|
|
668
725
|
}
|
package/nwss.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// === Network scanner script (nwss.js) v2.0.
|
|
1
|
+
// === Network scanner script (nwss.js) v2.0.25 ===
|
|
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
|
|
@@ -132,7 +132,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
|
|
|
132
132
|
const { monitorBrowserHealth, isBrowserHealthy, isQuicklyResponsive, performGroupWindowCleanup, performRealtimeWindowCleanup, trackPageForRealtime, updatePageUsage, cleanupPageBeforeReload } = require('./lib/browserhealth');
|
|
133
133
|
|
|
134
134
|
// --- Script Configuration & Constants ---
|
|
135
|
-
const VERSION = '2.0.
|
|
135
|
+
const VERSION = '2.0.25'; // Script version
|
|
136
136
|
|
|
137
137
|
// get startTime
|
|
138
138
|
const startTime = Date.now();
|
|
@@ -997,7 +997,7 @@ function matchesIgnoreDomain(domain, ignorePatterns) {
|
|
|
997
997
|
|
|
998
998
|
function setupFrameHandling(page, forceDebug) {
|
|
999
999
|
// Track active frames and clear on navigation to prevent detached frame access
|
|
1000
|
-
let activeFrames = new
|
|
1000
|
+
let activeFrames = new Set(); // Use Set to track frame references
|
|
1001
1001
|
|
|
1002
1002
|
// Clear frame tracking on navigation to prevent stale references
|
|
1003
1003
|
page.on('framenavigated', (frame) => {
|
|
@@ -1031,7 +1031,6 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1031
1031
|
// Enhanced frame validation with multiple safety checks
|
|
1032
1032
|
let frameUrl;
|
|
1033
1033
|
try {
|
|
1034
|
-
// Test frame accessibility first
|
|
1035
1034
|
frameUrl = frame.url();
|
|
1036
1035
|
|
|
1037
1036
|
// Check if frame is detached (if method exists)
|
|
@@ -1041,12 +1040,17 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1041
1040
|
}
|
|
1042
1041
|
return;
|
|
1043
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
activeFrames.add(frame);
|
|
1045
|
+
|
|
1046
|
+
if (forceDebug) {
|
|
1047
|
+
console.log(formatLogMessage('debug', `New frame attached: ${frameUrl || 'about:blank'}`));
|
|
1048
|
+
}
|
|
1044
1049
|
} catch (frameAccessError) {
|
|
1045
1050
|
// Frame is not accessible (likely detached)
|
|
1046
1051
|
return;
|
|
1047
1052
|
}
|
|
1048
|
-
|
|
1049
|
-
activeFrames.add(frame);
|
|
1053
|
+
|
|
1050
1054
|
} catch (detachError) {
|
|
1051
1055
|
// Frame state checking can throw in 23.x, handle gracefully
|
|
1052
1056
|
if (forceDebug) {
|
|
@@ -1055,14 +1059,10 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1055
1059
|
return;
|
|
1056
1060
|
}
|
|
1057
1061
|
|
|
1058
|
-
// Store frame with timestamp for tracking
|
|
1059
|
-
activeFrames.set(frame, Date.now());
|
|
1060
1062
|
|
|
1061
1063
|
if (frame !== page.mainFrame() && frame.parentFrame()) { // Only handle child frames
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
console.log(formatLogMessage('debug', `New frame attached: ${frameUrl || 'about:blank'}`));
|
|
1065
|
-
}
|
|
1064
|
+
let frameUrl;
|
|
1065
|
+
frameUrl = frame.url();
|
|
1066
1066
|
|
|
1067
1067
|
// Don't try to navigate to frames with invalid/empty URLs
|
|
1068
1068
|
if (!frameUrl ||
|
|
@@ -1100,6 +1100,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1100
1100
|
// Let frames load naturally - manual navigation often causes Protocol errors
|
|
1101
1101
|
// await frame.goto(frame.url(), { waitUntil: 'domcontentloaded', timeout: 5000 });
|
|
1102
1102
|
|
|
1103
|
+
try {
|
|
1103
1104
|
if (forceDebug) {
|
|
1104
1105
|
console.log(formatLogMessage('debug', `Frame will load naturally: ${frameUrl}`));
|
|
1105
1106
|
}
|
|
@@ -1117,11 +1118,11 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1117
1118
|
});
|
|
1118
1119
|
// Handle frame navigations (keep this for monitoring)
|
|
1119
1120
|
page.on('framenavigated', (frame) => {
|
|
1120
|
-
let frameUrl;
|
|
1121
1121
|
|
|
1122
1122
|
// Skip if frame is not in our active set
|
|
1123
1123
|
if (!activeFrames.has(frame)) return;
|
|
1124
1124
|
|
|
1125
|
+
let frameUrl;
|
|
1125
1126
|
try {
|
|
1126
1127
|
frameUrl = frame.url();
|
|
1127
1128
|
} catch (urlErr) {
|
|
@@ -1143,17 +1144,14 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1143
1144
|
// Optional: Handle frame detachment for cleanup
|
|
1144
1145
|
page.on('framedetached', (frame) => {
|
|
1145
1146
|
// Remove from active tracking
|
|
1146
|
-
activeFrames.delete(frame);
|
|
1147
|
+
activeFrames.delete(frame); // This works for both Map and Set
|
|
1147
1148
|
|
|
1148
|
-
|
|
1149
|
-
let frameUrl;
|
|
1149
|
+
|
|
1150
1150
|
if (forceDebug) {
|
|
1151
|
+
let frameUrl;
|
|
1151
1152
|
try {
|
|
1152
1153
|
frameUrl = frame.url();
|
|
1153
|
-
|
|
1154
|
-
// Frame already detached, can't get URL
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1154
|
+
|
|
1157
1155
|
if (frameUrl &&
|
|
1158
1156
|
frameUrl !== 'about:blank' &&
|
|
1159
1157
|
frameUrl !== 'about:srcdoc' &&
|
|
@@ -1162,6 +1160,11 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1162
1160
|
!frameUrl.startsWith('chrome-extension://')) {
|
|
1163
1161
|
console.log(formatLogMessage('debug', `Frame detached: ${frameUrl}`));
|
|
1164
1162
|
}
|
|
1163
|
+
} catch (urlErr) {
|
|
1164
|
+
// Frame already detached, can't get URL - this is expected
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1165
1168
|
}
|
|
1166
1169
|
});
|
|
1167
1170
|
}
|
|
@@ -1951,7 +1954,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1951
1954
|
// Parse searchstring patterns using module
|
|
1952
1955
|
const { searchStrings, searchStringsAnd, hasSearchString, hasSearchStringAnd } = parseSearchStrings(siteConfig.searchstring, siteConfig.searchstring_and);
|
|
1953
1956
|
const useCurl = siteConfig.curl === true; // Use curl if enabled, regardless of searchstring
|
|
1954
|
-
let useGrep = siteConfig.grep === true
|
|
1957
|
+
let useGrep = siteConfig.grep === true; // Grep can work independently
|
|
1955
1958
|
|
|
1956
1959
|
// Get user agent for curl if needed
|
|
1957
1960
|
let curlUserAgent = '';
|
|
@@ -1973,7 +1976,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1973
1976
|
}
|
|
1974
1977
|
|
|
1975
1978
|
if (useGrep && forceDebug) {
|
|
1976
|
-
console.log(formatLogMessage('debug', `Grep-based pattern matching enabled for ${currentUrl}`));
|
|
1979
|
+
console.log(formatLogMessage('debug', `Grep-based pattern matching enabled for ${currentUrl}${useCurl ? ' (with curl)' : ' (with response handler)'}`));
|
|
1977
1980
|
}
|
|
1978
1981
|
|
|
1979
1982
|
// Validate grep availability if needed
|
|
@@ -1993,7 +1996,6 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1993
1996
|
if (!curlCheck.isAvailable) {
|
|
1994
1997
|
console.warn(formatLogMessage('warn', `Curl not available for ${currentUrl}: ${curlCheck.error}. Skipping curl-based analysis.`));
|
|
1995
1998
|
useCurl = false;
|
|
1996
|
-
useGrep = false; // Grep requires curl
|
|
1997
1999
|
} else if (forceDebug) {
|
|
1998
2000
|
console.log(formatLogMessage('debug', `Using curl: ${curlCheck.version}`));
|
|
1999
2001
|
}
|
|
@@ -2643,7 +2645,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2643
2645
|
|
|
2644
2646
|
// If curl is enabled, download and analyze content immediately
|
|
2645
2647
|
if (useCurl) {
|
|
2646
|
-
// Check bypass_cache before attempting cache lookup
|
|
2648
|
+
// Check bypass_cache before attempting cache lookup (curl mode)
|
|
2647
2649
|
let cachedContent = null;
|
|
2648
2650
|
if (!shouldBypassCacheForUrl(reqUrl, siteConfig)) {
|
|
2649
2651
|
// Check request cache first if smart cache is available and caching is enabled
|
|
@@ -2732,8 +2734,30 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2732
2734
|
}
|
|
2733
2735
|
}
|
|
2734
2736
|
}
|
|
2737
|
+
} else if (useGrep && (hasSearchString || hasSearchStringAnd)) {
|
|
2738
|
+
// Use grep with response handler (no curl)
|
|
2739
|
+
if (forceDebug) {
|
|
2740
|
+
console.log(formatLogMessage('debug', `[grep-response] Queuing ${reqUrl} for grep analysis via response handler`));
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
// Queue for grep processing via response handler
|
|
2744
|
+
// The response handler will download content and call grep
|
|
2745
|
+
if (dryRunMode) {
|
|
2746
|
+
matchedDomains.get('dryRunMatches').push({
|
|
2747
|
+
regex: matchedRegexPattern,
|
|
2748
|
+
domain: reqDomain,
|
|
2749
|
+
resourceType: resourceType,
|
|
2750
|
+
fullUrl: reqUrl,
|
|
2751
|
+
isFirstParty: isFirstParty,
|
|
2752
|
+
needsGrepCheck: true
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// Don't process immediately - let response handler do the work
|
|
2757
|
+
if (forceDebug) {
|
|
2758
|
+
console.log(formatLogMessage('debug', `URL ${reqUrl} queued for grep analysis via response handler`));
|
|
2759
|
+
}
|
|
2735
2760
|
}
|
|
2736
|
-
|
|
2737
2761
|
// No break needed since we've already determined if regex matched
|
|
2738
2762
|
}
|
|
2739
2763
|
request.continue();
|
|
@@ -2742,8 +2766,8 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2742
2766
|
// Mark page as actively processing network requests
|
|
2743
2767
|
updatePageUsage(page, true);
|
|
2744
2768
|
|
|
2745
|
-
// Add response handler
|
|
2746
|
-
if ((hasSearchString || hasSearchStringAnd) && !useCurl && !
|
|
2769
|
+
// Add response handler if searchstring is defined and either no curl, or grep without curl
|
|
2770
|
+
if ((hasSearchString || hasSearchStringAnd) && (!useCurl || (useGrep && !useCurl))) {
|
|
2747
2771
|
const responseHandler = createResponseHandler({
|
|
2748
2772
|
searchStrings,
|
|
2749
2773
|
searchStringsAnd,
|
|
@@ -2761,6 +2785,7 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
2761
2785
|
} : undefined,
|
|
2762
2786
|
currentUrl,
|
|
2763
2787
|
perSiteSubDomains,
|
|
2788
|
+
useGrep, // Pass grep flag to response handler
|
|
2764
2789
|
ignoreDomains,
|
|
2765
2790
|
matchesIgnoreDomain,
|
|
2766
2791
|
getRootDomain,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.25",
|
|
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": {
|