@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/redirect.js
CHANGED
|
@@ -165,8 +165,14 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
165
165
|
// Inject JavaScript redirect detection
|
|
166
166
|
await jsRedirectDetector();
|
|
167
167
|
|
|
168
|
-
if (forceDebug
|
|
169
|
-
|
|
168
|
+
if (forceDebug) {
|
|
169
|
+
// Avoid Object.keys allocation just to check emptiness — a for...in
|
|
170
|
+
// early-exit on the first own key is enough.
|
|
171
|
+
let hasOpts = false;
|
|
172
|
+
for (const _k in gotoOptions) { hasOpts = true; break; }
|
|
173
|
+
if (hasOpts) {
|
|
174
|
+
console.log(formatLogMessage('debug', `Using goto options: ${JSON.stringify(gotoOptions)}`));
|
|
175
|
+
}
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
// Initial navigation. Puppeteer's page.goto returns the response for the
|
|
@@ -184,7 +190,7 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
184
190
|
} catch (_) { /* response disposed or detached — fine, stays null */ }
|
|
185
191
|
}
|
|
186
192
|
|
|
187
|
-
if (response && response.url() !== currentUrl) {
|
|
193
|
+
if (response && response.url() !== currentUrl && !response.url().startsWith('chrome-error://')) {
|
|
188
194
|
// Check redirect limit before adding
|
|
189
195
|
if (redirectChain.length >= maxRedirects) {
|
|
190
196
|
if (forceDebug) {
|
|
@@ -192,12 +198,12 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
192
198
|
}
|
|
193
199
|
finalUrl = currentUrl; // Keep original URL
|
|
194
200
|
} else {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
201
|
+
finalUrl = response.url();
|
|
202
|
+
redirected = true;
|
|
203
|
+
if (!redirectChain.includes(finalUrl)) redirectChain.push(finalUrl);
|
|
198
204
|
}
|
|
199
205
|
if (forceDebug) {
|
|
200
|
-
console.log(formatLogMessage('debug', `HTTP redirect detected: ${currentUrl}
|
|
206
|
+
console.log(formatLogMessage('debug', `HTTP redirect detected: ${currentUrl} -> ${finalUrl}`));
|
|
201
207
|
}
|
|
202
208
|
}
|
|
203
209
|
|
|
@@ -223,9 +229,11 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
223
229
|
};
|
|
224
230
|
});
|
|
225
231
|
|
|
226
|
-
// Check if URL changed (either through JS redirect or automatic redirect)
|
|
232
|
+
// Check if URL changed (either through JS redirect or automatic redirect).
|
|
233
|
+
// Skip chrome-error://* — it's Puppeteer's landing page on DNS/connection
|
|
234
|
+
// failure and adding it to the chain produces bogus intermediate hops.
|
|
227
235
|
const currentPageUrl = page.url();
|
|
228
|
-
if (currentPageUrl && currentPageUrl !== finalUrl && !redirectChain.includes(currentPageUrl)) {
|
|
236
|
+
if (currentPageUrl && currentPageUrl !== finalUrl && !currentPageUrl.startsWith('chrome-error://') && !redirectChain.includes(currentPageUrl)) {
|
|
229
237
|
// Check redirect limit before adding
|
|
230
238
|
if (redirectChain.length >= maxRedirects) {
|
|
231
239
|
if (forceDebug) {
|
|
@@ -275,21 +283,23 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
275
283
|
await detectCommonJSRedirects(page, forceDebug, formatLogMessage);
|
|
276
284
|
}
|
|
277
285
|
|
|
278
|
-
// Final URL check
|
|
286
|
+
// Final URL check. Same chrome-error://* skip as the earlier branches —
|
|
287
|
+
// a navigation that ended in a chrome-error landing shouldn't be treated
|
|
288
|
+
// as the "final" URL of a successful redirect chain.
|
|
279
289
|
const finalPageUrl = page.url();
|
|
280
|
-
if (finalPageUrl && finalPageUrl !== finalUrl) {
|
|
290
|
+
if (finalPageUrl && finalPageUrl !== finalUrl && !finalPageUrl.startsWith('chrome-error://')) {
|
|
281
291
|
// Check redirect limit before final update
|
|
282
292
|
if (redirectChain.length >= maxRedirects) {
|
|
283
293
|
if (forceDebug) {
|
|
284
294
|
console.log(formatLogMessage('debug', `Maximum redirects (${maxRedirects}) reached, keeping current finalUrl`));
|
|
285
295
|
}
|
|
286
296
|
} else {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
297
|
+
finalUrl = finalPageUrl;
|
|
298
|
+
redirected = true;
|
|
299
|
+
if (!redirectChain.includes(finalUrl)) {
|
|
300
|
+
redirectChain.push(finalUrl);
|
|
301
|
+
}
|
|
291
302
|
}
|
|
292
|
-
}
|
|
293
303
|
}
|
|
294
304
|
|
|
295
305
|
} finally {
|
|
@@ -298,21 +308,19 @@ async function navigateWithRedirectHandling(page, currentUrl, siteConfig, gotoOp
|
|
|
298
308
|
|
|
299
309
|
// Log redirect summary
|
|
300
310
|
if (redirected && forceDebug) {
|
|
301
|
-
console.log(formatLogMessage('debug', `Redirect chain: ${redirectChain.join('
|
|
311
|
+
console.log(formatLogMessage('debug', `Redirect chain: ${redirectChain.join(' -> ')}`));
|
|
302
312
|
}
|
|
303
313
|
|
|
304
|
-
// Extract redirect domains
|
|
305
|
-
|
|
314
|
+
// Extract intermediate redirect domains (exclude the final entry). Single
|
|
315
|
+
// loop instead of slice().map().filter() — three array allocations down to
|
|
316
|
+
// one push-loop. redirectChain is bounded at maxRedirects (default 10).
|
|
317
|
+
const redirectDomains = [];
|
|
306
318
|
if (redirected && redirectChain.length > 1) {
|
|
307
|
-
|
|
308
|
-
const intermediateDomains = redirectChain.slice(0, -1).map(url => {
|
|
319
|
+
for (let i = 0; i < redirectChain.length - 1; i++) {
|
|
309
320
|
try {
|
|
310
|
-
|
|
311
|
-
} catch {
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
}).filter(Boolean);
|
|
315
|
-
redirectDomains = intermediateDomains;
|
|
321
|
+
redirectDomains.push(new URL(redirectChain[i]).hostname);
|
|
322
|
+
} catch (_) { /* skip malformed entries */ }
|
|
323
|
+
}
|
|
316
324
|
}
|
|
317
325
|
|
|
318
326
|
return { finalUrl, redirected, redirectChain, originalUrl: currentUrl, redirectDomains, httpStatus, cfRay };
|
|
@@ -410,13 +418,21 @@ async function handleRedirectTimeout(page, originalUrl, error, safeGetDomain, fo
|
|
|
410
418
|
|
|
411
419
|
try {
|
|
412
420
|
const currentPageUrl = page.url();
|
|
413
|
-
|
|
421
|
+
// Skip chrome-error://* the same way navigateWithRedirectHandling does:
|
|
422
|
+
// a DNS/connection-failure landing isn't a "partial redirect recovery",
|
|
423
|
+
// and safeGetDomain('chrome-error://chromewebdata/') returns
|
|
424
|
+
// 'chromewebdata', which would otherwise differ from the original
|
|
425
|
+
// domain and falsely report success here.
|
|
426
|
+
if (currentPageUrl
|
|
427
|
+
&& currentPageUrl !== 'about:blank'
|
|
428
|
+
&& !currentPageUrl.startsWith('chrome-error://')
|
|
429
|
+
&& currentPageUrl !== originalUrl) {
|
|
414
430
|
const originalDomain = safeGetDomain(originalUrl);
|
|
415
431
|
const currentDomain = safeGetDomain(currentPageUrl);
|
|
416
|
-
|
|
432
|
+
|
|
417
433
|
if (originalDomain !== currentDomain) {
|
|
418
434
|
if (forceDebug) {
|
|
419
|
-
console.log(formatLogMessage('debug', `Partial redirect timeout recovered: ${originalDomain}
|
|
435
|
+
console.log(formatLogMessage('debug', `Partial redirect timeout recovered: ${originalDomain} -> ${currentDomain}`));
|
|
420
436
|
}
|
|
421
437
|
return { success: true, finalUrl: currentPageUrl, redirected: true };
|
|
422
438
|
}
|
package/lib/referrer.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// === Referrer Header Generation Module ===
|
|
2
2
|
// This module handles generation of referrer headers for different traffic simulation modes
|
|
3
3
|
|
|
4
|
+
const { formatLogMessage, messageColors } = require('./colorize');
|
|
5
|
+
|
|
6
|
+
// Precomputed colored '[referrer]' subsystem prefix — matches the project
|
|
7
|
+
// convention used by other modules (flowproxy/cloudflare/smart-cache/etc.).
|
|
8
|
+
const REFERRER_TAG = messageColors.processing('[referrer]');
|
|
9
|
+
|
|
4
10
|
/**
|
|
5
11
|
* Performance utility: Get random element from array
|
|
6
12
|
* Reduces code duplication and improves readability
|
|
@@ -78,7 +84,10 @@ const REFERRER_COLLECTIONS = Object.freeze({
|
|
|
78
84
|
* @returns {string} Selected search term
|
|
79
85
|
*/
|
|
80
86
|
function generateSearchTerm(customTerms, context = null) {
|
|
81
|
-
|
|
87
|
+
// Array.isArray guard belt-and-braces: validateReferrerConfig now blocks
|
|
88
|
+
// non-array search_terms at config load, but a direct internal caller
|
|
89
|
+
// could still pass a non-array and trigger a TypeError on .length.
|
|
90
|
+
if (Array.isArray(customTerms) && customTerms.length > 0) {
|
|
82
91
|
return getRandomElement(customTerms);
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -107,7 +116,7 @@ function generateSearchReferrer(searchTerms, context, forceDebug) {
|
|
|
107
116
|
const referrerUrl = randomEngine + encodeURIComponent(searchTerm);
|
|
108
117
|
|
|
109
118
|
if (forceDebug) {
|
|
110
|
-
console.log(
|
|
119
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Generated search referrer: ${referrerUrl} (engine: ${randomEngine.split('//')[1].split('/')[0]}, term: "${searchTerm}")`));
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
return referrerUrl;
|
|
@@ -122,7 +131,7 @@ function generateSocialMediaReferrer(forceDebug) {
|
|
|
122
131
|
const randomSocial = getRandomElement(REFERRER_COLLECTIONS.SOCIAL_MEDIA);
|
|
123
132
|
|
|
124
133
|
if (forceDebug) {
|
|
125
|
-
console.log(
|
|
134
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Generated social media referrer: ${randomSocial}`));
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
return randomSocial;
|
|
@@ -137,7 +146,7 @@ function generateNewsReferrer(forceDebug) {
|
|
|
137
146
|
const randomNews = getRandomElement(REFERRER_COLLECTIONS.NEWS_SITES);
|
|
138
147
|
|
|
139
148
|
if (forceDebug) {
|
|
140
|
-
console.log(
|
|
149
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Generated news referrer: ${randomNews}`));
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
return randomNews;
|
|
@@ -179,35 +188,44 @@ function shouldDisableReferrer(targetUrl, disableList, forceDebug = false) {
|
|
|
179
188
|
|
|
180
189
|
for (const disablePattern of disableList) {
|
|
181
190
|
if (typeof disablePattern !== 'string') continue;
|
|
182
|
-
|
|
191
|
+
|
|
183
192
|
// Fast check: Exact URL match (no parsing needed)
|
|
184
193
|
if (targetUrl === disablePattern) {
|
|
185
|
-
if (forceDebug) console.log(
|
|
194
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Referrer disabled for exact match: ${targetUrl}`));
|
|
186
195
|
return true;
|
|
187
196
|
}
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} catch (
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
|
|
198
|
+
// Resolve pattern to a hostname — full URL patterns ('https://example.com')
|
|
199
|
+
// and bare-hostname patterns ('example.com') both end up running through
|
|
200
|
+
// the same suffix-match logic so they behave identically. Previously
|
|
201
|
+
// full-URL patterns only did exact hostname equality (no subdomain
|
|
202
|
+
// match), while bare-hostname patterns did suffix match — same user
|
|
203
|
+
// intent, different result depending on string form.
|
|
204
|
+
let patternHostname = null;
|
|
205
|
+
if (disablePattern.includes('/')) {
|
|
206
|
+
try { patternHostname = new URL(disablePattern).hostname; } catch (_) { /* fall through */ }
|
|
207
|
+
} else {
|
|
208
|
+
patternHostname = disablePattern;
|
|
200
209
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
210
|
+
|
|
211
|
+
if (targetUrlParsed && patternHostname) {
|
|
212
|
+
const p = patternHostname.toLowerCase();
|
|
213
|
+
const h = targetHostname.toLowerCase();
|
|
214
|
+
if (h === p || h.endsWith('.' + p)) {
|
|
215
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Referrer disabled for hostname match: ${p} matches ${h}`));
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
} else if (!targetUrlParsed) {
|
|
219
|
+
// Pathological fallback: target URL didn't parse. Substring match
|
|
220
|
+
// as last resort. Shouldn't fire in practice — we only call this
|
|
221
|
+
// on URLs we're about to navigate to.
|
|
204
222
|
if (targetUrl.includes(disablePattern)) {
|
|
205
|
-
if (forceDebug) console.log(
|
|
223
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Referrer disabled for pattern match (unparseable target): ${disablePattern}`));
|
|
206
224
|
return true;
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
227
|
}
|
|
210
|
-
|
|
228
|
+
|
|
211
229
|
return false;
|
|
212
230
|
}
|
|
213
231
|
|
|
@@ -218,94 +236,94 @@ function shouldDisableReferrer(targetUrl, disableList, forceDebug = false) {
|
|
|
218
236
|
* @returns {string} Generated referrer URL or empty string
|
|
219
237
|
*/
|
|
220
238
|
function generateReferrerUrl(referrerConfig, forceDebug = false) {
|
|
221
|
-
try
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
// No top-level try/catch — nothing in here throws synchronously.
|
|
240
|
+
// typeof / Array.isArray / object property access / string concat /
|
|
241
|
+
// console.log / the helper functions are all non-throwing. The old
|
|
242
|
+
// try/catch was unreachable defensive scaffolding.
|
|
243
|
+
|
|
244
|
+
// Handle simple string URLs
|
|
245
|
+
if (typeof referrerConfig === 'string') {
|
|
246
|
+
const url = isValidUrl(referrerConfig) ? referrerConfig : '';
|
|
247
|
+
if (forceDebug && url) {
|
|
248
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Using direct referrer URL: ${url}`));
|
|
249
|
+
} else if (forceDebug && !url) {
|
|
250
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Invalid referrer URL provided: ${referrerConfig}`));
|
|
231
251
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
return url;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Handle arrays - pick random URL
|
|
256
|
+
if (Array.isArray(referrerConfig)) {
|
|
257
|
+
if (referrerConfig.length === 0) {
|
|
258
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Empty referrer array provided`));
|
|
259
|
+
return '';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const randomUrl = getRandomElement(referrerConfig);
|
|
263
|
+
const url = isValidUrl(randomUrl) ? randomUrl : '';
|
|
264
|
+
|
|
265
|
+
if (forceDebug) {
|
|
266
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Selected referrer from array (${referrerConfig.length} options): ${url || 'invalid URL'}`));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return url;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Handle object modes
|
|
273
|
+
if (typeof referrerConfig === 'object' && referrerConfig !== null && referrerConfig.mode) {
|
|
274
|
+
switch (referrerConfig.mode) {
|
|
275
|
+
case 'random_search': {
|
|
276
|
+
const searchTerms = referrerConfig.search_terms;
|
|
277
|
+
const context = referrerConfig.context; // Optional context hint
|
|
278
|
+
return generateSearchReferrer(searchTerms, context, forceDebug);
|
|
238
279
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (forceDebug) {
|
|
244
|
-
console.log(`[debug] Selected referrer from array (${referrerConfig.length} options): ${url || 'invalid URL'}`);
|
|
280
|
+
|
|
281
|
+
case 'social_media': {
|
|
282
|
+
return generateSocialMediaReferrer(forceDebug);
|
|
245
283
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return generateSocialMediaReferrer(forceDebug);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
case 'news_sites': {
|
|
264
|
-
return generateNewsReferrer(forceDebug);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
case 'direct_navigation': {
|
|
268
|
-
if (forceDebug) console.log(`[debug] Using direct navigation (no referrer)`);
|
|
269
|
-
return '';
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
case 'custom': {
|
|
273
|
-
const url = isValidUrl(referrerConfig.url) ? referrerConfig.url : '';
|
|
274
|
-
if (forceDebug) {
|
|
275
|
-
console.log(`[debug] Using custom referrer URL: ${url || 'invalid URL provided'}`);
|
|
276
|
-
}
|
|
277
|
-
return url;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
case 'mixed': {
|
|
281
|
-
// Randomly choose between different referrer types
|
|
282
|
-
const modes = ['random_search', 'social_media', 'news_sites'];
|
|
283
|
-
const randomMode = getRandomElement(modes);
|
|
284
|
-
|
|
285
|
-
if (forceDebug) console.log(`[debug] Mixed mode selected: ${randomMode}`);
|
|
286
|
-
|
|
287
|
-
const mixedConfig = { mode: randomMode };
|
|
288
|
-
if (randomMode === 'random_search' && referrerConfig.search_terms) {
|
|
289
|
-
mixedConfig.search_terms = referrerConfig.search_terms;
|
|
290
|
-
mixedConfig.context = referrerConfig.context;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return generateReferrerUrl(mixedConfig, forceDebug);
|
|
284
|
+
|
|
285
|
+
case 'news_sites': {
|
|
286
|
+
return generateNewsReferrer(forceDebug);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
case 'direct_navigation': {
|
|
290
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Using direct navigation (no referrer)`));
|
|
291
|
+
return '';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case 'custom': {
|
|
295
|
+
const url = isValidUrl(referrerConfig.url) ? referrerConfig.url : '';
|
|
296
|
+
if (forceDebug) {
|
|
297
|
+
console.log(formatLogMessage('debug', `${REFERRER_TAG} Using custom referrer URL: ${url || 'invalid URL provided'}`));
|
|
294
298
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
return url;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case 'mixed': {
|
|
303
|
+
// Randomly choose between different referrer types
|
|
304
|
+
const modes = ['random_search', 'social_media', 'news_sites'];
|
|
305
|
+
const randomMode = getRandomElement(modes);
|
|
306
|
+
|
|
307
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Mixed mode selected: ${randomMode}`));
|
|
308
|
+
|
|
309
|
+
const mixedConfig = { mode: randomMode };
|
|
310
|
+
if (randomMode === 'random_search' && referrerConfig.search_terms) {
|
|
311
|
+
mixedConfig.search_terms = referrerConfig.search_terms;
|
|
312
|
+
mixedConfig.context = referrerConfig.context;
|
|
299
313
|
}
|
|
314
|
+
|
|
315
|
+
return generateReferrerUrl(mixedConfig, forceDebug);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
default: {
|
|
319
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Unknown referrer mode: ${referrerConfig.mode}`));
|
|
320
|
+
return '';
|
|
300
321
|
}
|
|
301
322
|
}
|
|
302
|
-
|
|
303
|
-
if (forceDebug) console.log(`[debug] Invalid referrer configuration type: ${typeof referrerConfig}`);
|
|
304
|
-
return '';
|
|
305
|
-
} catch (err) {
|
|
306
|
-
if (forceDebug) console.log(`[debug] Referrer generation failed: ${err.message}`);
|
|
307
|
-
return '';
|
|
308
323
|
}
|
|
324
|
+
|
|
325
|
+
if (forceDebug) console.log(formatLogMessage('debug', `${REFERRER_TAG} Invalid referrer configuration type: ${typeof referrerConfig}`));
|
|
326
|
+
return '';
|
|
309
327
|
}
|
|
310
328
|
|
|
311
329
|
/**
|
|
@@ -350,30 +368,24 @@ function validateReferrerConfig(referrerConfig) {
|
|
|
350
368
|
return result;
|
|
351
369
|
}
|
|
352
370
|
|
|
353
|
-
// Validate arrays
|
|
371
|
+
// Validate arrays — every item gets checked. The old code spot-checked
|
|
372
|
+
// only first and last when length > 10 "for performance", but config
|
|
373
|
+
// validation runs ONCE at startup and referrer arrays are tiny; the
|
|
374
|
+
// perf savings were imaginary, the correctness gap (items 2..N-1 never
|
|
375
|
+
// validated, typo'd URLs slipping through) was real.
|
|
354
376
|
if (Array.isArray(referrerConfig)) {
|
|
355
377
|
if (referrerConfig.length === 0) {
|
|
356
378
|
result.warnings.push('Empty referrer array will result in no referrer');
|
|
357
379
|
return result;
|
|
358
380
|
}
|
|
359
|
-
|
|
360
|
-
// Fast validation: check only first and last items if array is large
|
|
361
|
-
const itemsToCheck = referrerConfig.length > 10
|
|
362
|
-
? [referrerConfig[0], referrerConfig[referrerConfig.length - 1]]
|
|
363
|
-
: referrerConfig;
|
|
364
|
-
|
|
365
|
-
itemsToCheck.forEach((url, index) => {
|
|
366
|
-
if (!isValidUrl(url)) {
|
|
367
|
-
const actualIndex = itemsToCheck === referrerConfig ? index : (index === 0 ? 0 : referrerConfig.length - 1);
|
|
368
|
-
result.errors.push(`Array item ${actualIndex} is not a valid HTTP/HTTPS URL: ${url}`);
|
|
369
|
-
result.isValid = false;
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
381
|
|
|
373
|
-
|
|
374
|
-
|
|
382
|
+
for (let i = 0; i < referrerConfig.length; i++) {
|
|
383
|
+
if (!isValidUrl(referrerConfig[i])) {
|
|
384
|
+
result.errors.push(`Array item ${i} is not a valid HTTP/HTTPS URL: ${referrerConfig[i]}`);
|
|
385
|
+
result.isValid = false;
|
|
386
|
+
}
|
|
375
387
|
}
|
|
376
|
-
|
|
388
|
+
|
|
377
389
|
return result;
|
|
378
390
|
}
|
|
379
391
|
|
|
@@ -406,11 +418,25 @@ function validateReferrerConfig(referrerConfig) {
|
|
|
406
418
|
break;
|
|
407
419
|
|
|
408
420
|
case 'random_search':
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
421
|
+
// Upgrade from warning to error: generateSearchTerm reads
|
|
422
|
+
// customTerms.length, which throws TypeError on a non-array
|
|
423
|
+
// (e.g. a single string the user expected to be auto-wrapped).
|
|
424
|
+
// Letting this slip past validation produces a runtime crash
|
|
425
|
+
// mid-scan instead of a clean config-load failure.
|
|
426
|
+
if (referrerConfig.search_terms !== undefined && !Array.isArray(referrerConfig.search_terms)) {
|
|
427
|
+
result.isValid = false;
|
|
428
|
+
result.errors.push(`search_terms must be an array of strings (got ${typeof referrerConfig.search_terms})`);
|
|
429
|
+
} else if (Array.isArray(referrerConfig.search_terms)) {
|
|
430
|
+
if (referrerConfig.search_terms.length === 0) {
|
|
431
|
+
result.warnings.push('Empty search_terms array will use default terms');
|
|
432
|
+
} else {
|
|
433
|
+
for (let i = 0; i < referrerConfig.search_terms.length; i++) {
|
|
434
|
+
if (typeof referrerConfig.search_terms[i] !== 'string') {
|
|
435
|
+
result.isValid = false;
|
|
436
|
+
result.errors.push(`search_terms[${i}] must be a string (got ${typeof referrerConfig.search_terms[i]})`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
414
440
|
}
|
|
415
441
|
break;
|
|
416
442
|
}
|
|
@@ -464,47 +490,14 @@ function validateReferrerDisable(referrerDisable) {
|
|
|
464
490
|
return result;
|
|
465
491
|
}
|
|
466
492
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
'random_search': 'Generate random search engine referrers with customizable search terms',
|
|
474
|
-
'social_media': 'Use random social media platform referrers',
|
|
475
|
-
'news_sites': 'Use random news website referrers',
|
|
476
|
-
'direct_navigation': 'No referrer (simulates direct URL entry)',
|
|
477
|
-
'custom': 'Use a specific custom referrer URL',
|
|
478
|
-
'mixed': 'Randomly mix different referrer types for varied traffic simulation'
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Gets statistics about available referrer collections
|
|
484
|
-
* @returns {Object} Statistics about referrer collections
|
|
485
|
-
*/
|
|
486
|
-
function getReferrerStats() {
|
|
487
|
-
return {
|
|
488
|
-
searchEngines: REFERRER_COLLECTIONS.SEARCH_ENGINES.length,
|
|
489
|
-
socialMedia: REFERRER_COLLECTIONS.SOCIAL_MEDIA.length,
|
|
490
|
-
newsSites: REFERRER_COLLECTIONS.NEWS_SITES.length,
|
|
491
|
-
defaultSearchTerms: REFERRER_COLLECTIONS.DEFAULT_SEARCH_TERMS.length,
|
|
492
|
-
ecommerceTerms: REFERRER_COLLECTIONS.ECOMMERCE_TERMS.length,
|
|
493
|
-
techTerms: REFERRER_COLLECTIONS.TECH_TERMS.length
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
493
|
+
// Public surface used by nwss.js. Internal helpers
|
|
494
|
+
// (generateReferrerUrl, shouldDisableReferrer, generateSearch/Social/News
|
|
495
|
+
// Referrer, isValidUrl, REFERRER_COLLECTIONS) stay module-private —
|
|
496
|
+
// the old export list included nine names no caller imported, plus two
|
|
497
|
+
// dead helper functions (getReferrerModes, getReferrerStats) that have
|
|
498
|
+
// been removed entirely.
|
|
497
499
|
module.exports = {
|
|
498
|
-
generateReferrerUrl,
|
|
499
500
|
getReferrerForUrl,
|
|
500
|
-
shouldDisableReferrer,
|
|
501
501
|
validateReferrerConfig,
|
|
502
|
-
validateReferrerDisable
|
|
503
|
-
getReferrerModes,
|
|
504
|
-
getReferrerStats,
|
|
505
|
-
generateSearchReferrer,
|
|
506
|
-
generateSocialMediaReferrer,
|
|
507
|
-
generateNewsReferrer,
|
|
508
|
-
isValidUrl,
|
|
509
|
-
REFERRER_COLLECTIONS
|
|
502
|
+
validateReferrerDisable
|
|
510
503
|
};
|