@fanboynz/network-scanner 1.0.68 → 1.0.69

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/nwss.js CHANGED
@@ -1,4 +1,4 @@
1
- // === Network scanner script (nwss.js) v1.0.68 ===
1
+ // === Network scanner script (nwss.js) v1.0.69 ===
2
2
 
3
3
  // puppeteer for browser automation, fs for file system operations, psl for domain parsing.
4
4
  // const pLimit = require('p-limit'); // Will be dynamically imported
@@ -44,6 +44,37 @@ function fastTimeout(ms) {
44
44
  return new Promise(resolve => setTimeout(resolve, ms));
45
45
  }
46
46
 
47
+ // --- Configuration Constants ---
48
+ const TIMEOUTS = {
49
+ DEFAULT_PAGE: 30000,
50
+ DEFAULT_NAVIGATION: 25000,
51
+ DEFAULT_NAVIGATION_REDUCED: 20000, // Reduced timeout for faster failures
52
+ DEFAULT_PAGE_REDUCED: 15000, // Reduced page timeout
53
+ FRAME_LOAD_WAIT: 2000,
54
+ NETWORK_IDLE: 2000,
55
+ NETWORK_IDLE_MAX: 10000,
56
+ FAST_SITE_THRESHOLD: 15000,
57
+ EMERGENCY_RESTART_DELAY: 2000,
58
+ BROWSER_STABILIZE_DELAY: 1000,
59
+ CURL_HANDLER_DELAY: 3000,
60
+ PROTOCOL_TIMEOUT: 60000,
61
+ REDIRECT_JS_TIMEOUT: 5000
62
+ };
63
+
64
+ const CACHE_LIMITS = {
65
+ DISK_CACHE_SIZE: 52428800, // 50MB
66
+ MEDIA_CACHE_SIZE: 52428800, // 50MB
67
+ DEFAULT_CACHE_PATH: '.cache',
68
+ DEFAULT_MAX_SIZE: 5000
69
+ };
70
+
71
+ const CONCURRENCY_LIMITS = {
72
+ MIN: 1,
73
+ MAX: 50,
74
+ DEFAULT: 6,
75
+ HIGH_CONCURRENCY_THRESHOLD: 12 // Auto-enable aggressive caching above this
76
+ };
77
+
47
78
  /**
48
79
  * Detects the installed Puppeteer version dynamically
49
80
  * @returns {Object} Version info and compatibility settings
@@ -87,7 +118,7 @@ const { navigateWithRedirectHandling, handleRedirectTimeout } = require('./lib/r
87
118
  const { monitorBrowserHealth, isBrowserHealthy } = require('./lib/browserhealth');
88
119
 
89
120
  // --- Script Configuration & Constants ---
90
- const VERSION = '1.0.68'; // Script version
121
+ const VERSION = '1.0.69'; // Script version
91
122
 
92
123
  // get startTime
93
124
  const startTime = Date.now();
@@ -278,7 +309,7 @@ if (clearCache && !dryRunMode) {
278
309
  clearPersistentCache({
279
310
  silent: silentMode,
280
311
  forceDebug,
281
- cachePath: '.cache' // Default path, will be updated after config loads if needed
312
+ cachePath: CACHE_LIMITS.DEFAULT_CACHE_PATH // Default path, will be updated after config loads if needed
282
313
  });
283
314
  }
284
315
 
@@ -568,24 +599,24 @@ const {
568
599
  const MAX_CONCURRENT_SITES = (() => {
569
600
  // Check command line argument first
570
601
  if (maxConcurrentSites !== null) {
571
- if (maxConcurrentSites > 0 && maxConcurrentSites <= 50) {
602
+ if (maxConcurrentSites >= CONCURRENCY_LIMITS.MIN && maxConcurrentSites <= CONCURRENCY_LIMITS.MAX) {
572
603
  if (forceDebug) console.log(formatLogMessage('debug', `Using command line max_concurrent_sites: ${maxConcurrentSites}`));
573
604
  return maxConcurrentSites;
574
605
  } else {
575
- console.warn(`⚠ Invalid --max-concurrent value: ${maxConcurrentSites}. Must be 1-50. Using config/default value.`);
606
+ console.warn(`⚠ Invalid --max-concurrent value: ${maxConcurrentSites}. Must be ${CONCURRENCY_LIMITS.MIN}-${CONCURRENCY_LIMITS.MAX}. Using config/default value.`);
576
607
  }
577
608
  }
578
609
 
579
610
  // Check config.json value
580
- if (typeof max_concurrent_sites === 'number' && max_concurrent_sites > 0 && max_concurrent_sites <= 50) {
611
+ if (typeof max_concurrent_sites === 'number' && max_concurrent_sites >= CONCURRENCY_LIMITS.MIN && max_concurrent_sites <= CONCURRENCY_LIMITS.MAX) {
581
612
  if (forceDebug) console.log(formatLogMessage('debug', `Using config max_concurrent_sites: ${max_concurrent_sites}`));
582
613
  return max_concurrent_sites;
583
- } else if (max_concurrent_sites !== 6) {
584
- console.warn(`⚠ Invalid config max_concurrent_sites value: ${max_concurrent_sites}. Using default: 6`);
614
+ } else if (max_concurrent_sites !== CONCURRENCY_LIMITS.DEFAULT) {
615
+ console.warn(`⚠ Invalid config max_concurrent_sites value: ${max_concurrent_sites}. Using default: ${CONCURRENCY_LIMITS.DEFAULT}`);
585
616
  }
586
617
 
587
618
  // Use default
588
- return 6;
619
+ return CONCURRENCY_LIMITS.DEFAULT;
589
620
  })();
590
621
 
591
622
  const RESOURCE_CLEANUP_INTERVAL = (() => {
@@ -621,7 +652,7 @@ if (clearCache && dryRunMode) {
621
652
  }
622
653
 
623
654
  // Also clear for custom cache paths in normal mode if not already cleared
624
- if (clearCache && !dryRunMode && config.cache_path && config.cache_path !== '.cache') {
655
+ if (clearCache && !dryRunMode && config.cache_path && config.cache_path !== CACHE_LIMITS.DEFAULT_CACHE_PATH) {
625
656
  clearPersistentCache({
626
657
  silent: silentMode,
627
658
  forceDebug,
@@ -638,11 +669,11 @@ smartCache = createSmartCache({
638
669
  ...config,
639
670
  forceDebug,
640
671
  max_concurrent_sites: MAX_CONCURRENT_SITES, // Pass concurrency info
641
- cache_aggressive_mode: MAX_CONCURRENT_SITES > 12, // Auto-enable for high concurrency
672
+ cache_aggressive_mode: MAX_CONCURRENT_SITES > CONCURRENCY_LIMITS.HIGH_CONCURRENCY_THRESHOLD, // Auto-enable for high concurrency
642
673
  cache_persistence: false, // Disable persistence completely
643
674
  cache_autosave: false, // Disable auto-save completely
644
675
  cache_autosave_minutes: config.cache_autosave_minutes || 1,
645
- cache_max_size: config.cache_max_size || 5000
676
+ cache_max_size: config.cache_max_size || CACHE_LIMITS.DEFAULT_MAX_SIZE
646
677
  });
647
678
  }
648
679
 
@@ -1142,8 +1173,8 @@ function setupFrameHandling(page, forceDebug) {
1142
1173
  args: [
1143
1174
  // Disk space controls - 50MB cache limits
1144
1175
  '--disable-features=VizDisplayCompositor',
1145
- '--disk-cache-size=52428800', // 50MB disk cache (50 * 1024 * 1024)
1146
- '--media-cache-size=52428800', // 50MB media cache
1176
+ `--disk-cache-size=${CACHE_LIMITS.DISK_CACHE_SIZE}`, // 50MB disk cache
1177
+ `--media-cache-size=${CACHE_LIMITS.MEDIA_CACHE_SIZE}`, // 50MB media cache
1147
1178
  '--disable-application-cache',
1148
1179
  '--disable-offline-load-stale-cache',
1149
1180
  '--disable-background-downloads',
@@ -1188,7 +1219,7 @@ function setupFrameHandling(page, forceDebug) {
1188
1219
  '--no-zygote', // Better process isolation
1189
1220
  ],
1190
1221
  // Optimized timeouts for Puppeteer 23.x performance
1191
- protocolTimeout: 60000, // Reduced from 60s to 45s for faster failure detection
1222
+ protocolTimeout: TIMEOUTS.PROTOCOL_TIMEOUT,
1192
1223
  slowMo: 0, // No artificial delays
1193
1224
  defaultViewport: null, // Use system default viewport
1194
1225
  ignoreDefaultArgs: ['--enable-automation'] // Avoid automation detection
@@ -1336,7 +1367,7 @@ function setupFrameHandling(page, forceDebug) {
1336
1367
  matchedDomains.set('dryRunNetTools', []);
1337
1368
  matchedDomains.set('dryRunSearchString', new Map()); // Map URL to search results
1338
1369
  }
1339
- const timeout = siteConfig.timeout || 30000;
1370
+ const timeout = siteConfig.timeout || TIMEOUTS.DEFAULT_PAGE;
1340
1371
 
1341
1372
  if (!silentMode) console.log(`\n${messageColors.scanning('Scanning:')} ${currentUrl}`);
1342
1373
 
@@ -1393,8 +1424,8 @@ function setupFrameHandling(page, forceDebug) {
1393
1424
 
1394
1425
  // Set aggressive timeouts for problematic operations
1395
1426
  // Optimized timeouts for Puppeteer 23.x responsiveness
1396
- page.setDefaultTimeout(Math.min(timeout, 20000)); // Reduced from 15000 for faster failures
1397
- page.setDefaultNavigationTimeout(Math.min(timeout, 25000)); // Reduced from 18000
1427
+ page.setDefaultTimeout(Math.min(timeout, TIMEOUTS.DEFAULT_PAGE_REDUCED));
1428
+ page.setDefaultNavigationTimeout(Math.min(timeout, TIMEOUTS.DEFAULT_NAVIGATION));
1398
1429
  // Aggressive timeouts prevent hanging in Puppeteer 23.x while maintaining speed
1399
1430
 
1400
1431
  page.on('console', (msg) => {
@@ -1461,8 +1492,8 @@ function setupFrameHandling(page, forceDebug) {
1461
1492
  // Apply flowProxy timeouts if detection is enabled
1462
1493
  if (flowproxyDetection) {
1463
1494
  const flowproxyTimeouts = getFlowProxyTimeouts(siteConfig);
1464
- page.setDefaultTimeout(Math.min(flowproxyTimeouts.pageTimeout, 25000));
1465
- page.setDefaultNavigationTimeout(Math.min(flowproxyTimeouts.navigationTimeout, 30000));
1495
+ page.setDefaultTimeout(Math.min(flowproxyTimeouts.pageTimeout, TIMEOUTS.DEFAULT_NAVIGATION));
1496
+ page.setDefaultNavigationTimeout(Math.min(flowproxyTimeouts.navigationTimeout, TIMEOUTS.DEFAULT_PAGE));
1466
1497
  if (forceDebug) {
1467
1498
  console.log(formatLogMessage('debug', `Applied flowProxy timeouts - page: ${flowproxyTimeouts.pageTimeout}ms, nav: ${flowproxyTimeouts.navigationTimeout}ms`));
1468
1499
  }
@@ -2293,13 +2324,13 @@ function setupFrameHandling(page, forceDebug) {
2293
2324
  // Use explicit Promise-based timeouts instead of page.waitForTimeout()
2294
2325
 
2295
2326
  // Use faster defaults for sites with long timeouts to improve responsiveness
2296
- const isFastSite = timeout <= 15000;
2327
+ const isFastSite = timeout <= TIMEOUTS.FAST_SITE_THRESHOLD;
2297
2328
  const defaultWaitUntil = 'domcontentloaded'; // Always use faster option in Puppeteer 23.x
2298
2329
 
2299
2330
  // Enhanced navigation options for Puppeteer 23.x
2300
2331
  const defaultGotoOptions = {
2301
2332
  waitUntil: defaultWaitUntil,
2302
- timeout: Math.min(timeout, 30000), // Reduced from 20000 for faster failures
2333
+ timeout: Math.min(timeout, TIMEOUTS.DEFAULT_PAGE), // Cap at default page timeout
2303
2334
  // Puppeteer 23.x: Fixed referrer header handling
2304
2335
  ...(siteConfig.referrer_headers && (() => {
2305
2336
  const referrerUrl = Array.isArray(siteConfig.referrer_headers)
@@ -2418,7 +2449,7 @@ function setupFrameHandling(page, forceDebug) {
2418
2449
  if (forceDebug) {
2419
2450
  try {
2420
2451
  // Use fast timeout helper for compatibility
2421
- await fastTimeout(2000); // Give iframes time to load
2452
+ await fastTimeout(TIMEOUTS.FRAME_LOAD_WAIT); // Give iframes time to load
2422
2453
  const frames = page.frames();
2423
2454
  console.log(formatLogMessage('debug', `Total frames found: ${frames.length}`));
2424
2455
  frames.forEach((frame, index) => {
@@ -2462,10 +2493,10 @@ function setupFrameHandling(page, forceDebug) {
2462
2493
  const delayMs = siteConfig.delay || 4000;
2463
2494
 
2464
2495
  // Optimized delays for Puppeteer 23.x performance
2465
- const isFastSite = timeout <= 15000;
2466
- const networkIdleTime = 2000; // Balanced: 2s for reliable network detection
2467
- const networkIdleTimeout = Math.min(timeout / 2, 10000); // Balanced: 10s timeout
2468
- const actualDelay = Math.min(delayMs, 2000); // Balanced: 2s delay for stability
2496
+ const isFastSite = timeout <= TIMEOUTS.FAST_SITE_THRESHOLD;
2497
+ const networkIdleTime = TIMEOUTS.NETWORK_IDLE; // Balanced: 2s for reliable network detection
2498
+ const networkIdleTimeout = Math.min(timeout / 2, TIMEOUTS.NETWORK_IDLE_MAX); // Balanced: 10s timeout
2499
+ const actualDelay = Math.min(delayMs, TIMEOUTS.NETWORK_IDLE); // Balanced: 2s delay for stability
2469
2500
 
2470
2501
  await page.waitForNetworkIdle({
2471
2502
  idleTime: networkIdleTime,
@@ -2550,7 +2581,7 @@ function setupFrameHandling(page, forceDebug) {
2550
2581
 
2551
2582
  // Wait a moment for async nettools/searchstring operations to complete
2552
2583
  // Use fast timeout helper for Puppeteer 22.x compatibility
2553
- await fastTimeout(3000); // Increased for nettools operations
2584
+ await fastTimeout(TIMEOUTS.CURL_HANDLER_DELAY); // Wait for async operations
2554
2585
 
2555
2586
  outputDryRunResults(currentUrl, enhancedMatches, dryRunNetTools, pageTitle);
2556
2587
 
@@ -2767,7 +2798,7 @@ function setupFrameHandling(page, forceDebug) {
2767
2798
 
2768
2799
  // Reset cleanup counter and add delay
2769
2800
  urlsSinceLastCleanup = 0;
2770
- await fastTimeout(1000);
2801
+ await fastTimeout(TIMEOUTS.BROWSER_STABILIZE_DELAY);
2771
2802
  }
2772
2803
 
2773
2804
  if (forceDebug) {
@@ -2835,7 +2866,7 @@ function setupFrameHandling(page, forceDebug) {
2835
2866
  }
2836
2867
  browser = await createBrowser();
2837
2868
  urlsSinceLastCleanup = 0; // Reset counter
2838
- await fastTimeout(2000); // Give browser time to stabilize
2869
+ await fastTimeout(TIMEOUTS.EMERGENCY_RESTART_DELAY); // Give browser time to stabilize
2839
2870
  } catch (emergencyRestartErr) {
2840
2871
  if (forceDebug) console.log(formatLogMessage('debug', `Emergency restart failed: ${emergencyRestartErr.message}`));
2841
2872
  }
@@ -3013,7 +3044,7 @@ function setupFrameHandling(page, forceDebug) {
3013
3044
  forceDebug,
3014
3045
  comprehensive: true
3015
3046
  });
3016
- await fastTimeout(1000); // Give filesystem time to sync
3047
+ await fastTimeout(TIMEOUTS.BROWSER_STABILIZE_DELAY); // Give filesystem time to sync
3017
3048
 
3018
3049
  // Calculate timing, success rates, and provide summary information
3019
3050
  if (forceDebug) console.log(formatLogMessage('debug', `Calculating timing statistics...`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanboynz/network-scanner",
3
- "version": "1.0.68",
3
+ "version": "1.0.69",
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": {
@@ -0,0 +1,713 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Regex Tools - Converter & Validator</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1000px;
23
+ margin: 0 auto;
24
+ background: rgba(255, 255, 255, 0.95);
25
+ border-radius: 20px;
26
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .header {
31
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
32
+ color: white;
33
+ padding: 30px;
34
+ text-align: center;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5em;
39
+ margin-bottom: 10px;
40
+ animation: fadeInDown 0.5s ease;
41
+ }
42
+
43
+ .header p {
44
+ opacity: 0.9;
45
+ font-size: 1.1em;
46
+ }
47
+
48
+ .tabs {
49
+ display: flex;
50
+ background: #f7f9fc;
51
+ border-bottom: 2px solid #e2e8f0;
52
+ }
53
+
54
+ .tab {
55
+ flex: 1;
56
+ padding: 20px;
57
+ text-align: center;
58
+ cursor: pointer;
59
+ background: transparent;
60
+ border: none;
61
+ font-size: 1.1em;
62
+ font-weight: 600;
63
+ color: #64748b;
64
+ transition: all 0.3s ease;
65
+ position: relative;
66
+ }
67
+
68
+ .tab:hover {
69
+ background: rgba(102, 126, 234, 0.1);
70
+ }
71
+
72
+ .tab.active {
73
+ color: #667eea;
74
+ background: white;
75
+ }
76
+
77
+ .tab.active::after {
78
+ content: '';
79
+ position: absolute;
80
+ bottom: -2px;
81
+ left: 0;
82
+ right: 0;
83
+ height: 3px;
84
+ background: linear-gradient(90deg, #667eea, #764ba2);
85
+ animation: slideIn 0.3s ease;
86
+ }
87
+
88
+ .content {
89
+ padding: 40px;
90
+ min-height: 500px;
91
+ }
92
+
93
+ .tab-content {
94
+ display: none;
95
+ animation: fadeIn 0.3s ease;
96
+ }
97
+
98
+ .tab-content.active {
99
+ display: block;
100
+ }
101
+
102
+ .input-group {
103
+ margin-bottom: 30px;
104
+ }
105
+
106
+ .input-group label {
107
+ display: block;
108
+ margin-bottom: 10px;
109
+ font-weight: 600;
110
+ color: #334155;
111
+ font-size: 1.1em;
112
+ }
113
+
114
+ .input-group input[type="text"],
115
+ .input-group textarea {
116
+ width: 100%;
117
+ padding: 15px;
118
+ border: 2px solid #e2e8f0;
119
+ border-radius: 10px;
120
+ font-size: 1em;
121
+ font-family: 'Consolas', 'Monaco', monospace;
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .input-group input[type="text"]:focus,
126
+ .input-group textarea:focus {
127
+ outline: none;
128
+ border-color: #667eea;
129
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
130
+ }
131
+
132
+ .input-group textarea {
133
+ resize: vertical;
134
+ min-height: 100px;
135
+ }
136
+
137
+ .checkbox-group {
138
+ display: flex;
139
+ align-items: center;
140
+ margin-bottom: 20px;
141
+ }
142
+
143
+ .checkbox-group input[type="checkbox"] {
144
+ width: 20px;
145
+ height: 20px;
146
+ margin-right: 10px;
147
+ cursor: pointer;
148
+ }
149
+
150
+ .checkbox-group label {
151
+ cursor: pointer;
152
+ font-weight: 500;
153
+ color: #475569;
154
+ }
155
+
156
+ .button {
157
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
158
+ color: white;
159
+ border: none;
160
+ padding: 15px 40px;
161
+ border-radius: 10px;
162
+ font-size: 1.1em;
163
+ font-weight: 600;
164
+ cursor: pointer;
165
+ transition: all 0.3s ease;
166
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
167
+ }
168
+
169
+ .button:hover {
170
+ transform: translateY(-2px);
171
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
172
+ }
173
+
174
+ .button:active {
175
+ transform: translateY(0);
176
+ }
177
+
178
+ .output-section {
179
+ margin-top: 40px;
180
+ padding: 25px;
181
+ background: #f8fafc;
182
+ border-radius: 10px;
183
+ border: 2px solid #e2e8f0;
184
+ }
185
+
186
+ .output-section h3 {
187
+ margin-bottom: 15px;
188
+ color: #334155;
189
+ font-size: 1.3em;
190
+ }
191
+
192
+ .output-box {
193
+ background: white;
194
+ padding: 20px;
195
+ border-radius: 8px;
196
+ border: 1px solid #e2e8f0;
197
+ font-family: 'Consolas', 'Monaco', monospace;
198
+ margin-bottom: 20px;
199
+ word-break: break-all;
200
+ transition: all 0.3s ease;
201
+ }
202
+
203
+ .output-box:hover {
204
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
205
+ }
206
+
207
+ .examples-list {
208
+ list-style: none;
209
+ padding: 0;
210
+ }
211
+
212
+ .examples-list li {
213
+ background: white;
214
+ padding: 15px;
215
+ margin-bottom: 10px;
216
+ border-radius: 8px;
217
+ border-left: 4px solid #667eea;
218
+ font-family: 'Consolas', 'Monaco', monospace;
219
+ transition: all 0.3s ease;
220
+ }
221
+
222
+ .examples-list li:hover {
223
+ transform: translateX(5px);
224
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
225
+ }
226
+
227
+ .error {
228
+ background: #fee;
229
+ color: #c00;
230
+ padding: 15px;
231
+ border-radius: 8px;
232
+ border: 1px solid #fcc;
233
+ margin-top: 10px;
234
+ }
235
+
236
+ .success {
237
+ background: #e6fffa;
238
+ color: #047857;
239
+ padding: 15px;
240
+ border-radius: 8px;
241
+ border: 1px solid #6ee7b7;
242
+ margin-top: 10px;
243
+ }
244
+
245
+ @keyframes fadeIn {
246
+ from {
247
+ opacity: 0;
248
+ transform: translateY(10px);
249
+ }
250
+ to {
251
+ opacity: 1;
252
+ transform: translateY(0);
253
+ }
254
+ }
255
+
256
+ @keyframes fadeInDown {
257
+ from {
258
+ opacity: 0;
259
+ transform: translateY(-20px);
260
+ }
261
+ to {
262
+ opacity: 1;
263
+ transform: translateY(0);
264
+ }
265
+ }
266
+
267
+ @keyframes slideIn {
268
+ from {
269
+ transform: scaleX(0);
270
+ }
271
+ to {
272
+ transform: scaleX(1);
273
+ }
274
+ }
275
+
276
+ @media (max-width: 768px) {
277
+ .header h1 {
278
+ font-size: 1.8em;
279
+ }
280
+
281
+ .content {
282
+ padding: 20px;
283
+ }
284
+
285
+ .button {
286
+ width: 100%;
287
+ }
288
+ }
289
+ </style>
290
+ </head>
291
+ <body>
292
+ <div class="container">
293
+ <div class="header">
294
+ <h1>🔍 Regex Tools</h1>
295
+ <p>Convert and Validate Regular Expressions with Ease</p>
296
+ </div>
297
+
298
+ <div class="tabs">
299
+ <button class="tab active" onclick="switchTab('convert')">
300
+ 📝 Convert to JSON
301
+ </button>
302
+ <button class="tab" onclick="switchTab('validate')">
303
+ ✅ Validate Regex
304
+ </button>
305
+ </div>
306
+
307
+ <div class="content">
308
+ <!-- Convert Tab -->
309
+ <div id="convert" class="tab-content active">
310
+ <div class="input-group">
311
+ <label for="standardRegex">Enter Standard Regex Pattern:</label>
312
+ <input type="text" id="standardRegex" placeholder="e.g., ^https?://.*\.example\.com/.*$">
313
+ </div>
314
+
315
+ <button class="button" onclick="convertToJSON()">Convert to JSON</button>
316
+
317
+ <div id="convertOutput"></div>
318
+ </div>
319
+
320
+ <!-- Validate Tab -->
321
+ <div id="validate" class="tab-content">
322
+ <div class="checkbox-group" style="background: #f0f9ff; padding: 15px; border-radius: 8px; margin-bottom: 20px; border: 2px solid #0ea5e9;">
323
+ <input type="checkbox" id="useStandardRegex" onchange="toggleRegexInput()">
324
+ <label for="useStandardRegex"><strong>✓ Check this box to validate Standard Regex</strong> (uncheck for JSON format)</label>
325
+ </div>
326
+
327
+ <div class="input-group">
328
+ <label for="regexToValidate">Enter Regex Pattern:</label>
329
+ <textarea id="regexToValidate" placeholder='⚠️ IMPORTANT: Check the box above if entering standard regex!&#10;&#10;For JSON (box unchecked): {"filterRegex": ["^https?:\\\\/\\\\/.*"]}&#10;For Standard (box checked): ^https?:\\/\\/.*'></textarea>
330
+ </div>
331
+
332
+ <button class="button" onclick="validateRegex()">Validate Regex</button>
333
+
334
+ <div id="validateOutput"></div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+
339
+ <script>
340
+ function switchTab(tabName) {
341
+ // Update tabs
342
+ document.querySelectorAll('.tab').forEach(tab => {
343
+ tab.classList.remove('active');
344
+ });
345
+ event.target.classList.add('active');
346
+
347
+ // Update content
348
+ document.querySelectorAll('.tab-content').forEach(content => {
349
+ content.classList.remove('active');
350
+ });
351
+ document.getElementById(tabName).classList.add('active');
352
+ }
353
+
354
+ function toggleRegexInput() {
355
+ const checkbox = document.getElementById('useStandardRegex');
356
+ const textarea = document.getElementById('regexToValidate');
357
+
358
+ if (checkbox.checked) {
359
+ textarea.placeholder = 'Enter standard regex pattern, e.g., ^https?://.*';
360
+ } else {
361
+ textarea.placeholder = 'Enter JSON regex, e.g., {"pattern": "^https?://.*", "flags": "gi"}';
362
+ }
363
+ textarea.value = '';
364
+ }
365
+
366
+ function generateExamples(regex) {
367
+ const examples = [];
368
+
369
+ try {
370
+ // Get the pattern string
371
+ const pattern = regex.source || regex.pattern || regex.toString();
372
+
373
+ // Helper function to generate random string of specific length
374
+ function generateRandomString(minLen, maxLen) {
375
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-+=';
376
+ const length = maxLen ? Math.floor(Math.random() * (maxLen - minLen + 1)) + minLen : minLen;
377
+ let result = '';
378
+ for (let i = 0; i < length; i++) {
379
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
380
+ }
381
+ return result;
382
+ }
383
+
384
+ if (pattern.includes('http')) {
385
+ // Check for specific domain requirements
386
+ if (pattern.includes('com|org')) {
387
+ // Pattern requires .com or .org domains
388
+ const protocols = pattern.includes('https?') ? ['https://', 'http://'] : ['https://'];
389
+ const domains = ['example', 'test-site', 'web-app', 'api-server', 'my-service'];
390
+ const extensions = ['com', 'org'];
391
+
392
+ for (let i = 0; i < 5; i++) {
393
+ const protocol = protocols[i % protocols.length];
394
+ const domain = domains[i % domains.length];
395
+ const ext = extensions[i % extensions.length];
396
+
397
+ // Build the path part
398
+ let path = '';
399
+
400
+ // Check for specific length requirements in path
401
+ const pathMatch = pattern.match(/\[A-Za-z0-9[^\]]*\]\{(\d+),?(\d*)\}/);
402
+ if (pathMatch) {
403
+ const minLen = parseInt(pathMatch[1]);
404
+ const maxLen = pathMatch[2] ? parseInt(pathMatch[2]) : minLen + 20;
405
+ path = generateRandomString(minLen, maxLen);
406
+ } else {
407
+ path = 'api/v1/users/profile/data';
408
+ }
409
+
410
+ let url = `${protocol}${domain}.${ext}/${path}`;
411
+
412
+ // Add query params if pattern requires them
413
+ if (pattern.includes('\\?')) {
414
+ // Check for minimum length requirements in query
415
+ const queryMatch = pattern.match(/\\\?\.\{(\d+),?(\d*)\}/);
416
+ if (queryMatch) {
417
+ const minLen = parseInt(queryMatch[1]);
418
+ const maxLen = queryMatch[2] ? parseInt(queryMatch[2]) : minLen + 10;
419
+ url += '?' + generateRandomString(minLen, maxLen);
420
+ } else if (pattern.match(/\?\.\{(\d+),/)) {
421
+ // Handle any {n,} pattern after ?
422
+ const match = pattern.match(/\?\.\{(\d+),/);
423
+ const minLen = parseInt(match[1]);
424
+ url += '?param=' + generateRandomString(Math.max(minLen - 6, 1), minLen + 15);
425
+ } else {
426
+ url += '?param=value&key=data';
427
+ }
428
+ } else if (pattern.includes('&.{')) {
429
+ // Handle &.{n,} case
430
+ const ampMatch = pattern.match(/&\.\{(\d+),/);
431
+ if (ampMatch) {
432
+ const minLen = parseInt(ampMatch[1]);
433
+ url += '&' + generateRandomString(minLen, minLen + 15);
434
+ }
435
+ }
436
+
437
+ examples.push(url);
438
+ }
439
+ } else {
440
+ // General URL patterns
441
+ const protocols = pattern.includes('https?') ? ['http://', 'https://'] : ['https://'];
442
+ const urls = [
443
+ 'www.example.com/path/to/resource',
444
+ 'subdomain.test.com/api/endpoint',
445
+ 'example.org/page/section/item',
446
+ 'demo.net/products/category',
447
+ 'sample.io/user/profile/settings'
448
+ ];
449
+
450
+ urls.forEach((url, i) => {
451
+ if (i < 5) {
452
+ let fullUrl = protocols[i % protocols.length] + url;
453
+
454
+ // Check for length requirements
455
+ const lengthMatch = pattern.match(/\{(\d+),?(\d*)\}/);
456
+ if (lengthMatch && !pattern.includes('\\?')) {
457
+ const minLen = parseInt(lengthMatch[1]);
458
+ const maxLen = lengthMatch[2] ? parseInt(lengthMatch[2]) : minLen + 10;
459
+ fullUrl += '/' + generateRandomString(minLen, maxLen);
460
+ }
461
+
462
+ examples.push(fullUrl);
463
+ }
464
+ });
465
+ }
466
+
467
+ // Test all generated examples against the regex
468
+ const validExamples = [];
469
+ for (const example of examples) {
470
+ try {
471
+ // Reset regex lastIndex in case of global flag
472
+ regex.lastIndex = 0;
473
+ if (regex.test(example)) {
474
+ validExamples.push(example);
475
+ }
476
+ } catch (e) {
477
+ console.error('Error testing example:', e);
478
+ }
479
+ }
480
+
481
+ // If we don't have enough valid examples, try more specific ones with proper lengths
482
+ if (validExamples.length < 5) {
483
+ const moreSpecific = [];
484
+ for (let i = 0; i < 10; i++) {
485
+ const protocol = i % 2 === 0 ? 'https://' : 'http://';
486
+ const domain = ['test-api', 'web-server', 'my-app', 'demo-site', 'api-gateway'][i % 5];
487
+ const ext = i % 2 === 0 ? 'com' : 'org';
488
+
489
+ // Generate path with proper length if specified
490
+ let path = 'Path_';
491
+ const pathMatch = pattern.match(/\[A-Za-z0-9[^\]]*\]\{(\d+),?(\d*)\}/);
492
+ if (pathMatch) {
493
+ const minLen = parseInt(pathMatch[1]);
494
+ const maxLen = pathMatch[2] ? parseInt(pathMatch[2]) : minLen + 20;
495
+ path = generateRandomString(minLen, maxLen);
496
+ } else {
497
+ path += generateRandomString(10, 30);
498
+ }
499
+
500
+ let url = `${protocol}${domain}.${ext}/${path}`;
501
+
502
+ // Add query with proper length if needed
503
+ if (pattern.includes('\\?')) {
504
+ const queryMatch = pattern.match(/\\\?\.\{(\d+),?(\d*)\}/);
505
+ if (queryMatch) {
506
+ const minLen = parseInt(queryMatch[1]);
507
+ url += '?q=' + generateRandomString(Math.max(minLen - 2, 1), minLen + 10);
508
+ } else if (pattern.match(/\?\.\{(\d+),/)) {
509
+ const match = pattern.match(/\?\.\{(\d+),/);
510
+ const minLen = parseInt(match[1]);
511
+ url += '?' + generateRandomString(minLen, minLen + 15);
512
+ } else {
513
+ url += '?param=' + generateRandomString(10, 20);
514
+ }
515
+ } else if (pattern.includes('&.{')) {
516
+ const ampMatch = pattern.match(/&\.\{(\d+),/);
517
+ if (ampMatch) {
518
+ const minLen = parseInt(ampMatch[1]);
519
+ url += '&' + generateRandomString(minLen, minLen + 15);
520
+ }
521
+ }
522
+
523
+ moreSpecific.push(url);
524
+ }
525
+
526
+ for (const url of moreSpecific) {
527
+ regex.lastIndex = 0;
528
+ if (regex.test(url) && validExamples.length < 5) {
529
+ validExamples.push(url);
530
+ }
531
+ }
532
+ }
533
+
534
+ return validExamples.length > 0 ? validExamples.slice(0, 5) : [
535
+ 'Pattern is very specific - generating custom examples...',
536
+ 'Try: https://example.com/Page_Name/path?query=value12345',
537
+ 'Try: http://test.org/API_endpoint/data?param=longvalue123',
538
+ 'Manual testing recommended for this pattern',
539
+ 'Validation successful'
540
+ ];
541
+ } else if (pattern.includes('@')) {
542
+ examples.push(
543
+ 'user@example.com',
544
+ 'john.doe@company.org',
545
+ 'admin+tag@subdomain.example.com',
546
+ 'contact@business.co.uk',
547
+ 'support123@service.io'
548
+ );
549
+ } else if (pattern.includes('\\d')) {
550
+ // Check for length requirements
551
+ const lengthMatch = pattern.match(/\{(\d+),?(\d*)\}/);
552
+ if (lengthMatch) {
553
+ const minLen = parseInt(lengthMatch[1]);
554
+ const maxLen = lengthMatch[2] ? parseInt(lengthMatch[2]) : minLen + 10;
555
+ for (let i = 0; i < 5; i++) {
556
+ examples.push(generateRandomString(minLen, maxLen));
557
+ }
558
+ } else {
559
+ examples.push(
560
+ '12345',
561
+ '2024-01-15',
562
+ 'ID-9876-XY',
563
+ 'Version 3.14.159',
564
+ 'Order #00425'
565
+ );
566
+ }
567
+ } else if (pattern.includes('\\w')) {
568
+ // Check for length requirements
569
+ const lengthMatch = pattern.match(/\{(\d+),?(\d*)\}/);
570
+ if (lengthMatch) {
571
+ const minLen = parseInt(lengthMatch[1]);
572
+ const maxLen = lengthMatch[2] ? parseInt(lengthMatch[2]) : minLen + 10;
573
+ for (let i = 0; i < 5; i++) {
574
+ examples.push(generateRandomString(minLen, maxLen));
575
+ }
576
+ } else {
577
+ examples.push(
578
+ 'HelloWorld',
579
+ 'user_name_123',
580
+ 'CamelCaseExample',
581
+ );
582
+ }
583
+ } else {
584
+ // Check for any length requirements in the pattern
585
+ const lengthMatch = pattern.match(/\{(\d+),?(\d*)\}/);
586
+ if (lengthMatch) {
587
+ const minLen = parseInt(lengthMatch[1]);
588
+ const maxLen = lengthMatch[2] ? parseInt(lengthMatch[2]) : minLen + 15;
589
+ for (let i = 0; i < 5; i++) {
590
+ examples.push(generateRandomString(minLen, maxLen));
591
+ }
592
+ } else {
593
+ // Generic examples
594
+ examples.push(
595
+ 'example text',
596
+ 'Sample String 123',
597
+ 'test-data-here',
598
+ 'Another_Example',
599
+ 'final.test.value'
600
+ );
601
+ }
602
+ }
603
+
604
+ // Filter examples that actually match the regex
605
+ const validExamples = examples.filter(ex => {
606
+ try {
607
+ regex.lastIndex = 0;
608
+ return regex.test(ex);
609
+ } catch {
610
+ return false;
611
+ }
612
+ });
613
+
614
+ return validExamples.length > 0 ? validExamples.slice(0, 5) : [
615
+ 'No automatic examples could be generated.',
616
+ 'The pattern may be too specific.',
617
+ 'Try testing with your own strings.',
618
+ 'Enter test strings manually.',
619
+ 'Pattern validation successful.'
620
+ ];
621
+ } catch (error) {
622
+ console.error('Error in generateExamples:', error);
623
+ return ['Error generating examples'];
624
+ }
625
+ }
626
+
627
+ function convertToJSON() {
628
+ const standardRegex = document.getElementById('standardRegex').value;
629
+ const outputDiv = document.getElementById('convertOutput');
630
+
631
+ if (!standardRegex) {
632
+ outputDiv.innerHTML = '<div class="error">Please enter a regex pattern</div>';
633
+ return;
634
+ }
635
+
636
+ try {
637
+ // Remove surrounding quotes if present
638
+ let cleanPattern = standardRegex.trim();
639
+ if ((cleanPattern.startsWith('"') && cleanPattern.endsWith('"')) ||
640
+ (cleanPattern.startsWith("'") && cleanPattern.endsWith("'"))) {
641
+ cleanPattern = cleanPattern.slice(1, -1);
642
+ }
643
+
644
+ // Test if the regex is valid first (using the cleaned pattern)
645
+ const testRegex = new RegExp(cleanPattern);
646
+
647
+ // For JSON string format: the input pattern stays as is
648
+ // JSON.stringify will handle the escaping
649
+ const jsonOutput = {
650
+ filterRegex: [cleanPattern]
651
+ };
652
+
653
+ // Generate examples
654
+ const examples = generateExamples(testRegex);
655
+
656
+ outputDiv.innerHTML = `
657
+ <div class="output-section">
658
+ <h3>📋 JSON Output:</h3>
659
+ <div class="output-box">${JSON.stringify(jsonOutput, null, 2)}</div>
660
+
661
+ <h3>✨ Example Matches:</h3>
662
+ <ul class="examples-list">
663
+ ${examples.map(ex => `<li>${escapeHtml(ex)}</li>`).join('')}
664
+ </ul>
665
+
666
+ <div class="success">✅ Regex successfully converted to JSON format!</div>
667
+ </div>
668
+ `;
669
+ } catch (error) {
670
+ outputDiv.innerHTML = `<div class="error">❌ Invalid regex pattern: ${escapeHtml(error.message)}</div>`;
671
+ }
672
+ }
673
+
674
+ function validatandard ? 'Standard Regex' : 'JSON Regex'}
675
+ </div>
676
+
677
+ <h3>✨ Example Matches:</h3>
678
+ <ul class="examples-list">
679
+ ${examples.map(ex => `<li>${escapeHtml(ex)}</li>`).join('')}
680
+ </ul>
681
+
682
+ <div class="success">✅ Regex is valid!</div>
683
+ </div>
684
+ `;
685
+ } catch (error) {
686
+ if (error instanceof SyntaxError && !useStandard) {
687
+ outputDiv.innerHTML = `<div class="error">❌ Invalid JSON syntax: ${escapeHtml(error.message)}<br><br>Expected format: {"filterRegex": ["your-regex-pattern"]} or "your-regex-pattern"</div>`;
688
+ } else {
689
+ outputDiv.innerHTML = `<div class="error">❌ Invalid regex: ${escapeHtml(error.message)}</div>`;
690
+ }
691
+ }
692
+ }
693
+
694
+ function escapeHtml(text) {
695
+ const div = document.createElement('div');
696
+ div.textContent = text;
697
+ return div.innerHTML;
698
+ }
699
+
700
+ // Add enter key support
701
+ document.getElementById('standardRegex').addEventListener('keypress', function(e) {
702
+ if (e.key === 'Enter') convertToJSON();
703
+ });
704
+
705
+ document.getElementById('regexToValidate').addEventListener('keypress', function(e) {
706
+ if (e.key === 'Enter' && !e.shiftKey) {
707
+ e.preventDefault();
708
+ validateRegex();
709
+ }
710
+ });
711
+ </script>
712
+ </body>
713
+ </html>