@fanboynz/network-scanner 2.0.65 → 3.0.0

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
@@ -4,6 +4,10 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const psl = require('psl');
7
+ const { formatLogMessage, messageColors } = require('./colorize');
8
+ // Subsystem tag matches the project convention (other modules use
9
+ // flowproxy/cloudflare/curl/grep/etc. precomputed tags).
10
+ const ADBLOCK_TAG = messageColors.processing('[adblock]');
7
11
 
8
12
  // Hoisted constants — avoid recreating per rule (~80K times for EasyList)
9
13
  const COSMETIC_OPTIONS = new Set(['generichide', 'elemhide', 'specifichide', 'genericblock']);
@@ -15,32 +19,41 @@ const PARSE_TYPE_MAP = {
15
19
  };
16
20
 
17
21
  /**
18
- * Simple LRU cache for URL parsing results
19
- * Prevents memory leaks with fixed size limit
22
+ * FIFO cache with a hard size cap. Evicts the OLDEST-inserted entry on
23
+ * overflow (not the least-recently-used) this is FIFO, not LRU, despite
24
+ * the original "Simple LRU cache" naming. Proper LRU would require
25
+ * deleting-and-reinserting on every .get() hit to bubble accessed keys
26
+ * to the end, which adds per-hit Map.delete + Map.set overhead. In hot
27
+ * paths (32k-entry result cache hit thousands of times per scan), that
28
+ * overhead is real; FIFO with a generous cap is the simpler trade.
29
+ *
30
+ * If access-pattern shifts become a real problem (a few "always hot"
31
+ * URLs evicted while never-used entries survive), implement move-on-get
32
+ * here — the API stays compatible.
20
33
  */
21
- class URLCache {
34
+ class FIFOCache {
22
35
  constructor(maxSize = 1000) {
23
36
  this.cache = new Map();
24
37
  this.maxSize = maxSize;
25
38
  }
26
-
39
+
27
40
  get(url) {
28
41
  return this.cache.get(url);
29
42
  }
30
-
43
+
31
44
  set(url, value) {
32
- // LRU eviction: if at max size, delete oldest entry
45
+ // FIFO eviction: drop the oldest-inserted entry when full.
33
46
  if (this.cache.size >= this.maxSize) {
34
47
  const firstKey = this.cache.keys().next().value;
35
48
  this.cache.delete(firstKey);
36
49
  }
37
50
  this.cache.set(url, value);
38
51
  }
39
-
52
+
40
53
  clear() {
41
54
  this.cache.clear();
42
55
  }
43
-
56
+
44
57
  getStats() {
45
58
  return {
46
59
  size: this.cache.size,
@@ -56,10 +69,12 @@ class URLCache {
56
69
  * @returns {Object} Rule matcher with matching functions
57
70
  */
58
71
  function parseAdblockRules(filePath, options = {}) {
59
- const {
60
- enableLogging = false,
61
- caseSensitive = false
62
- } = options;
72
+ // caseSensitive option removed — it was destructured here and threaded
73
+ // through to createMatcher, then never actually read. All hostname/
74
+ // pattern comparisons are case-insensitive (lowercased explicitly or
75
+ // via /i regex flag), so the option would have been a substantial
76
+ // refactor to honor. Removed rather than left as a documented lie.
77
+ const { enableLogging = false } = options;
63
78
 
64
79
  let fileContent;
65
80
  try {
@@ -175,27 +190,27 @@ function parseAdblockRules(filePath, options = {}) {
175
190
  } catch (err) {
176
191
  rules.stats.invalid++;
177
192
  if (enableLogging) {
178
- console.log(`[Adblock] Failed to parse rule: ${line} - ${err.message}`);
193
+ console.log(formatLogMessage('warn', `${ADBLOCK_TAG} Failed to parse rule: ${line} - ${err.message}`));
179
194
  }
180
195
  }
181
196
  }
182
197
 
183
198
  if (enableLogging) {
184
- console.log(`[Adblock] Loaded ${rules.stats.total} rules:`);
185
- console.log(` - Domain rules: ${rules.stats.domain}`);
186
- console.log(` • Exact matches (Map): ${rules.stats.domainMapEntries}`);
187
- console.log(` • Wildcard patterns (Array): ${rules.domainRules.length}`);
188
- console.log(` - Third-party rules: ${rules.stats.thirdParty}`);
189
- console.log(` - First-party rules: ${rules.stats.firstParty}`);
190
- console.log(` - Path rules: ${rules.stats.path}`);
191
- console.log(` - Script rules: ${rules.stats.script}`);
192
- console.log(` - Regex rules: ${rules.stats.regex}`);
193
- console.log(` - Whitelist rules: ${rules.stats.whitelist}`);
194
- console.log(` - Comments/Element hiding: ${rules.stats.comments + rules.stats.elementHiding}`);
195
- console.log(` - Invalid rules: ${rules.stats.invalid}`);
199
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Loaded ${rules.stats.total} rules:`));
200
+ console.log(formatLogMessage('debug', ` - Domain rules: ${rules.stats.domain}`));
201
+ console.log(formatLogMessage('debug', ` • Exact matches (Map): ${rules.stats.domainMapEntries}`));
202
+ console.log(formatLogMessage('debug', ` • Wildcard patterns (Array): ${rules.domainRules.length}`));
203
+ console.log(formatLogMessage('debug', ` - Third-party rules: ${rules.stats.thirdParty}`));
204
+ console.log(formatLogMessage('debug', ` - First-party rules: ${rules.stats.firstParty}`));
205
+ console.log(formatLogMessage('debug', ` - Path rules: ${rules.stats.path}`));
206
+ console.log(formatLogMessage('debug', ` - Script rules: ${rules.stats.script}`));
207
+ console.log(formatLogMessage('debug', ` - Regex rules: ${rules.stats.regex}`));
208
+ console.log(formatLogMessage('debug', ` - Whitelist rules: ${rules.stats.whitelist}`));
209
+ console.log(formatLogMessage('debug', ` - Comments/Element hiding: ${rules.stats.comments + rules.stats.elementHiding}`));
210
+ console.log(formatLogMessage('debug', ` - Invalid rules: ${rules.stats.invalid}`));
196
211
  }
197
212
 
198
- return createMatcher(rules, { enableLogging, caseSensitive });
213
+ return createMatcher(rules, { enableLogging });
199
214
  }
200
215
 
201
216
  /**
@@ -229,12 +244,13 @@ function parseRule(rule, isWhitelist, enableLogging = false) {
229
244
  const options = optionsStr.split(',');
230
245
  const parsedOptions = {};
231
246
 
247
+ // No cosmetic-option guard here — the line-level filter at lines
248
+ // ~126-135 already drops any rule containing $generichide/$elemhide/
249
+ // etc. before parseRule is ever called. The previous in-loop
250
+ // COSMETIC_OPTIONS.has() guard was unreachable.
232
251
  for (const opt of options) {
233
252
  const [key, value] = opt.split('=');
234
- const trimmedKey = key.trim();
235
- if (!COSMETIC_OPTIONS.has(trimmedKey)) {
236
- parsedOptions[trimmedKey] = value ? value.trim() : true;
237
- }
253
+ parsedOptions[key.trim()] = value ? value.trim() : true;
238
254
  }
239
255
 
240
256
  // Check for third-party option
@@ -359,10 +375,12 @@ function createDomainMatcher(domain) {
359
375
  }
360
376
 
361
377
  /**
362
- * Shared regex cache — deduplicates identical compiled patterns across rules
363
- * Large lists (EasyList ~80K rules) often have thousands of duplicate patterns
378
+ * Shared regex cache — deduplicates identical compiled patterns across rules.
379
+ * Large lists (EasyList ~80K rules) often have thousands of duplicate
380
+ * patterns. Bounded via FIFOCache (was a bare Map with no size cap; for
381
+ * giant lists with many unique patterns it could grow unbounded).
364
382
  */
365
- const _regexCache = new Map();
383
+ const _regexCache = new FIFOCache(20000);
366
384
 
367
385
  /**
368
386
  * Creates a pattern matcher for path/wildcard rules
@@ -370,8 +388,12 @@ const _regexCache = new Map();
370
388
  * @returns {Function} Matcher function
371
389
  */
372
390
  function createPatternMatcher(pattern) {
373
- // Check cache for already-compiled identical pattern
374
- const cached = _regexCache.get(pattern);
391
+ // Capture the ORIGINAL pattern as the cache key the local `pattern`
392
+ // var gets mutated by the anchor-strip below, so previously the get()
393
+ // used the pre-strip key but set() stored under the post-strip key,
394
+ // making every anchored pattern miss its own cache on the next lookup.
395
+ const cacheKey = pattern;
396
+ const cached = _regexCache.get(cacheKey);
375
397
  if (cached) return cached;
376
398
 
377
399
  // Convert adblock pattern to regex
@@ -401,7 +423,7 @@ function createPatternMatcher(pattern) {
401
423
 
402
424
  const regex = new RegExp(regexPattern, 'i');
403
425
  const matcher = (url) => regex.test(url);
404
- _regexCache.set(pattern, matcher);
426
+ _regexCache.set(cacheKey, matcher);
405
427
  return matcher;
406
428
  }
407
429
 
@@ -412,15 +434,21 @@ function createPatternMatcher(pattern) {
412
434
  * @returns {Object} Matcher with shouldBlock function
413
435
  */
414
436
  function createMatcher(rules, options = {}) {
415
- const { enableLogging = false, caseSensitive = false } = options;
437
+ const { enableLogging = false } = options;
416
438
 
417
- const urlCache = new URLCache(16000);
418
- let cacheHits = 0;
419
- let cacheMisses = 0;
439
+ const urlCache = new FIFOCache(16000);
440
+ // Per-cache counters split out. Previously ONE counter pair received
441
+ // hits/misses from THREE caches: the result cache, the request-URL
442
+ // parse cache, AND the source-URL parse cache — getStats().hitRate
443
+ // was the average of three different cache behaviors and not
444
+ // interpretable for diagnosing performance.
445
+ let resultCacheHits = 0, resultCacheMisses = 0;
446
+ let urlCacheHits = 0, urlCacheMisses = 0;
447
+ let sourceCacheHits = 0, sourceCacheMisses = 0;
420
448
  const hasPartyRules = rules.thirdPartyRules.length > 0 || rules.firstPartyRules.length > 0;
421
- // Result cache with LRU eviction evicts oldest entries one at a time
422
- // instead of clearing everything when full
423
- const resultCache = new URLCache(32000);
449
+ // Result cache uses FIFO eviction (see FIFOCache class comment)
450
+ // evicts oldest entries one at a time instead of clearing everything.
451
+ const resultCache = new FIFOCache(32000);
424
452
 
425
453
  function resultCacheGet(url, sourceUrl, resourceType) {
426
454
  return resultCache.get(url + '\0' + sourceUrl + '\0' + resourceType);
@@ -445,30 +473,30 @@ function createMatcher(rules, options = {}) {
445
473
  // Check result cache — same URL+source+type always produces same result
446
474
  const cachedResult = resultCacheGet(url, sourceUrl, resourceType);
447
475
  if (cachedResult) {
448
- cacheHits++;
476
+ resultCacheHits++;
449
477
  return cachedResult;
450
478
  }
451
- cacheMisses++;
479
+ resultCacheMisses++;
452
480
 
453
481
  // OPTIMIZATION: Check cache first for URL parsing (60% faster)
454
482
  let cachedData = urlCache.get(url);
455
483
  let hostname, lowerHostname;
456
-
484
+
457
485
  if (cachedData) {
458
486
  hostname = cachedData.hostname;
459
487
  lowerHostname = cachedData.lowerHostname;
460
- cacheHits++;
488
+ urlCacheHits++;
461
489
  } else {
462
490
  // Parse URL and cache result
463
491
  const urlObj = new URL(url);
464
492
  hostname = urlObj.hostname;
465
493
  lowerHostname = hostname.toLowerCase();
466
-
494
+
467
495
  urlCache.set(url, {
468
496
  hostname,
469
497
  lowerHostname
470
498
  });
471
- cacheMisses++;
499
+ urlCacheMisses++;
472
500
  }
473
501
 
474
502
  // Lazy parent domain computation — only built when exact Map lookup misses
@@ -491,19 +519,19 @@ function createMatcher(rules, options = {}) {
491
519
 
492
520
  if (cachedSourceData) {
493
521
  sourceDomain = cachedSourceData.lowerHostname;
494
- cacheHits++;
522
+ sourceCacheHits++;
495
523
  } else {
496
524
  // Parse and cache sourceUrl
497
525
  try {
498
526
  const sourceUrlObj = new URL(sourceUrl);
499
527
  sourceDomain = sourceUrlObj.hostname.toLowerCase();
500
-
528
+
501
529
  // Cache sourceUrl parsing result (same as request URLs)
502
530
  urlCache.set(sourceUrl, {
503
531
  hostname: sourceUrlObj.hostname,
504
532
  lowerHostname: sourceDomain
505
533
  });
506
- cacheMisses++;
534
+ sourceCacheMisses++;
507
535
  } catch (err) {
508
536
  // Invalid sourceUrl, leave as null
509
537
  }
@@ -522,7 +550,7 @@ function createMatcher(rules, options = {}) {
522
550
  if (rule) {
523
551
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
524
552
  if (enableLogging) {
525
- console.log(`[Adblock] Whitelisted: ${url} (${rule.raw || rule.pattern})`);
553
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Whitelisted: ${url} (${rule.raw || rule.pattern})`));
526
554
  }
527
555
  const r = { blocked: false, rule: rule.raw || rule.pattern, reason: 'whitelisted' };
528
556
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -537,7 +565,7 @@ function createMatcher(rules, options = {}) {
537
565
  if (rule) {
538
566
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
539
567
  if (enableLogging) {
540
- console.log(`[Adblock] Whitelisted: ${url} (${rule.raw || rule.pattern})`);
568
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Whitelisted: ${url} (${rule.raw || rule.pattern})`));
541
569
  }
542
570
  const r = { blocked: false, rule: rule.raw || rule.pattern, reason: 'whitelisted' };
543
571
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -552,7 +580,7 @@ function createMatcher(rules, options = {}) {
552
580
  const rule = rules.whitelist[i];
553
581
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
554
582
  if (enableLogging) {
555
- console.log(`[Adblock] Whitelisted: ${url} (${rule.raw || rule.pattern})`);
583
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Whitelisted: ${url} (${rule.raw || rule.pattern})`));
556
584
  }
557
585
  const r = { blocked: false, rule: rule.raw || rule.pattern, reason: 'whitelisted' };
558
586
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -567,7 +595,7 @@ function createMatcher(rules, options = {}) {
567
595
  if (rule) {
568
596
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
569
597
  if (enableLogging) {
570
- console.log(`[Adblock] Blocked domain: ${url} (${rule.raw || rule.pattern})`);
598
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked domain: ${url} (${rule.raw || rule.pattern})`));
571
599
  }
572
600
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'domain_rule' };
573
601
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -581,7 +609,7 @@ function createMatcher(rules, options = {}) {
581
609
  if (rule) {
582
610
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
583
611
  if (enableLogging) {
584
- console.log(`[Adblock] Blocked domain: ${url} (${rule.raw || rule.pattern})`);
612
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked domain: ${url} (${rule.raw || rule.pattern})`));
585
613
  }
586
614
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'domain_rule' };
587
615
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -596,7 +624,7 @@ function createMatcher(rules, options = {}) {
596
624
  const rule = rules.domainRules[i];
597
625
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
598
626
  if (enableLogging) {
599
- console.log(`[Adblock] Blocked domain: ${url} (${rule.raw || rule.pattern})`);
627
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked domain: ${url} (${rule.raw || rule.pattern})`));
600
628
  }
601
629
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'domain_rule' };
602
630
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -611,7 +639,7 @@ function createMatcher(rules, options = {}) {
611
639
  const rule = rules.thirdPartyRules[i];
612
640
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
613
641
  if (enableLogging) {
614
- console.log(`[Adblock] Blocked third-party: ${url} (${rule.raw || rule.pattern})`);
642
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked third-party: ${url} (${rule.raw || rule.pattern})`));
615
643
  }
616
644
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'third_party_rule' };
617
645
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -627,7 +655,7 @@ function createMatcher(rules, options = {}) {
627
655
  const rule = rules.firstPartyRules[i];
628
656
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
629
657
  if (enableLogging) {
630
- console.log(`[Adblock] Blocked first-party: ${url} (${rule.raw || rule.pattern})`);
658
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked first-party: ${url} (${rule.raw || rule.pattern})`));
631
659
  }
632
660
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'first_party_rule' };
633
661
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -643,7 +671,7 @@ function createMatcher(rules, options = {}) {
643
671
  const rule = rules.scriptRules[i];
644
672
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
645
673
  if (enableLogging) {
646
- console.log(`[Adblock] Blocked script: ${url} (${rule.raw || rule.pattern})`);
674
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked script: ${url} (${rule.raw || rule.pattern})`));
647
675
  }
648
676
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'script_rule' };
649
677
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -658,7 +686,7 @@ function createMatcher(rules, options = {}) {
658
686
  const rule = rules.pathRules[i];
659
687
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
660
688
  if (enableLogging) {
661
- console.log(`[Adblock] Blocked path: ${url} (${rule.raw || rule.pattern})`);
689
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked path: ${url} (${rule.raw || rule.pattern})`));
662
690
  }
663
691
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'path_rule' };
664
692
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -672,7 +700,7 @@ function createMatcher(rules, options = {}) {
672
700
  const rule = rules.regexRules[i];
673
701
  if (matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDomain)) {
674
702
  if (enableLogging) {
675
- console.log(`[Adblock] Blocked regex: ${url} (${rule.raw || rule.pattern})`);
703
+ console.log(formatLogMessage('debug', `${ADBLOCK_TAG} Blocked regex: ${url} (${rule.raw || rule.pattern})`));
676
704
  }
677
705
  const r = { blocked: true, rule: rule.raw || rule.pattern, reason: 'regex_rule' };
678
706
  resultCacheSet(url, sourceUrl, resourceType, r);
@@ -687,7 +715,7 @@ function createMatcher(rules, options = {}) {
687
715
 
688
716
  } catch (err) {
689
717
  if (enableLogging) {
690
- console.log(`[Adblock] Error checking ${url}: ${err.message}`);
718
+ console.log(formatLogMessage('warn', `${ADBLOCK_TAG} Error checking ${url}: ${err.message}`));
691
719
  }
692
720
  // On error, allow request
693
721
  return {
@@ -703,19 +731,34 @@ function createMatcher(rules, options = {}) {
703
731
  * @returns {Object} Statistics object
704
732
  */
705
733
  getStats() {
706
- const hitRate = cacheHits + cacheMisses > 0
707
- ? ((cacheHits / (cacheHits + cacheMisses)) * 100).toFixed(1) + '%'
708
- : '0%';
709
-
710
- return {
734
+ // Per-cache hit rates instead of one lumped figure. The previous
735
+ // single `hitRate` averaged three different cache behaviors and
736
+ // wasn't actionable for diagnosing performance.
737
+ const rate = (hits, misses) =>
738
+ hits + misses > 0 ? ((hits / (hits + misses)) * 100).toFixed(1) + '%' : '0%';
739
+
740
+ return {
711
741
  ...rules.stats,
712
742
  cache: {
713
- hits: cacheHits,
714
- misses: cacheMisses,
715
- hitRate: hitRate,
716
- urlCacheSize: urlCache.cache.size,
717
- resultCacheSize: resultCache.cache.size,
718
- maxSize: urlCache.maxSize
743
+ result: {
744
+ hits: resultCacheHits,
745
+ misses: resultCacheMisses,
746
+ hitRate: rate(resultCacheHits, resultCacheMisses),
747
+ size: resultCache.cache.size,
748
+ maxSize: resultCache.maxSize
749
+ },
750
+ url: {
751
+ hits: urlCacheHits,
752
+ misses: urlCacheMisses,
753
+ hitRate: rate(urlCacheHits, urlCacheMisses),
754
+ size: urlCache.cache.size,
755
+ maxSize: urlCache.maxSize
756
+ },
757
+ source: {
758
+ hits: sourceCacheHits,
759
+ misses: sourceCacheMisses,
760
+ hitRate: rate(sourceCacheHits, sourceCacheMisses)
761
+ }
719
762
  }
720
763
  };
721
764
  }
@@ -849,24 +892,26 @@ function matchesRule(rule, url, hostname, isThirdParty, resourceType, sourceDoma
849
892
  }
850
893
 
851
894
  /**
852
- * Extract base domain from hostname using Public Suffix List
853
- * Correctly handles multi-part TLDs like .co.uk, .com.au, .com.br
895
+ * Extract base domain from hostname using Public Suffix List.
896
+ * Correctly handles multi-part TLDs like .co.uk, .com.au, .com.br.
854
897
  * @param {string} hostname - Full hostname
855
898
  * @returns {string} Base domain
856
899
  */
857
- const _baseDomainCache = new Map();
900
+ // FIFOCache (not the previous clear-all-when-full Map). The old code
901
+ // nuked all 10000 cached entries on overflow, forcing the next 10000
902
+ // hosts to re-pay the psl.parse cost. FIFO evicts one entry at a time.
903
+ const _baseDomainCache = new FIFOCache(10000);
858
904
  function getBaseDomain(hostname) {
859
905
  const cached = _baseDomainCache.get(hostname);
860
906
  if (cached) return cached;
861
907
  const parsed = psl.parse(hostname);
862
908
  const result = (parsed && parsed.domain) ? parsed.domain : hostname;
863
- // Cap cache size
864
- if (_baseDomainCache.size > 10000) _baseDomainCache.clear();
865
909
  _baseDomainCache.set(hostname, result);
866
910
  return result;
867
911
  }
868
912
 
913
+ // Public surface. getBaseDomain stays internal — used by createMatcher's
914
+ // third-party check but no external caller ever imported it.
869
915
  module.exports = {
870
- parseAdblockRules,
871
- getBaseDomain
916
+ parseAdblockRules
872
917
  };