@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 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 script option
237
- if (parsed.options['script']) {
238
- parsed.isScript = true;
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
- parsed.matcher = new RegExp(regexPattern, 'i');
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 third-party rules to check
382
- // Avoids expensive URL parsing (2x new URL() calls) when no third-party rules exist
383
- const isThirdParty = (sourceUrl && rules.thirdPartyRules.length > 0)
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 script option
699
- if (rule.isScript && resourceType !== 'script' && !url.endsWith('.js')) {
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
  };
@@ -345,6 +345,5 @@ async function clearSiteDataEnhanced(page, currentUrl, forceDebug) {
345
345
  module.exports = {
346
346
  clearSiteData,
347
347
  clearSiteDataViaCDP,
348
- clearSiteDataViaPage,
349
- clearSiteDataEnhanced
348
+ clearSiteDataViaPage
350
349
  };
package/lib/grep.js CHANGED
@@ -88,6 +88,7 @@ async function grepContent(content, searchPatterns, options = {}) {
88
88
  }
89
89
 
90
90
  let tempFile = null;
91
+ let tempDir = null;
91
92
 
92
93
  try {
93
94
  // Create temporary directory and file with content
@@ -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 new Promise(resolve => setTimeout(resolve, SCROLLING.SMOOTH_INCREMENT_DELAY));
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, ELEMENT_INTERACTION.TEXT_PREVIEW_LENGTH)
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.45",
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": {