@fanboynz/network-scanner 2.0.66 → 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/.github/workflows/npm-publish.yml +134 -10
- package/CHANGELOG.md +135 -0
- package/CLAUDE.md +18 -7
- package/README.md +12 -4
- package/lib/adblock-rust.js +23 -18
- package/lib/adblock.js +127 -82
- package/lib/browserexit.js +210 -200
- package/lib/browserhealth.js +84 -60
- package/lib/cdp.js +103 -81
- package/lib/clear_sitedata.js +61 -159
- package/lib/cloudflare.js +579 -409
- package/lib/colorize.js +29 -12
- package/lib/compare.js +16 -8
- package/lib/compress.js +2 -1
- package/lib/curl.js +287 -220
- package/lib/domain-cache.js +87 -40
- package/lib/dry-run.js +137 -194
- package/lib/fingerprint.js +20 -18
- package/lib/flowproxy.js +391 -188
- package/lib/ghost-cursor.js +8 -7
- package/lib/grep.js +248 -171
- package/lib/ignore_similar.js +70 -124
- package/lib/interaction.js +132 -235
- package/lib/nettools.js +309 -87
- package/lib/openvpn_vpn.js +12 -11
- package/lib/output.js +92 -59
- package/lib/post-processing.js +216 -162
- package/lib/redirect.js +46 -30
- package/lib/referrer.js +158 -165
- package/lib/searchstring.js +290 -381
- package/lib/smart-cache.js +141 -91
- package/lib/socks-relay.js +8 -7
- package/lib/spawn-async.js +137 -0
- package/lib/validate_rules.js +188 -176
- package/lib/wireguard_vpn.js +111 -117
- package/nwss.js +740 -156
- package/package.json +4 -4
package/lib/smart-cache.js
CHANGED
|
@@ -6,11 +6,24 @@
|
|
|
6
6
|
const { LRUCache } = require('lru-cache');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const { formatLogMessage } = require('./colorize');
|
|
9
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
10
|
+
|
|
11
|
+
// Precomputed colored '[SmartCache]' subsystem prefix. formatLogMessage only
|
|
12
|
+
// colors the [severity] tag; the '[SmartCache]' substring was sitting plain
|
|
13
|
+
// inside every debug message. Colored once at module load so we don't
|
|
14
|
+
// re-colorize per log call.
|
|
15
|
+
const SMART_CACHE_TAG = messageColors.processing('[SmartCache]');
|
|
10
16
|
|
|
11
17
|
// Shared frozen empty object -- avoids allocating new {} for every default param
|
|
12
18
|
const EMPTY = Object.freeze({});
|
|
13
19
|
|
|
20
|
+
// Headers we fold into the request cache key. Hoisted (not built per-call)
|
|
21
|
+
// so _generateRequestCacheKey doesn't allocate a fresh array on every cache
|
|
22
|
+
// op. Frozen because the array is treated as read-only at runtime.
|
|
23
|
+
const REQUEST_CACHE_KEY_HEADERS = Object.freeze([
|
|
24
|
+
'accept', 'accept-encoding', 'accept-language', 'user-agent'
|
|
25
|
+
]);
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* SmartCache - Intelligent caching system with multiple cache layers
|
|
16
29
|
* @class
|
|
@@ -48,21 +61,20 @@ class SmartCache {
|
|
|
48
61
|
this.saveTimeout = null;
|
|
49
62
|
this.pendingSave = false;
|
|
50
63
|
|
|
51
|
-
// Cache hot-path flags BEFORE _initializeCaches uses them
|
|
64
|
+
// Cache hot-path flags BEFORE _initializeCaches uses them.
|
|
52
65
|
this._debug = this.options.forceDebug;
|
|
53
66
|
this._highConcurrency = this.options.concurrency > 10;
|
|
54
67
|
this._criticalThreshold = this._highConcurrency ? 0.85 : 1.0;
|
|
55
68
|
this._warningThreshold = this._highConcurrency ? 0.70 : 0.85;
|
|
56
69
|
this._infoThreshold = this._highConcurrency ? 0.60 : 0.75;
|
|
57
|
-
|
|
58
|
-
// Initialize cache layers (may disable responseCache for high concurrency)
|
|
59
|
-
this._initializeCaches();
|
|
60
|
-
|
|
61
|
-
// Cache enable flags AFTER _initializeCaches which may modify options
|
|
62
70
|
this._enablePattern = this.options.enablePatternCache;
|
|
63
71
|
this._enableResponse = this.options.enableResponseCache;
|
|
64
72
|
this._enableWhois = this.options.enableWhoisCache;
|
|
65
73
|
this._enableRequest = this.options.enableRequestCache;
|
|
74
|
+
|
|
75
|
+
// Initialize cache layers. _initializeCaches may flip this._enableResponse
|
|
76
|
+
// off for very high concurrency / aggressive mode (and null the LRU).
|
|
77
|
+
this._initializeCaches();
|
|
66
78
|
|
|
67
79
|
// Initialize statistics
|
|
68
80
|
this._initializeStats();
|
|
@@ -71,12 +83,6 @@ class SmartCache {
|
|
|
71
83
|
this._lastHeapUsed = 0;
|
|
72
84
|
this._memoryPressure = false;
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
// NEW: Clear request cache
|
|
76
|
-
if (this._enableRequest) {
|
|
77
|
-
this.clearRequestCache();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
86
|
// Load persistent cache if enabled
|
|
81
87
|
if (this.options.enablePersistence) {
|
|
82
88
|
this._loadPersistentCache();
|
|
@@ -136,15 +142,29 @@ class SmartCache {
|
|
|
136
142
|
ttl: this.options.ttl * 2 // Patterns are more stable
|
|
137
143
|
});
|
|
138
144
|
|
|
139
|
-
// Response content cache - aggressive limits for high concurrency
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Response content cache - aggressive limits for high concurrency.
|
|
146
|
+
// Skip allocation entirely when very high concurrency / aggressive mode
|
|
147
|
+
// would disable it anyway (avoids a dead LRU sitting in memory).
|
|
148
|
+
const responseCacheDisabled = this.options.concurrency > 15 || this.options.aggressiveMode;
|
|
149
|
+
if (responseCacheDisabled) {
|
|
150
|
+
this.options.enableResponseCache = false;
|
|
151
|
+
this._enableResponse = false;
|
|
152
|
+
this.responseCache = null;
|
|
153
|
+
if (this._debug) {
|
|
154
|
+
console.log(formatLogMessage('debug',
|
|
155
|
+
`${SMART_CACHE_TAG} Response cache disabled for high concurrency (${this.options.concurrency})`
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const responseCacheSize = this._highConcurrency ? 50 : 200;
|
|
160
|
+
const responseCacheMemory = this._highConcurrency ? 20 * 1024 * 1024 : 50 * 1024 * 1024;
|
|
161
|
+
this.responseCache = new LRUCache({
|
|
162
|
+
max: responseCacheSize,
|
|
163
|
+
ttl: 1000 * 60 * 30, // 30 minutes for response content
|
|
164
|
+
maxSize: responseCacheMemory,
|
|
165
|
+
sizeCalculation: (value) => value.length
|
|
166
|
+
});
|
|
167
|
+
}
|
|
148
168
|
|
|
149
169
|
// NEW: Request-level cache for --cache-requests feature
|
|
150
170
|
if (this._enableRequest) {
|
|
@@ -163,19 +183,9 @@ class SmartCache {
|
|
|
163
183
|
return size;
|
|
164
184
|
}
|
|
165
185
|
});
|
|
166
|
-
|
|
167
|
-
if (this._debug) {
|
|
168
|
-
console.log(formatLogMessage('debug', `[SmartCache] Request cache initialized: ${this.options.requestCacheMaxSize} entries`));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
186
|
|
|
172
|
-
// Disable response cache entirely for very high concurrency
|
|
173
|
-
if (this.options.concurrency > 15 || this.options.aggressiveMode) {
|
|
174
|
-
this.options.enableResponseCache = false;
|
|
175
187
|
if (this._debug) {
|
|
176
|
-
console.log(formatLogMessage('debug',
|
|
177
|
-
`[SmartCache] Response cache disabled for high concurrency (${this.options.concurrency})`
|
|
178
|
-
));
|
|
188
|
+
console.log(formatLogMessage('debug', `${SMART_CACHE_TAG} Request cache initialized: ${this.options.requestCacheMaxSize} entries`));
|
|
179
189
|
}
|
|
180
190
|
}
|
|
181
191
|
|
|
@@ -194,9 +204,6 @@ class SmartCache {
|
|
|
194
204
|
|
|
195
205
|
// Regex compilation cache (bounded to prevent unbounded growth)
|
|
196
206
|
this.regexCache = new LRUCache({ max: 500 });
|
|
197
|
-
|
|
198
|
-
// Precompile the regex-stripping pattern (used in getCompiledRegex)
|
|
199
|
-
this._regexStripPattern = /^\/(.*)\/$/;
|
|
200
207
|
}
|
|
201
208
|
|
|
202
209
|
/**
|
|
@@ -245,7 +252,7 @@ class SmartCache {
|
|
|
245
252
|
if (this._debug) {
|
|
246
253
|
const age = Date.now() - cached.timestamp;
|
|
247
254
|
console.log(formatLogMessage('debug',
|
|
248
|
-
|
|
255
|
+
`${SMART_CACHE_TAG} Cache hit for ${domain} (age: ${Math.round(age/1000)}s)`
|
|
249
256
|
));
|
|
250
257
|
}
|
|
251
258
|
return true;
|
|
@@ -273,7 +280,7 @@ class SmartCache {
|
|
|
273
280
|
|
|
274
281
|
if (this._debug) {
|
|
275
282
|
console.log(formatLogMessage('debug',
|
|
276
|
-
|
|
283
|
+
`${SMART_CACHE_TAG} Marked ${domain} as processed`
|
|
277
284
|
));
|
|
278
285
|
}
|
|
279
286
|
}
|
|
@@ -349,16 +356,16 @@ class SmartCache {
|
|
|
349
356
|
_generateRequestCacheKey(url, options = EMPTY, normalizedUrl = null) {
|
|
350
357
|
const method = options.method || 'GET';
|
|
351
358
|
const normUrl = normalizedUrl || this._normalizeUrl(url);
|
|
352
|
-
const headers = options.headers ||
|
|
353
|
-
|
|
354
|
-
|
|
359
|
+
const headers = options.headers || EMPTY;
|
|
360
|
+
|
|
355
361
|
// Build header portion of key by direct concat (avoids JSON.stringify + intermediate array)
|
|
356
362
|
let headerKey = '';
|
|
357
|
-
for (
|
|
363
|
+
for (let i = 0; i < REQUEST_CACHE_KEY_HEADERS.length; i++) {
|
|
364
|
+
const header = REQUEST_CACHE_KEY_HEADERS[i];
|
|
358
365
|
const val = headers[header];
|
|
359
366
|
if (val) headerKey += header + '=' + val + '&';
|
|
360
367
|
}
|
|
361
|
-
|
|
368
|
+
|
|
362
369
|
return method + '|' + normUrl + '|' + headerKey;
|
|
363
370
|
}
|
|
364
371
|
|
|
@@ -382,14 +389,26 @@ class SmartCache {
|
|
|
382
389
|
if (cached) {
|
|
383
390
|
this.stats.requestCacheHits++;
|
|
384
391
|
if (this._debug) {
|
|
385
|
-
console.log(formatLogMessage('debug',
|
|
386
|
-
|
|
392
|
+
console.log(formatLogMessage('debug',
|
|
393
|
+
`${SMART_CACHE_TAG} Request cache hit for ${url.substring(0, 50)}... (${cached.status || 'unknown status'})`
|
|
387
394
|
));
|
|
388
395
|
}
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
396
|
+
// Return a shallow copy with hit-time metadata. Mutating the live LRU
|
|
397
|
+
// entry races on cacheAge with concurrent readers and lets a caller
|
|
398
|
+
// accidentally corrupt the shared body/headers. The copy costs an
|
|
399
|
+
// object allocation per hit -- worth it for the safety.
|
|
400
|
+
return {
|
|
401
|
+
timestamp: cached.timestamp,
|
|
402
|
+
status: cached.status,
|
|
403
|
+
statusText: cached.statusText,
|
|
404
|
+
headers: cached.headers,
|
|
405
|
+
body: cached.body,
|
|
406
|
+
url: cached.url,
|
|
407
|
+
originalUrl: cached.originalUrl,
|
|
408
|
+
requestOptions: cached.requestOptions,
|
|
409
|
+
fromCache: true,
|
|
410
|
+
cacheAge: Date.now() - cached.timestamp
|
|
411
|
+
};
|
|
393
412
|
}
|
|
394
413
|
|
|
395
414
|
this.stats.requestCacheMisses++;
|
|
@@ -446,16 +465,16 @@ class SmartCache {
|
|
|
446
465
|
body: result.body,
|
|
447
466
|
url: normalizedUrl,
|
|
448
467
|
originalUrl: url,
|
|
449
|
-
requestOptions: { method: method, headers: options.headers ||
|
|
468
|
+
requestOptions: { method: method, headers: options.headers || EMPTY }
|
|
450
469
|
});
|
|
451
470
|
|
|
452
471
|
if (this._debug) {
|
|
453
472
|
const bodySize = result.body ? result.body.length : 0;
|
|
454
473
|
console.log(formatLogMessage('debug',
|
|
455
|
-
|
|
474
|
+
`${SMART_CACHE_TAG} Cached request: ${normalizedUrl.substring(0, 50)}... (${result.status || 'unknown'}, ${Math.round(bodySize / 1024)}KB)`
|
|
456
475
|
));
|
|
457
476
|
if (normalizedUrl !== url) {
|
|
458
|
-
console.log(formatLogMessage('debug',
|
|
477
|
+
console.log(formatLogMessage('debug', `${SMART_CACHE_TAG} URL normalized: ${url} -> ${normalizedUrl}`));
|
|
459
478
|
}
|
|
460
479
|
}
|
|
461
480
|
}
|
|
@@ -473,7 +492,7 @@ class SmartCache {
|
|
|
473
492
|
|
|
474
493
|
if (this._debug) {
|
|
475
494
|
console.log(formatLogMessage('debug',
|
|
476
|
-
|
|
495
|
+
`${SMART_CACHE_TAG} Cleared request cache: ${clearedCount} entries removed`
|
|
477
496
|
));
|
|
478
497
|
}
|
|
479
498
|
|
|
@@ -552,15 +571,31 @@ class SmartCache {
|
|
|
552
571
|
|
|
553
572
|
this.stats.regexCompilations++;
|
|
554
573
|
try {
|
|
555
|
-
|
|
574
|
+
// Strip /.../ wrapper only when the pattern actually has it. String.replace
|
|
575
|
+
// allocates a result string even on no-match; the charCode check is free
|
|
576
|
+
// and skips the allocation for the common bare-pattern case. (47 == '/')
|
|
577
|
+
let source = pattern;
|
|
578
|
+
if (
|
|
579
|
+
pattern.length >= 2 &&
|
|
580
|
+
pattern.charCodeAt(0) === 47 &&
|
|
581
|
+
pattern.charCodeAt(pattern.length - 1) === 47
|
|
582
|
+
) {
|
|
583
|
+
source = pattern.slice(1, -1);
|
|
584
|
+
}
|
|
585
|
+
const regex = new RegExp(source);
|
|
556
586
|
this.regexCache.set(pattern, regex);
|
|
557
587
|
return regex;
|
|
558
588
|
} catch (err) {
|
|
559
589
|
if (this._debug) {
|
|
560
|
-
console.log(formatLogMessage('debug',
|
|
561
|
-
|
|
590
|
+
console.log(formatLogMessage('debug',
|
|
591
|
+
`${SMART_CACHE_TAG} Failed to compile regex: ${pattern}`
|
|
562
592
|
));
|
|
563
593
|
}
|
|
594
|
+
// Cache the failure so repeated lookups of the same bad pattern don't
|
|
595
|
+
// re-enter the try/throw/catch path (a JS throw is ~10x the cost of
|
|
596
|
+
// compiling the regex itself). The reader at line 561 uses `!== undefined`
|
|
597
|
+
// so a stored `null` correctly registers as a cache hit.
|
|
598
|
+
this.regexCache.set(pattern, null);
|
|
564
599
|
return null;
|
|
565
600
|
}
|
|
566
601
|
}
|
|
@@ -581,7 +616,7 @@ class SmartCache {
|
|
|
581
616
|
this.stats.patternHits++;
|
|
582
617
|
if (this._debug) {
|
|
583
618
|
console.log(formatLogMessage('debug',
|
|
584
|
-
|
|
619
|
+
`${SMART_CACHE_TAG} Pattern cache hit for ${url.substring(0, 50)}...`
|
|
585
620
|
));
|
|
586
621
|
}
|
|
587
622
|
return cached;
|
|
@@ -621,7 +656,7 @@ class SmartCache {
|
|
|
621
656
|
this.stats.responseHits++;
|
|
622
657
|
if (this._debug) {
|
|
623
658
|
console.log(formatLogMessage('debug',
|
|
624
|
-
|
|
659
|
+
`${SMART_CACHE_TAG} Response cache hit for ${url.substring(0, 50)}...`
|
|
625
660
|
));
|
|
626
661
|
}
|
|
627
662
|
return cached;
|
|
@@ -675,7 +710,7 @@ class SmartCache {
|
|
|
675
710
|
this.stats.netToolsHits++;
|
|
676
711
|
if (this._debug) {
|
|
677
712
|
console.log(formatLogMessage('debug',
|
|
678
|
-
|
|
713
|
+
`${SMART_CACHE_TAG} ${tool.toUpperCase()} cache hit for ${domain}`
|
|
679
714
|
));
|
|
680
715
|
}
|
|
681
716
|
return cached;
|
|
@@ -763,7 +798,7 @@ class SmartCache {
|
|
|
763
798
|
this.stats.memoryWarnings++;
|
|
764
799
|
if (this._debug) {
|
|
765
800
|
console.log(formatLogMessage('debug',
|
|
766
|
-
|
|
801
|
+
`${SMART_CACHE_TAG} Memory info: ${heapUsedMB}MB/${maxHeapMB}MB (${usagePercent.toFixed(1)}%)`
|
|
767
802
|
));
|
|
768
803
|
}
|
|
769
804
|
}
|
|
@@ -780,13 +815,13 @@ class SmartCache {
|
|
|
780
815
|
|
|
781
816
|
if (this._debug) {
|
|
782
817
|
console.log(formatLogMessage('debug',
|
|
783
|
-
|
|
818
|
+
`${SMART_CACHE_TAG} Memory ${level}: ${heapUsedMB}MB/${maxHeapMB}MB, performing cleanup...`
|
|
784
819
|
));
|
|
785
820
|
}
|
|
786
821
|
|
|
787
822
|
if (level === 'critical' || this._highConcurrency) {
|
|
788
823
|
// Aggressive cleanup - clear volatile caches
|
|
789
|
-
this.responseCache.clear();
|
|
824
|
+
if (this.responseCache) this.responseCache.clear();
|
|
790
825
|
this.patternCache.clear();
|
|
791
826
|
this.similarityCache.clear();
|
|
792
827
|
|
|
@@ -798,12 +833,12 @@ class SmartCache {
|
|
|
798
833
|
const currentSize = this.domainCache.size;
|
|
799
834
|
this.domainCache.clear();
|
|
800
835
|
if (this._debug) {
|
|
801
|
-
console.log(formatLogMessage('debug',
|
|
836
|
+
console.log(formatLogMessage('debug', `${SMART_CACHE_TAG} Cleared ${currentSize} domain cache entries`));
|
|
802
837
|
}
|
|
803
838
|
}
|
|
804
839
|
} else if (level === 'warning') {
|
|
805
840
|
// Moderate cleanup - clear largest cache
|
|
806
|
-
this.responseCache.clear();
|
|
841
|
+
if (this.responseCache) this.responseCache.clear();
|
|
807
842
|
|
|
808
843
|
// NEW: Clear request cache during warning cleanup if it's large
|
|
809
844
|
if (this._enableRequest && this.requestCache.size > this.options.requestCacheMaxSize * 0.8) {
|
|
@@ -863,7 +898,7 @@ class SmartCache {
|
|
|
863
898
|
netToolsHitRate: (netToolsHitRate * 100).toFixed(2) + '%',
|
|
864
899
|
domainCacheSize: this.domainCache.size,
|
|
865
900
|
patternCacheSize: this.patternCache.size,
|
|
866
|
-
responseCacheSize: this.responseCache.size,
|
|
901
|
+
responseCacheSize: this.responseCache ? this.responseCache.size : 0,
|
|
867
902
|
netToolsCacheSize: this.netToolsCache.size,
|
|
868
903
|
similarityCacheSize: this.similarityCache.size,
|
|
869
904
|
regexCacheSize: this.regexCache.size,
|
|
@@ -873,12 +908,14 @@ class SmartCache {
|
|
|
873
908
|
requestCacheMemoryMB: (this._enableRequest && this.requestCache) ?
|
|
874
909
|
Math.round((this.requestCache.calculatedSize || 0) / 1048576) : 0,
|
|
875
910
|
totalCacheEntries: this.domainCache.size + this.patternCache.size +
|
|
876
|
-
this.responseCache.size + this.netToolsCache.size +
|
|
911
|
+
(this.responseCache ? this.responseCache.size : 0) + this.netToolsCache.size +
|
|
877
912
|
this.similarityCache.size + this.regexCache.size + ((this._enableRequest && this.requestCache) ? this.requestCache.size : 0),
|
|
878
913
|
memoryUsageMB: Math.round(heapUsed / 1048576),
|
|
879
914
|
memoryMaxMB: Math.round(maxHeap / 1048576),
|
|
880
915
|
memoryUsagePercent: ((heapUsed / maxHeap) * 100).toFixed(1) + '%',
|
|
881
|
-
responseCacheMemoryMB:
|
|
916
|
+
responseCacheMemoryMB: this.responseCache
|
|
917
|
+
? Math.round((this.responseCache.calculatedSize || 0) / 1048576)
|
|
918
|
+
: 0
|
|
882
919
|
};
|
|
883
920
|
}
|
|
884
921
|
|
|
@@ -888,7 +925,7 @@ class SmartCache {
|
|
|
888
925
|
clear() {
|
|
889
926
|
this.domainCache.clear();
|
|
890
927
|
this.patternCache.clear();
|
|
891
|
-
this.responseCache.clear();
|
|
928
|
+
if (this.responseCache) this.responseCache.clear();
|
|
892
929
|
this.netToolsCache.clear();
|
|
893
930
|
this.similarityCache.clear();
|
|
894
931
|
this.regexCache.clear();
|
|
@@ -898,7 +935,7 @@ class SmartCache {
|
|
|
898
935
|
this._initializeStats();
|
|
899
936
|
|
|
900
937
|
if (this._debug) {
|
|
901
|
-
console.log(formatLogMessage('debug',
|
|
938
|
+
console.log(formatLogMessage('debug', `${SMART_CACHE_TAG} All caches cleared`));
|
|
902
939
|
}
|
|
903
940
|
}
|
|
904
941
|
|
|
@@ -909,7 +946,7 @@ class SmartCache {
|
|
|
909
946
|
_logMemorySkip(operation) {
|
|
910
947
|
if (this._debug) {
|
|
911
948
|
console.log(formatLogMessage('debug',
|
|
912
|
-
|
|
949
|
+
`${SMART_CACHE_TAG} Skipping ${operation} due to memory pressure`
|
|
913
950
|
));
|
|
914
951
|
}
|
|
915
952
|
}
|
|
@@ -936,28 +973,41 @@ class SmartCache {
|
|
|
936
973
|
// Validate cache age
|
|
937
974
|
if (data.timestamp && now - data.timestamp > 24 * 60 * 60 * 1000) {
|
|
938
975
|
if (this._debug) {
|
|
939
|
-
console.log(formatLogMessage('debug',
|
|
940
|
-
|
|
976
|
+
console.log(formatLogMessage('debug',
|
|
977
|
+
`${SMART_CACHE_TAG} Persistent cache too old, ignoring`
|
|
941
978
|
));
|
|
942
979
|
}
|
|
943
980
|
return;
|
|
944
981
|
}
|
|
945
982
|
|
|
946
|
-
// Load domain cache
|
|
983
|
+
// Load domain cache. Validate shape per-entry: a corrupted or
|
|
984
|
+
// version-mismatched on-disk file shouldn't be able to inject junk
|
|
985
|
+
// (missing timestamp, future timestamp, non-string keys, etc.).
|
|
947
986
|
if (data.domainCache && Array.isArray(data.domainCache)) {
|
|
948
987
|
const entries = data.domainCache;
|
|
988
|
+
const ttl = this.options.ttl;
|
|
949
989
|
for (let i = 0; i < entries.length; i++) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
990
|
+
const e = entries[i];
|
|
991
|
+
if (!Array.isArray(e) || e.length !== 2) continue;
|
|
992
|
+
const key = e[0];
|
|
993
|
+
const val = e[1];
|
|
994
|
+
if (typeof key !== 'string' || !val || typeof val !== 'object') continue;
|
|
995
|
+
const ts = val.timestamp;
|
|
996
|
+
if (typeof ts !== 'number' || ts > now || now - ts >= ttl) continue;
|
|
997
|
+
this.domainCache.set(key, val);
|
|
953
998
|
}
|
|
954
999
|
}
|
|
955
|
-
|
|
956
|
-
// Load nettools cache
|
|
1000
|
+
|
|
1001
|
+
// Load nettools cache (same shape validation).
|
|
957
1002
|
if (data.netToolsCache && Array.isArray(data.netToolsCache)) {
|
|
958
1003
|
const entries = data.netToolsCache;
|
|
959
1004
|
for (let i = 0; i < entries.length; i++) {
|
|
960
|
-
|
|
1005
|
+
const e = entries[i];
|
|
1006
|
+
if (!Array.isArray(e) || e.length !== 2) continue;
|
|
1007
|
+
const key = e[0];
|
|
1008
|
+
const val = e[1];
|
|
1009
|
+
if (typeof key !== 'string' || val === undefined) continue;
|
|
1010
|
+
this.netToolsCache.set(key, val);
|
|
961
1011
|
}
|
|
962
1012
|
}
|
|
963
1013
|
|
|
@@ -965,13 +1015,13 @@ class SmartCache {
|
|
|
965
1015
|
|
|
966
1016
|
if (this._debug) {
|
|
967
1017
|
console.log(formatLogMessage('debug',
|
|
968
|
-
|
|
1018
|
+
`${SMART_CACHE_TAG} Loaded persistent cache: ${this.domainCache.size} domains, ${this.netToolsCache.size} nettools`
|
|
969
1019
|
));
|
|
970
1020
|
}
|
|
971
1021
|
} catch (err) {
|
|
972
1022
|
if (this._debug) {
|
|
973
1023
|
console.log(formatLogMessage('debug',
|
|
974
|
-
|
|
1024
|
+
`${SMART_CACHE_TAG} Failed to load persistent cache: ${err.message}`
|
|
975
1025
|
));
|
|
976
1026
|
}
|
|
977
1027
|
}
|
|
@@ -987,7 +1037,7 @@ class SmartCache {
|
|
|
987
1037
|
if (this.saveInProgress) {
|
|
988
1038
|
this.pendingSave = true;
|
|
989
1039
|
if (this._debug) {
|
|
990
|
-
console.log(formatLogMessage('debug',
|
|
1040
|
+
console.log(formatLogMessage('debug', `${SMART_CACHE_TAG} Save in progress, marking pending...`));
|
|
991
1041
|
}
|
|
992
1042
|
return;
|
|
993
1043
|
}
|
|
@@ -1032,7 +1082,7 @@ class SmartCache {
|
|
|
1032
1082
|
if (writeErr) {
|
|
1033
1083
|
if (this._debug) {
|
|
1034
1084
|
console.log(formatLogMessage('debug',
|
|
1035
|
-
|
|
1085
|
+
`${SMART_CACHE_TAG} Failed to write cache temp file: ${writeErr.message}`
|
|
1036
1086
|
));
|
|
1037
1087
|
}
|
|
1038
1088
|
this.saveInProgress = false;
|
|
@@ -1043,14 +1093,14 @@ class SmartCache {
|
|
|
1043
1093
|
if (renameErr) {
|
|
1044
1094
|
if (this._debug) {
|
|
1045
1095
|
console.log(formatLogMessage('debug',
|
|
1046
|
-
|
|
1096
|
+
`${SMART_CACHE_TAG} Failed to rename cache file: ${renameErr.message}`
|
|
1047
1097
|
));
|
|
1048
1098
|
}
|
|
1049
1099
|
} else {
|
|
1050
1100
|
this.stats.persistenceSaves++;
|
|
1051
1101
|
if (this._debug) {
|
|
1052
1102
|
console.log(formatLogMessage('debug',
|
|
1053
|
-
|
|
1103
|
+
`${SMART_CACHE_TAG} Saved cache to disk: ${cacheFile}`
|
|
1054
1104
|
));
|
|
1055
1105
|
}
|
|
1056
1106
|
}
|
|
@@ -1069,7 +1119,7 @@ class SmartCache {
|
|
|
1069
1119
|
} catch (err) {
|
|
1070
1120
|
if (this._debug) {
|
|
1071
1121
|
console.log(formatLogMessage('debug',
|
|
1072
|
-
|
|
1122
|
+
`${SMART_CACHE_TAG} Failed to save cache: ${err.message}`
|
|
1073
1123
|
));
|
|
1074
1124
|
}
|
|
1075
1125
|
this.saveInProgress = false;
|
|
@@ -1131,7 +1181,7 @@ class SmartCache {
|
|
|
1131
1181
|
const errors = [];
|
|
1132
1182
|
|
|
1133
1183
|
if (!silent) {
|
|
1134
|
-
console.log(`\n
|
|
1184
|
+
console.log(`\n${messageColors.cleanup('Clearing cache...')}`);
|
|
1135
1185
|
}
|
|
1136
1186
|
|
|
1137
1187
|
// Try the directory first -- rmSync recursive handles all files inside
|
|
@@ -1200,13 +1250,13 @@ class SmartCache {
|
|
|
1200
1250
|
|
|
1201
1251
|
if (!silent) {
|
|
1202
1252
|
if (clearedItems > 0) {
|
|
1203
|
-
console.log(
|
|
1253
|
+
console.log(messageColors.cleanup(`Cache cleared: ${clearedItems} item(s), ${result.sizeMB}MB freed`));
|
|
1204
1254
|
} else {
|
|
1205
|
-
console.log(
|
|
1255
|
+
console.log(messageColors.cleanup(`No cache files found to clear`));
|
|
1206
1256
|
}
|
|
1207
|
-
|
|
1257
|
+
|
|
1208
1258
|
if (errors.length > 0) {
|
|
1209
|
-
console.warn(
|
|
1259
|
+
console.warn(messageColors.warn(`${errors.length} error(s) occurred during cache clearing`));
|
|
1210
1260
|
}
|
|
1211
1261
|
}
|
|
1212
1262
|
|
package/lib/socks-relay.js
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
const net = require('net');
|
|
21
21
|
const { SocksClient } = require('socks');
|
|
22
|
-
const { formatLogMessage } = require('./colorize');
|
|
22
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
23
|
+
const SOCKS_RELAY_TAG = messageColors.processing('[socks-relay]');
|
|
23
24
|
|
|
24
25
|
// upstreamKey -> { server, port, activeSockets:Set<net.Socket> }
|
|
25
26
|
const _relays = new Map();
|
|
@@ -63,7 +64,7 @@ function handleClient(client, upstream, forceDebug) {
|
|
|
63
64
|
handshakeTimer = setTimeout(() => {
|
|
64
65
|
if (phase !== 'piping') {
|
|
65
66
|
if (forceDebug) {
|
|
66
|
-
console.log(formatLogMessage('proxy',
|
|
67
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} handshake timeout (phase=${phase}) — closing`));
|
|
67
68
|
}
|
|
68
69
|
cleanup();
|
|
69
70
|
}
|
|
@@ -152,7 +153,7 @@ function handleClient(client, upstream, forceDebug) {
|
|
|
152
153
|
});
|
|
153
154
|
} catch (e) {
|
|
154
155
|
if (forceDebug) {
|
|
155
|
-
console.log(formatLogMessage('proxy',
|
|
156
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} upstream connect failed (${host}:${port}): ${e.message}`));
|
|
156
157
|
}
|
|
157
158
|
failReply(client, 0x05); // connection refused
|
|
158
159
|
return cleanup();
|
|
@@ -178,7 +179,7 @@ function handleClient(client, upstream, forceDebug) {
|
|
|
178
179
|
}
|
|
179
180
|
} catch (e) {
|
|
180
181
|
if (forceDebug) {
|
|
181
|
-
console.log(formatLogMessage('proxy',
|
|
182
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} handler error: ${e.message}`));
|
|
182
183
|
}
|
|
183
184
|
cleanup();
|
|
184
185
|
}
|
|
@@ -224,7 +225,7 @@ async function ensureRelay(upstream, forceDebug = false) {
|
|
|
224
225
|
server.removeListener('error', onErr);
|
|
225
226
|
// Keep a listener so a late server error doesn't crash the process.
|
|
226
227
|
server.on('error', (e) => {
|
|
227
|
-
if (forceDebug) console.log(formatLogMessage('proxy',
|
|
228
|
+
if (forceDebug) console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} server error: ${e.message}`));
|
|
228
229
|
});
|
|
229
230
|
resolve();
|
|
230
231
|
});
|
|
@@ -233,7 +234,7 @@ async function ensureRelay(upstream, forceDebug = false) {
|
|
|
233
234
|
const port = server.address().port;
|
|
234
235
|
_relays.set(key, { server, port, activeSockets });
|
|
235
236
|
if (forceDebug) {
|
|
236
|
-
console.log(formatLogMessage('proxy',
|
|
237
|
+
console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} 127.0.0.1:${port} -> ${upstream.host}:${upstream.port} (auth user "${upstream.username}")`));
|
|
237
238
|
}
|
|
238
239
|
return port;
|
|
239
240
|
}
|
|
@@ -258,7 +259,7 @@ async function closeAllRelays(forceDebug = false) {
|
|
|
258
259
|
await new Promise((res) => {
|
|
259
260
|
try { r.server.close(() => res()); } catch (_) { res(); }
|
|
260
261
|
});
|
|
261
|
-
if (forceDebug) console.log(formatLogMessage('proxy',
|
|
262
|
+
if (forceDebug) console.log(formatLogMessage('proxy', `${SOCKS_RELAY_TAG} closed relay for ${key}`));
|
|
262
263
|
}
|
|
263
264
|
_relays.clear();
|
|
264
265
|
}
|