@fanboynz/network-scanner 2.0.45 → 2.0.47
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/adblock.js +107 -19
- package/lib/cdp.js +1 -3
- package/lib/clear_sitedata.js +1 -2
- package/lib/grep.js +1 -0
- package/lib/interaction.js +10 -9
- package/nwss.js +0 -4
- package/package.json +1 -1
package/lib/adblock.js
CHANGED
|
@@ -62,6 +62,7 @@ function parseAdblockRules(filePath, options = {}) {
|
|
|
62
62
|
domainMap: new Map(), // ||domain.com^ - Exact domains for O(1) lookup
|
|
63
63
|
domainRules: [], // ||*.domain.com^ - Wildcard domains (fallback)
|
|
64
64
|
thirdPartyRules: [], // ||domain.com^$third-party
|
|
65
|
+
firstPartyRules: [],
|
|
65
66
|
pathRules: [], // /ads/*
|
|
66
67
|
scriptRules: [], // .js$script
|
|
67
68
|
regexRules: [], // /regex/
|
|
@@ -73,6 +74,7 @@ function parseAdblockRules(filePath, options = {}) {
|
|
|
73
74
|
domain: 0,
|
|
74
75
|
domainMapEntries: 0, // Exact domain matches in Map
|
|
75
76
|
thirdParty: 0,
|
|
77
|
+
firstParty: 0,
|
|
76
78
|
path: 0,
|
|
77
79
|
script: 0,
|
|
78
80
|
regex: 0,
|
|
@@ -137,6 +139,9 @@ function parseAdblockRules(filePath, options = {}) {
|
|
|
137
139
|
if (parsedRule.isThirdParty) {
|
|
138
140
|
rules.thirdPartyRules.push(parsedRule);
|
|
139
141
|
rules.stats.thirdParty++;
|
|
142
|
+
} else if (parsedRule.isFirstParty) {
|
|
143
|
+
rules.firstPartyRules.push(parsedRule);
|
|
144
|
+
rules.stats.firstParty++;
|
|
140
145
|
} else if (parsedRule.isDomain) {
|
|
141
146
|
// Store exact domains in Map for O(1) lookup, wildcards in array
|
|
142
147
|
if (parsedRule.domain && !parsedRule.domain.includes('*')) {
|
|
@@ -170,6 +175,7 @@ function parseAdblockRules(filePath, options = {}) {
|
|
|
170
175
|
console.log(` • Exact matches (Map): ${rules.stats.domainMapEntries}`);
|
|
171
176
|
console.log(` • Wildcard patterns (Array): ${rules.domainRules.length}`);
|
|
172
177
|
console.log(` - Third-party rules: ${rules.stats.thirdParty}`);
|
|
178
|
+
console.log(` - First-party rules: ${rules.stats.firstParty}`);
|
|
173
179
|
console.log(` - Path rules: ${rules.stats.path}`);
|
|
174
180
|
console.log(` - Script rules: ${rules.stats.script}`);
|
|
175
181
|
console.log(` - Regex rules: ${rules.stats.regex}`);
|
|
@@ -193,7 +199,10 @@ function parseRule(rule, isWhitelist) {
|
|
|
193
199
|
isWhitelist,
|
|
194
200
|
isDomain: false,
|
|
195
201
|
isThirdParty: false,
|
|
202
|
+
isFirstParty: false,
|
|
196
203
|
isScript: false,
|
|
204
|
+
resourceTypes: null, // Array of allowed resource types, null = all types
|
|
205
|
+
excludedResourceTypes: null, // Array of excluded resource types ($~script, $~image)
|
|
197
206
|
isRegex: false,
|
|
198
207
|
domainRestrictions: null, // { include: ['site.com'], exclude: ['~site.com'] }
|
|
199
208
|
pattern: '',
|
|
@@ -233,10 +242,47 @@ function parseRule(rule, isWhitelist) {
|
|
|
233
242
|
parsed.isThirdParty = true;
|
|
234
243
|
}
|
|
235
244
|
|
|
236
|
-
// Check for
|
|
237
|
-
if (parsed.options['
|
|
238
|
-
parsed.
|
|
245
|
+
// Check for first-party option ($first-party, $1p, $~third-party)
|
|
246
|
+
if (parsed.options['first-party'] || parsed.options['1p'] || parsed.options['~third-party']) {
|
|
247
|
+
parsed.isFirstParty = true;
|
|
239
248
|
}
|
|
249
|
+
|
|
250
|
+
// Parse resource type options
|
|
251
|
+
const TYPE_MAP = {
|
|
252
|
+
'script': 'script',
|
|
253
|
+
'stylesheet': 'stylesheet',
|
|
254
|
+
'css': 'stylesheet',
|
|
255
|
+
'image': 'image',
|
|
256
|
+
'xmlhttprequest': 'xhr',
|
|
257
|
+
'xhr': 'xhr',
|
|
258
|
+
'font': 'font',
|
|
259
|
+
'media': 'media',
|
|
260
|
+
'websocket': 'websocket',
|
|
261
|
+
'subdocument': 'subdocument',
|
|
262
|
+
'document': 'document',
|
|
263
|
+
'ping': 'ping',
|
|
264
|
+
'other': 'other'
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const matchedTypes = Object.keys(parsed.options)
|
|
268
|
+
.filter(key => TYPE_MAP[key])
|
|
269
|
+
.map(key => TYPE_MAP[key]);
|
|
270
|
+
|
|
271
|
+
const excludedTypes = Object.keys(parsed.options)
|
|
272
|
+
.filter(key => key.startsWith('~') && TYPE_MAP[key.substring(1)])
|
|
273
|
+
.map(key => TYPE_MAP[key.substring(1)]);
|
|
274
|
+
|
|
275
|
+
if (matchedTypes.length > 0) {
|
|
276
|
+
parsed.resourceTypes = matchedTypes;
|
|
277
|
+
if (parsed.options['script']) {
|
|
278
|
+
parsed.isScript = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (excludedTypes.length > 0) {
|
|
283
|
+
parsed.excludedResourceTypes = excludedTypes;
|
|
284
|
+
}
|
|
285
|
+
|
|
240
286
|
// Parse domain option: $domain=site1.com|site2.com|~excluded.com
|
|
241
287
|
if (parsed.options['domain']) {
|
|
242
288
|
const domainList = parsed.options['domain'];
|
|
@@ -261,15 +307,6 @@ function parseRule(rule, isWhitelist) {
|
|
|
261
307
|
exclude: exclude.length > 0 ? exclude : null
|
|
262
308
|
};
|
|
263
309
|
|
|
264
|
-
// For debugging
|
|
265
|
-
if (enableLogging && parsed.domainRestrictions) {
|
|
266
|
-
if (parsed.domainRestrictions.include) {
|
|
267
|
-
// console.log(`[Adblock] Rule includes domains: ${parsed.domainRestrictions.include.join(', ')}`);
|
|
268
|
-
}
|
|
269
|
-
if (parsed.domainRestrictions.exclude) {
|
|
270
|
-
// console.log(`[Adblock] Rule excludes domains: ${parsed.domainRestrictions.exclude.join(', ')}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
310
|
}
|
|
274
311
|
}
|
|
275
312
|
|
|
@@ -284,7 +321,8 @@ function parseRule(rule, isWhitelist) {
|
|
|
284
321
|
else if (pattern.startsWith('/') && pattern.endsWith('/')) {
|
|
285
322
|
parsed.isRegex = true;
|
|
286
323
|
const regexPattern = pattern.substring(1, pattern.length - 1);
|
|
287
|
-
|
|
324
|
+
const regex = new RegExp(regexPattern, 'i');
|
|
325
|
+
parsed.matcher = (url) => regex.test(url);
|
|
288
326
|
}
|
|
289
327
|
// Path/wildcard rules: /ads/* or ad.js
|
|
290
328
|
else {
|
|
@@ -378,10 +416,10 @@ function createMatcher(rules, options = {}) {
|
|
|
378
416
|
cacheMisses++;
|
|
379
417
|
}
|
|
380
418
|
|
|
381
|
-
// OPTIMIZATION #1: Only calculate third-party status if we have
|
|
382
|
-
|
|
383
|
-
const isThirdParty = (sourceUrl &&
|
|
384
|
-
? isThirdPartyRequest(url, sourceUrl)
|
|
419
|
+
// OPTIMIZATION #1: Only calculate third-party status if we have rules that need it
|
|
420
|
+
const hasPartyRules = rules.thirdPartyRules.length > 0 || rules.firstPartyRules.length > 0;
|
|
421
|
+
const isThirdParty = (sourceUrl && hasPartyRules)
|
|
422
|
+
? isThirdPartyRequest(url, sourceUrl)
|
|
385
423
|
: false;
|
|
386
424
|
|
|
387
425
|
// OPTIMIZATION #2: Calculate hostname parts once and reuse (avoid duplicate split operations)
|
|
@@ -513,6 +551,24 @@ function createMatcher(rules, options = {}) {
|
|
|
513
551
|
}
|
|
514
552
|
}
|
|
515
553
|
|
|
554
|
+
// Check first-party rules
|
|
555
|
+
if (!isThirdParty) {
|
|
556
|
+
const firstPartyLen = rules.firstPartyRules.length;
|
|
557
|
+
for (let i = 0; i < firstPartyLen; i++) {
|
|
558
|
+
const rule = rules.firstPartyRules[i];
|
|
559
|
+
if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
|
|
560
|
+
if (enableLogging) {
|
|
561
|
+
console.log(`[Adblock] Blocked first-party: ${url} (${rule.raw})`);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
blocked: true,
|
|
565
|
+
rule: rule.raw,
|
|
566
|
+
reason: 'first_party_rule'
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
516
572
|
// Check script rules
|
|
517
573
|
if (resourceType === 'script' || url.endsWith('.js')) {
|
|
518
574
|
const scriptRulesLen = rules.scriptRules.length; // V8: Cache length
|
|
@@ -675,6 +731,14 @@ function matchesDomainRestrictions(rule, sourceDomain) {
|
|
|
675
731
|
return true;
|
|
676
732
|
}
|
|
677
733
|
|
|
734
|
+
// Module-level constant for resource type normalization (hot path)
|
|
735
|
+
const RESOURCE_TYPE_ALIASES = {
|
|
736
|
+
'script': 'script', 'stylesheet': 'stylesheet', 'image': 'image',
|
|
737
|
+
'xhr': 'xhr', 'fetch': 'xhr', 'font': 'font', 'media': 'media',
|
|
738
|
+
'websocket': 'websocket', 'subdocument': 'subdocument',
|
|
739
|
+
'document': 'document', 'ping': 'ping', 'other': 'other'
|
|
740
|
+
};
|
|
741
|
+
|
|
678
742
|
/**
|
|
679
743
|
* Check if rule matches the given URL
|
|
680
744
|
* @param {Object} rule - Parsed rule object
|
|
@@ -685,6 +749,7 @@ function matchesDomainRestrictions(rule, sourceDomain) {
|
|
|
685
749
|
* @param {string|null} sourceDomain - Source page domain (for $domain option)
|
|
686
750
|
* @returns {boolean} True if rule matches
|
|
687
751
|
*/
|
|
752
|
+
|
|
688
753
|
function matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain) {
|
|
689
754
|
// Check domain restrictions first
|
|
690
755
|
if (!matchesDomainRestrictions(rule, sourceDomain)) {
|
|
@@ -695,11 +760,34 @@ function matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDoma
|
|
|
695
760
|
return false;
|
|
696
761
|
}
|
|
697
762
|
|
|
698
|
-
// Check
|
|
699
|
-
if (rule.
|
|
763
|
+
// Check first-party option
|
|
764
|
+
if (rule.isFirstParty && isThirdParty) {
|
|
700
765
|
return false;
|
|
701
766
|
}
|
|
702
767
|
|
|
768
|
+
// Check resource type restrictions
|
|
769
|
+
if (rule.resourceTypes) {
|
|
770
|
+
if (!resourceType) {
|
|
771
|
+
// No resource type info available — allow match for safety
|
|
772
|
+
} else {
|
|
773
|
+
// Normalize Puppeteer resource types to match our type names
|
|
774
|
+
const normalizedType = RESOURCE_TYPE_ALIASES[resourceType] || resourceType;
|
|
775
|
+
if (!rule.resourceTypes.includes(normalizedType)) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Check negated resource type restrictions ($~script, $~image, etc.)
|
|
782
|
+
if (rule.excludedResourceTypes) {
|
|
783
|
+
if (resourceType) {
|
|
784
|
+
const normalizedType = RESOURCE_TYPE_ALIASES[resourceType] || resourceType;
|
|
785
|
+
if (rule.excludedResourceTypes.includes(normalizedType)) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
703
791
|
// Apply matcher function
|
|
704
792
|
if (rule.isDomain) {
|
|
705
793
|
return rule.matcher(url, hostname);
|
package/lib/cdp.js
CHANGED
|
@@ -425,7 +425,5 @@ async function createEnhancedCDPSession(page, currentUrl, options = {}) {
|
|
|
425
425
|
module.exports = {
|
|
426
426
|
createCDPSession,
|
|
427
427
|
createPageWithTimeout,
|
|
428
|
-
setRequestInterceptionWithTimeout
|
|
429
|
-
validateCDPConfig,
|
|
430
|
-
createEnhancedCDPSession
|
|
428
|
+
setRequestInterceptionWithTimeout
|
|
431
429
|
};
|
package/lib/clear_sitedata.js
CHANGED
package/lib/grep.js
CHANGED
package/lib/interaction.js
CHANGED
|
@@ -373,7 +373,6 @@ async function humanLikeMouseMove(page, fromX, fromY, toX, toY, options = {}) {
|
|
|
373
373
|
|
|
374
374
|
// Add slight curve to movement (more human-like)
|
|
375
375
|
if (curve > 0 && i > 0 && i < actualSteps) {
|
|
376
|
-
const midpoint = actualSteps / 2;
|
|
377
376
|
const curveIntensity = Math.sin((i / actualSteps) * Math.PI) * curve * distance * MOUSE_MOVEMENT.CURVE_INTENSITY_RATIO;
|
|
378
377
|
const perpX = -(toY - fromY) / distance;
|
|
379
378
|
const perpY = (toX - fromX) / distance;
|
|
@@ -449,7 +448,7 @@ async function simulateScrolling(page, options = {}) {
|
|
|
449
448
|
// Smooth scrolling by breaking into smaller increments
|
|
450
449
|
for (let j = 0; j < smoothness; j++) {
|
|
451
450
|
await page.mouse.wheel({ deltaY: scrollDelta / smoothness });
|
|
452
|
-
await
|
|
451
|
+
await fastTimeout(SCROLLING.SMOOTH_INCREMENT_DELAY);
|
|
453
452
|
}
|
|
454
453
|
|
|
455
454
|
if (i < amount - 1) {
|
|
@@ -549,7 +548,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
549
548
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
550
549
|
try {
|
|
551
550
|
// Find visible, clickable elements
|
|
552
|
-
const elements = await page.evaluate((selectors, avoidWords) => {
|
|
551
|
+
const elements = await page.evaluate((selectors, avoidWords, textPreviewLen) => {
|
|
553
552
|
const clickableElements = [];
|
|
554
553
|
|
|
555
554
|
selectors.forEach(selector => {
|
|
@@ -571,7 +570,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
571
570
|
y: rect.top + rect.height / 2,
|
|
572
571
|
width: rect.width,
|
|
573
572
|
height: rect.height,
|
|
574
|
-
text: text.substring(0,
|
|
573
|
+
text: text.substring(0, textPreviewLen)
|
|
575
574
|
});
|
|
576
575
|
}
|
|
577
576
|
}
|
|
@@ -579,7 +578,7 @@ async function interactWithElements(page, options = {}) {
|
|
|
579
578
|
});
|
|
580
579
|
|
|
581
580
|
return clickableElements;
|
|
582
|
-
}, elementTypes, avoidDestructive ? ['delete', 'remove', 'submit', 'buy', 'purchase', 'order'] : []);
|
|
581
|
+
}, elementTypes, avoidDestructive ? ['delete', 'remove', 'submit', 'buy', 'purchase', 'order'] : [], ELEMENT_INTERACTION.TEXT_PREVIEW_LENGTH);
|
|
583
582
|
|
|
584
583
|
if (elements.length > 0) {
|
|
585
584
|
// Choose a random element to interact with
|
|
@@ -590,12 +589,12 @@ async function interactWithElements(page, options = {}) {
|
|
|
590
589
|
await humanLikeMouseMove(page, currentPos.x, currentPos.y, element.x, element.y);
|
|
591
590
|
|
|
592
591
|
// Brief pause before clicking
|
|
593
|
-
await fastTimeout(TIMING.CLICK_PAUSE_MIN + Math.random() * TIMING.CLICK_PAUSE_MAX);
|
|
592
|
+
await fastTimeout(TIMING.CLICK_PAUSE_MIN + Math.random() * (TIMING.CLICK_PAUSE_MAX - TIMING.CLICK_PAUSE_MIN));
|
|
594
593
|
|
|
595
594
|
await page.mouse.click(element.x, element.y);
|
|
596
595
|
|
|
597
596
|
// Brief pause after clicking
|
|
598
|
-
await fastTimeout(TIMING.POST_CLICK_MIN + Math.random() * TIMING.POST_CLICK_MAX);
|
|
597
|
+
await fastTimeout(TIMING.POST_CLICK_MIN + Math.random() * (TIMING.POST_CLICK_MAX - TIMING.POST_CLICK_MIN));
|
|
599
598
|
}
|
|
600
599
|
} catch (elementErr) {
|
|
601
600
|
// Continue to next attempt if this one fails
|
|
@@ -673,9 +672,9 @@ async function simulateTyping(page, text, options = {}) {
|
|
|
673
672
|
if (mistakes && Math.random() < mistakeRate) {
|
|
674
673
|
const wrongChar = String.fromCharCode(97 + Math.floor(Math.random() * 26));
|
|
675
674
|
await page.keyboard.type(wrongChar);
|
|
676
|
-
await fastTimeout(TIMING.MISTAKE_PAUSE_MIN + Math.random() * TIMING.MISTAKE_PAUSE_MAX);
|
|
675
|
+
await fastTimeout(TIMING.MISTAKE_PAUSE_MIN + Math.random() * (TIMING.MISTAKE_PAUSE_MAX - TIMING.MISTAKE_PAUSE_MIN));
|
|
677
676
|
await page.keyboard.press('Backspace');
|
|
678
|
-
await fastTimeout(TIMING.BACKSPACE_DELAY_MIN + Math.random() * TIMING.BACKSPACE_DELAY_MAX);
|
|
677
|
+
await fastTimeout(TIMING.BACKSPACE_DELAY_MIN + Math.random() * (TIMING.BACKSPACE_DELAY_MAX - TIMING.BACKSPACE_DELAY_MIN));
|
|
679
678
|
}
|
|
680
679
|
|
|
681
680
|
await page.keyboard.type(char);
|
|
@@ -818,6 +817,7 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
818
817
|
}
|
|
819
818
|
return;
|
|
820
819
|
}
|
|
820
|
+
await bodyExists.dispose();
|
|
821
821
|
} catch (bodyCheckErr) {
|
|
822
822
|
if (forceDebug) {
|
|
823
823
|
console.log(`[interaction] Page not ready for interaction on ${currentUrl} (waited ${Math.min(Math.max((options.siteTimeout || 20000) / 8, 2000), 5000)}ms): ${bodyCheckErr.message}`);
|
|
@@ -922,6 +922,7 @@ async function performPageInteraction(page, currentUrl, options = {}, forceDebug
|
|
|
922
922
|
const bodyElement = await page.$('body');
|
|
923
923
|
if (bodyElement) {
|
|
924
924
|
await page.hover('body');
|
|
925
|
+
await bodyElement.dispose();
|
|
925
926
|
}
|
|
926
927
|
} catch (hoverErr) {
|
|
927
928
|
// Silently handle hover failures - not critical
|
package/nwss.js
CHANGED
|
@@ -1790,10 +1790,6 @@ function setupFrameHandling(page, forceDebug) {
|
|
|
1790
1790
|
}
|
|
1791
1791
|
});
|
|
1792
1792
|
|
|
1793
|
-
page.on('response', (response) => {
|
|
1794
|
-
// Response handler - removed incorrect error logging
|
|
1795
|
-
});
|
|
1796
|
-
|
|
1797
1793
|
// Apply flowProxy timeouts if detection is enabled
|
|
1798
1794
|
if (flowproxyDetection) {
|
|
1799
1795
|
const flowproxyTimeouts = getFlowProxyTimeouts(siteConfig);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanboynz/network-scanner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.47",
|
|
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": {
|