@detiq/crawler 0.1.3
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/Dockerfile +49 -0
- package/README.md +11 -0
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +177 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +329 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawler.d.ts +179 -0
- package/dist/crawler.js +3672 -0
- package/dist/crawler.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +54 -0
- package/dist/extractors/advanced.js +245 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +40 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +32 -0
- package/dist/frontier.js +83 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +35 -0
- package/dist/gap-pass.js +81 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +304 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +59 -0
- package/dist/incremental-diff.js +61 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +104 -0
- package/dist/interactive-probe.js +442 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +171 -0
- package/dist/job-queue.js +2256 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +100 -0
- package/dist/live-view.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics.d.ts +53 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +38 -0
- package/dist/phash.js +115 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +37 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +140 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +16 -0
- package/dist/screen-reader.js +46 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/session-capture.d.ts +35 -0
- package/dist/session-capture.js +267 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +152 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +2 -0
- package/dist/sqs-queue.js +47 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +168 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +67 -0
- package/dist/test-executor.js +311 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +87 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +25 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +45 -0
- package/src/accessibility-auditor.ts +102 -0
- package/src/ai-login.ts +218 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +396 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawler.ts +4305 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +283 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/failure-analysis-agent.ts +49 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +112 -0
- package/src/gap-pass.ts +122 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +433 -0
- package/src/incremental-diff.ts +121 -0
- package/src/index.ts +35 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +623 -0
- package/src/job-queue.test.ts +120 -0
- package/src/job-queue.ts +2701 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +141 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/native-crawler.ts +1106 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +151 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +46 -0
- package/src/profiles/domain-profile.ts +167 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +89 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/session-capture.ts +325 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +131 -0
- package/src/sitemap.ts +224 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +49 -0
- package/src/sso-token.ts +120 -0
- package/src/stealth/human-behavior.ts +191 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +424 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +114 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +57 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +48 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawler.d.ts +179 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +54 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +32 -0
- package/types/gap-pass.d.ts +35 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +59 -0
- package/types/index.d.ts +35 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +104 -0
- package/types/job-queue.d.ts +171 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics.d.ts +53 -0
- package/types/phash.d.ts +38 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +16 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/session-capture.d.ts +38 -0
- package/types/session-export.d.ts +49 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +2 -0
- package/types/sso-token.d.ts +29 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +67 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
function buildSmartLocatorHelper() {
|
|
2
|
+
return `
|
|
3
|
+
// smartLocator \u2014 falls back to similarity search when primary selector fails.
|
|
4
|
+
// Generated by ZeTa Crawler. Do not edit manually.
|
|
5
|
+
async function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {
|
|
6
|
+
try {
|
|
7
|
+
const el = page.locator(primary);
|
|
8
|
+
if (await el.count() > 0) return el;
|
|
9
|
+
} catch { /* fall through */ }
|
|
10
|
+
// Fallback: find by ARIA role + text
|
|
11
|
+
if (ref.role && ref.text) {
|
|
12
|
+
const byRole = page.getByRole(ref.role as any, { name: ref.text, exact: false });
|
|
13
|
+
if (await byRole.count() > 0) return byRole;
|
|
14
|
+
}
|
|
15
|
+
// Fallback: find by text content
|
|
16
|
+
if (ref.text) {
|
|
17
|
+
const byText = page.getByText(ref.text, { exact: false });
|
|
18
|
+
if (await byText.count() > 0) return byText;
|
|
19
|
+
}
|
|
20
|
+
// Fallback: find by placeholder
|
|
21
|
+
if (ref.placeholder) {
|
|
22
|
+
const byPlaceholder = page.getByPlaceholder(ref.placeholder, { exact: false });
|
|
23
|
+
if (await byPlaceholder.count() > 0) return byPlaceholder;
|
|
24
|
+
}
|
|
25
|
+
// Last resort: return the primary (will fail with original error)
|
|
26
|
+
return page.locator(primary);
|
|
27
|
+
}`.trim();
|
|
28
|
+
}
|
|
29
|
+
function sanitizeTestName(name) {
|
|
30
|
+
return name.replace(/[^a-zA-Z0-9 _-]/g, "").trim().slice(0, 80) || "Screen";
|
|
31
|
+
}
|
|
32
|
+
function escapeString(s) {
|
|
33
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/`/g, "\\`");
|
|
34
|
+
}
|
|
35
|
+
function generatePlaywrightTest(screens, opts = {}) {
|
|
36
|
+
const {
|
|
37
|
+
includeConsoleCheck = false,
|
|
38
|
+
testTimeout = 3e4,
|
|
39
|
+
projectName = "ZeTa Crawl",
|
|
40
|
+
useSmartLocators = true
|
|
41
|
+
} = opts;
|
|
42
|
+
const needsSmartLocator = useSmartLocators && screens.some((s) => s.smartLocator != null);
|
|
43
|
+
const lines = [
|
|
44
|
+
`// Generated by ZeTa Crawler \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
45
|
+
`// Project: ${escapeString(projectName)}`,
|
|
46
|
+
`// Screens: ${screens.length}`,
|
|
47
|
+
`// Run: npx playwright test`,
|
|
48
|
+
"",
|
|
49
|
+
"import { test, expect } from '@playwright/test';",
|
|
50
|
+
""
|
|
51
|
+
];
|
|
52
|
+
if (needsSmartLocator) {
|
|
53
|
+
lines.push(buildSmartLocatorHelper(), "");
|
|
54
|
+
}
|
|
55
|
+
lines.push(`test.setTimeout(${testTimeout});`, "");
|
|
56
|
+
if (includeConsoleCheck) {
|
|
57
|
+
lines.push(
|
|
58
|
+
`test.beforeEach(async ({ page }) => {`,
|
|
59
|
+
` const errors: string[] = [];`,
|
|
60
|
+
` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,
|
|
61
|
+
` page.on('pageerror', err => errors.push(err.message));`,
|
|
62
|
+
` (page as any).__consoleErrors = errors;`,
|
|
63
|
+
`});`,
|
|
64
|
+
""
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
for (const screen of screens) {
|
|
68
|
+
const testName = sanitizeTestName(screen.name);
|
|
69
|
+
lines.push(
|
|
70
|
+
`test('${escapeString(testName)}', async ({ page }) => {`,
|
|
71
|
+
` await page.goto('${escapeString(screen.url)}');`,
|
|
72
|
+
` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/);`
|
|
73
|
+
);
|
|
74
|
+
if (screen.title) {
|
|
75
|
+
lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/i);`);
|
|
76
|
+
}
|
|
77
|
+
if (screen.heading) {
|
|
78
|
+
const h = escapeString(screen.heading.slice(0, 60));
|
|
79
|
+
if (needsSmartLocator && screen.smartLocator) {
|
|
80
|
+
const tag = escapeString(screen.smartLocator.tag);
|
|
81
|
+
const text = escapeString(screen.smartLocator.text ?? "");
|
|
82
|
+
lines.push(
|
|
83
|
+
` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,
|
|
84
|
+
` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (includeConsoleCheck) {
|
|
91
|
+
lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
|
|
92
|
+
}
|
|
93
|
+
lines.push(`});`, "");
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
function generatePlaywrightConfig(opts = {}) {
|
|
98
|
+
const { baseUrl = "http://localhost:3000", reportDir = "playwright-report" } = opts;
|
|
99
|
+
return `// playwright.config.ts \u2014 generated by ZeTa Crawler
|
|
100
|
+
import { defineConfig } from '@playwright/test';
|
|
101
|
+
|
|
102
|
+
export default defineConfig({
|
|
103
|
+
testDir: './tests',
|
|
104
|
+
reporter: [['html', { outputFolder: '${reportDir}' }]],
|
|
105
|
+
use: {
|
|
106
|
+
baseURL: '${baseUrl}',
|
|
107
|
+
headless: true,
|
|
108
|
+
screenshot: 'only-on-failure',
|
|
109
|
+
video: 'retain-on-failure',
|
|
110
|
+
},
|
|
111
|
+
projects: [
|
|
112
|
+
{ name: 'chromium', use: { browserName: 'chromium' } },
|
|
113
|
+
{ name: 'firefox', use: { browserName: 'firefox' } },
|
|
114
|
+
{ name: 'webkit', use: { browserName: 'webkit' } },
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
generatePlaywrightConfig,
|
|
121
|
+
generatePlaywrightTest
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=playwright-recorder.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/playwright-recorder.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Playwright test recorder \u2014 generate Playwright test code from crawl data.\n *\n * Given a list of crawled screens, generates a minimal Playwright test file that:\n * 1. Navigates to each screen URL\n * 2. Verifies the page title\n * 3. Checks key elements visible on the page\n * 4. Verifies no console errors (optional)\n *\n * This is a \"smoke test\" generator \u2014 not a full interaction recorder.\n * Output: valid TypeScript Playwright test file ready to run with npx playwright test.\n */\n\nexport interface RecorderScreen {\n id: string;\n name: string;\n url: string;\n title?: string;\n heading?: string;\n markdownWordCount?: number;\n smartLocator?: {\n tag: string; // e.g. 'button', 'a', 'input'\n text?: string; // visible text content\n role?: string; // ARIA role\n placeholder?: string; // for inputs\n nearText?: string; // nearby label text\n };\n}\n\nexport interface RecorderOpts {\n baseUrl?: string;\n includeConsoleCheck?: boolean;\n testTimeout?: number; // ms, default 30000\n projectName?: string;\n useSmartLocators?: boolean; // default true\n}\n\nfunction buildSmartLocatorHelper(): string {\n return `\n// smartLocator \u2014 falls back to similarity search when primary selector fails.\n// Generated by ZeTa Crawler. Do not edit manually.\nasync function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {\n try {\n const el = page.locator(primary);\n if (await el.count() > 0) return el;\n } catch { /* fall through */ }\n // Fallback: find by ARIA role + text\n if (ref.role && ref.text) {\n const byRole = page.getByRole(ref.role as any, { name: ref.text, exact: false });\n if (await byRole.count() > 0) return byRole;\n }\n // Fallback: find by text content\n if (ref.text) {\n const byText = page.getByText(ref.text, { exact: false });\n if (await byText.count() > 0) return byText;\n }\n // Fallback: find by placeholder\n if (ref.placeholder) {\n const byPlaceholder = page.getByPlaceholder(ref.placeholder, { exact: false });\n if (await byPlaceholder.count() > 0) return byPlaceholder;\n }\n // Last resort: return the primary (will fail with original error)\n return page.locator(primary);\n}`.trim();\n}\n\nfunction sanitizeTestName(name: string): string {\n return name.replace(/[^a-zA-Z0-9 _-]/g, '').trim().slice(0, 80) || 'Screen';\n}\n\nfunction escapeString(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\").replace(/`/g, '\\\\`');\n}\n\nexport function generatePlaywrightTest(screens: RecorderScreen[], opts: RecorderOpts = {}): string {\n const {\n includeConsoleCheck = false,\n testTimeout = 30_000,\n projectName = 'ZeTa Crawl',\n useSmartLocators = true,\n } = opts;\n\n const needsSmartLocator = useSmartLocators && screens.some(s => s.smartLocator != null);\n\n const lines: string[] = [\n `// Generated by ZeTa Crawler \u2014 ${new Date().toISOString()}`,\n `// Project: ${escapeString(projectName)}`,\n `// Screens: ${screens.length}`,\n `// Run: npx playwright test`,\n '',\n \"import { test, expect } from '@playwright/test';\",\n '',\n ];\n\n if (needsSmartLocator) {\n lines.push(buildSmartLocatorHelper(), '');\n }\n\n lines.push(`test.setTimeout(${testTimeout});`, '');\n\n if (includeConsoleCheck) {\n lines.push(\n `test.beforeEach(async ({ page }) => {`,\n ` const errors: string[] = [];`,\n ` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,\n ` page.on('pageerror', err => errors.push(err.message));`,\n ` (page as any).__consoleErrors = errors;`,\n `});`,\n '',\n );\n }\n\n for (const screen of screens) {\n const testName = sanitizeTestName(screen.name);\n lines.push(\n `test('${escapeString(testName)}', async ({ page }) => {`,\n ` await page.goto('${escapeString(screen.url)}');`,\n ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))}/);`,\n );\n\n if (screen.title) {\n lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))}/i);`);\n }\n\n if (screen.heading) {\n const h = escapeString(screen.heading.slice(0, 60));\n if (needsSmartLocator && screen.smartLocator) {\n const tag = escapeString(screen.smartLocator.tag);\n const text = escapeString(screen.smartLocator.text ?? '');\n lines.push(\n ` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,\n ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`,\n );\n } else {\n lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);\n }\n }\n\n if (includeConsoleCheck) {\n lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);\n }\n\n lines.push(`});`, '');\n }\n\n return lines.join('\\n');\n}\n\nexport function generatePlaywrightConfig(opts: { baseUrl?: string; reportDir?: string } = {}): string {\n const { baseUrl = 'http://localhost:3000', reportDir = 'playwright-report' } = opts;\n return `// playwright.config.ts \u2014 generated by ZeTa Crawler\nimport { defineConfig } from '@playwright/test';\n\nexport default defineConfig({\n testDir: './tests',\n reporter: [['html', { outputFolder: '${reportDir}' }]],\n use: {\n baseURL: '${baseUrl}',\n headless: true,\n screenshot: 'only-on-failure',\n video: 'retain-on-failure',\n },\n projects: [\n { name: 'chromium', use: { browserName: 'chromium' } },\n { name: 'firefox', use: { browserName: 'firefox' } },\n { name: 'webkit', use: { browserName: 'webkit' } },\n ],\n});\n`;\n}\n"],
|
|
5
|
+
"mappings": "AAqCA,SAAS,0BAAkC;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyBN,KAAK;AACR;AAEA,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KAAK,QAAQ,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,KAAK;AACrE;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM,KAAK;AAC1E;AAEO,SAAS,uBAAuB,SAA2B,OAAqB,CAAC,GAAW;AACjG,QAAM;AAAA,IACJ,sBAAsB;AAAA,IACtB,cAAc;AAAA,IACd,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,IAAI;AAEJ,QAAM,oBAAoB,oBAAoB,QAAQ,KAAK,OAAK,EAAE,gBAAgB,IAAI;AAEtF,QAAM,QAAkB;AAAA,IACtB,wCAAkC,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IAC1D,eAAe,aAAa,WAAW,CAAC;AAAA,IACxC,eAAe,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,mBAAmB;AACrB,UAAM,KAAK,wBAAwB,GAAG,EAAE;AAAA,EAC1C;AAEA,QAAM,KAAK,mBAAmB,WAAW,MAAM,EAAE;AAEjD,MAAI,qBAAqB;AACvB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,iBAAiB,OAAO,IAAI;AAC7C,UAAM;AAAA,MACJ,SAAS,aAAa,QAAQ,CAAC;AAAA,MAC/B,sBAAsB,aAAa,OAAO,GAAG,CAAC;AAAA,MAC9C,mCAAmC,aAAa,OAAO,IAAI,QAAQ,uBAAuB,MAAM,CAAC,CAAC;AAAA,IACpG;AAEA,QAAI,OAAO,OAAO;AAChB,YAAM,KAAK,qCAAqC,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE,EAAE,QAAQ,uBAAuB,MAAM,CAAC,CAAC,MAAM;AAAA,IACtI;AAEA,QAAI,OAAO,SAAS;AAClB,YAAM,IAAI,aAAa,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC;AAClD,UAAI,qBAAqB,OAAO,cAAc;AAC5C,cAAM,MAAM,aAAa,OAAO,aAAa,GAAG;AAChD,cAAM,OAAO,aAAa,OAAO,aAAa,QAAQ,EAAE;AACxD,cAAM;AAAA,UACJ,sEAAsE,GAAG,aAAa,IAAI;AAAA,UAC1F,oDAAoD,CAAC;AAAA,QACvD;AAAA,MACF,OAAO;AACL,cAAM,KAAK,qEAAqE,CAAC,2BAA2B;AAAA,MAC9G;AAAA,IACF;AAEA,QAAI,qBAAqB;AACvB,YAAM,KAAK,gEAAgE;AAAA,IAC7E;AAEA,UAAM,KAAK,OAAO,EAAE;AAAA,EACtB;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,yBAAyB,OAAiD,CAAC,GAAW;AACpG,QAAM,EAAE,UAAU,yBAAyB,YAAY,oBAAoB,IAAI;AAC/E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,yCAKgC,SAAS;AAAA;AAAA,gBAElC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYvB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DomainProfile } from '../profiles/domain-profile.js';
|
|
2
|
+
export interface CrawlSafetyDecision {
|
|
3
|
+
allowed: boolean;
|
|
4
|
+
reason?: string;
|
|
5
|
+
severity: 'info' | 'warn' | 'block';
|
|
6
|
+
}
|
|
7
|
+
export declare function assessActionSafety(label: string, profile: DomainProfile): CrawlSafetyDecision;
|
|
8
|
+
export declare function redactSensitiveValue(value: string): string;
|
|
9
|
+
export declare function redactJson(value: unknown): unknown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DESTRUCTIVE_ACTION = /\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop)\b/i;
|
|
2
|
+
const SECRET_LIKE = /\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\b/i;
|
|
3
|
+
function assessActionSafety(label, profile) {
|
|
4
|
+
const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));
|
|
5
|
+
if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {
|
|
6
|
+
return {
|
|
7
|
+
allowed: false,
|
|
8
|
+
severity: "block",
|
|
9
|
+
reason: `Blocked risky action "${label}" for ${profile.industry} profile.`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return { allowed: true, severity: "info" };
|
|
13
|
+
}
|
|
14
|
+
function redactSensitiveValue(value) {
|
|
15
|
+
let redacted = value.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[email]").replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, "[phone-or-id]").replace(/\b(?:\d[ -]*?){13,19}\b/g, "[card-like-number]").replace(/\b[A-Za-z0-9_-]{24,}\b/g, "[token-like-value]");
|
|
16
|
+
if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\s*[^,\s}]+/g, ": [redacted]");
|
|
17
|
+
return redacted;
|
|
18
|
+
}
|
|
19
|
+
function redactJson(value) {
|
|
20
|
+
if (typeof value === "string") return redactSensitiveValue(value);
|
|
21
|
+
if (Array.isArray(value)) return value.map(redactJson);
|
|
22
|
+
if (value && typeof value === "object") {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [key, item] of Object.entries(value)) {
|
|
25
|
+
if (SECRET_LIKE.test(key)) out[key] = "[redacted]";
|
|
26
|
+
else out[key] = redactJson(item);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
assessActionSafety,
|
|
34
|
+
redactJson,
|
|
35
|
+
redactSensitiveValue
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=crawl-safety.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/policies/crawl-safety.ts"],
|
|
4
|
+
"sourcesContent": ["import type { DomainProfile } from '../profiles/domain-profile.js';\n\nexport interface CrawlSafetyDecision {\n allowed: boolean;\n reason?: string;\n severity: 'info' | 'warn' | 'block';\n}\n\nconst DESTRUCTIVE_ACTION = /\\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop)\\b/i;\nconst SECRET_LIKE = /\\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\\b/i;\n\nexport function assessActionSafety(label: string, profile: DomainProfile): CrawlSafetyDecision {\n const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));\n if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {\n return {\n allowed: false,\n severity: 'block',\n reason: `Blocked risky action \"${label}\" for ${profile.industry} profile.`,\n };\n }\n return { allowed: true, severity: 'info' };\n}\n\nexport function redactSensitiveValue(value: string): string {\n let redacted = value\n .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi, '[email]')\n .replace(/\\b(?:\\+?\\d[\\d -]{8,}\\d)\\b/g, '[phone-or-id]')\n .replace(/\\b(?:\\d[ -]*?){13,19}\\b/g, '[card-like-number]')\n .replace(/\\b[A-Za-z0-9_-]{24,}\\b/g, '[token-like-value]');\n if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\\s*[^,\\s}]+/g, ': [redacted]');\n return redacted;\n}\n\nexport function redactJson(value: unknown): unknown {\n if (typeof value === 'string') return redactSensitiveValue(value);\n if (Array.isArray(value)) return value.map(redactJson);\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value)) {\n if (SECRET_LIKE.test(key)) out[key] = '[redacted]';\n else out[key] = redactJson(item);\n }\n return out;\n }\n return value;\n}\n"],
|
|
5
|
+
"mappings": "AAQA,MAAM,qBAAqB;AAC3B,MAAM,cAAc;AAEb,SAAS,mBAAmB,OAAe,SAA6C;AAC7F,QAAM,iBAAiB,QAAQ,aAAa,KAAK,CAAC,SAAS,MAAM,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,CAAC;AAC3G,MAAI,mBAAmB,KAAK,KAAK,KAAK,gBAAgB;AACpD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU;AAAA,MACV,QAAQ,yBAAyB,KAAK,SAAS,QAAQ,QAAQ;AAAA,IACjE;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM,UAAU,OAAO;AAC3C;AAEO,SAAS,qBAAqB,OAAuB;AAC1D,MAAI,WAAW,MACZ,QAAQ,2CAA2C,SAAS,EAC5D,QAAQ,8BAA8B,eAAe,EACrD,QAAQ,4BAA4B,oBAAoB,EACxD,QAAQ,2BAA2B,oBAAoB;AAC1D,MAAI,YAAY,KAAK,QAAQ,EAAG,YAAW,SAAS,QAAQ,iBAAiB,cAAc;AAC3F,SAAO;AACT;AAEO,SAAS,WAAW,OAAyB;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,qBAAqB,KAAK;AAChE,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,UAAU;AACrD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC/C,UAAI,YAAY,KAAK,GAAG,EAAG,KAAI,GAAG,IAAI;AAAA,UACjC,KAAI,GAAG,IAAI,WAAW,IAAI;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Industry = 'ecommerce' | 'banking' | 'fintech' | 'healthcare' | 'pharma' | 'agriculture' | 'legacy-enterprise' | 'saas' | 'unknown';
|
|
2
|
+
export interface DomainProfile {
|
|
3
|
+
industry: Industry;
|
|
4
|
+
confidence: number;
|
|
5
|
+
importantScreenPatterns: string[];
|
|
6
|
+
workflowGoals: string[];
|
|
7
|
+
piiRules: string[];
|
|
8
|
+
riskyActions: string[];
|
|
9
|
+
safeProbeRules: string[];
|
|
10
|
+
requiredArtifacts: string[];
|
|
11
|
+
evidence: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function classifyDomainProfile(input: {
|
|
14
|
+
appUrl: string;
|
|
15
|
+
projectName?: string | null;
|
|
16
|
+
appType?: string | null;
|
|
17
|
+
seedText?: string;
|
|
18
|
+
}): DomainProfile;
|
|
19
|
+
export declare function getProfile(industry: Industry): DomainProfile;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const COMMON_REQUIRED_ARTIFACTS = [
|
|
2
|
+
"screenshot",
|
|
3
|
+
"dom",
|
|
4
|
+
"markdown",
|
|
5
|
+
"accessibility",
|
|
6
|
+
"network",
|
|
7
|
+
"performance"
|
|
8
|
+
];
|
|
9
|
+
const PROFILES = {
|
|
10
|
+
ecommerce: {
|
|
11
|
+
industry: "ecommerce",
|
|
12
|
+
importantScreenPatterns: ["catalog", "product", "search", "filter", "cart", "checkout", "payment", "order", "returns"],
|
|
13
|
+
workflowGoals: ["discover product catalogue", "map cart and checkout", "capture order confirmation and failure states", "map search/filter/sort"],
|
|
14
|
+
piiRules: ["mask email", "mask phone", "mask address", "mask card-like numbers"],
|
|
15
|
+
riskyActions: ["place order", "submit payment", "save card", "apply real coupon"],
|
|
16
|
+
safeProbeRules: ["do not click final payment/order buttons", "use invalid test card data only", "stop before irreversible purchase"],
|
|
17
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
18
|
+
},
|
|
19
|
+
banking: {
|
|
20
|
+
industry: "banking",
|
|
21
|
+
importantScreenPatterns: ["login", "mfa", "dashboard", "accounts", "transactions", "transfer", "beneficiary", "statement", "dispute"],
|
|
22
|
+
workflowGoals: ["map account overview", "map transactions and statements", "map transfer setup without submitting", "map MFA/session timeout states"],
|
|
23
|
+
piiRules: ["mask account numbers", "mask balances", "mask names", "mask addresses", "mask tax identifiers"],
|
|
24
|
+
riskyActions: ["transfer money", "add beneficiary", "submit dispute", "download sensitive statement"],
|
|
25
|
+
safeProbeRules: ["read-only by default", "never submit transfer confirmation", "never alter beneficiary data"],
|
|
26
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
27
|
+
},
|
|
28
|
+
fintech: {
|
|
29
|
+
industry: "fintech",
|
|
30
|
+
importantScreenPatterns: ["kyc", "wallet", "transactions", "cards", "payments", "payouts", "risk", "compliance", "disputes"],
|
|
31
|
+
workflowGoals: ["map KYC onboarding", "map wallet/payment states", "map failed payment and dispute flows", "capture compliance screens"],
|
|
32
|
+
piiRules: ["mask card numbers", "mask bank accounts", "mask identity documents", "mask balances"],
|
|
33
|
+
riskyActions: ["send payment", "withdraw funds", "change card status", "submit KYC with real data"],
|
|
34
|
+
safeProbeRules: ["sandbox data only", "stop before money movement", "never upload real identity documents"],
|
|
35
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
36
|
+
},
|
|
37
|
+
healthcare: {
|
|
38
|
+
industry: "healthcare",
|
|
39
|
+
importantScreenPatterns: ["patient", "appointment", "claims", "provider", "prescription", "records", "lab", "consent"],
|
|
40
|
+
workflowGoals: ["map patient lookup safely", "map appointment booking before final submit", "map claims and prescription screens", "capture consent and records UX"],
|
|
41
|
+
piiRules: ["mask PHI", "mask patient names", "mask MRN", "mask DOB", "mask diagnosis", "mask prescriptions"],
|
|
42
|
+
riskyActions: ["schedule real appointment", "submit claim", "change medication", "message provider with PHI"],
|
|
43
|
+
safeProbeRules: ["use synthetic patients only", "do not submit clinical changes", "avoid external notifications"],
|
|
44
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
45
|
+
},
|
|
46
|
+
pharma: {
|
|
47
|
+
industry: "pharma",
|
|
48
|
+
importantScreenPatterns: ["product", "dosage", "safety", "batch", "adverse-event", "regulatory", "trial", "label"],
|
|
49
|
+
workflowGoals: ["map product and safety content", "map adverse event intake before submit", "capture regulatory and batch workflows"],
|
|
50
|
+
piiRules: ["mask patient reports", "mask reporter contact", "mask trial participant identifiers"],
|
|
51
|
+
riskyActions: ["submit adverse event", "change safety record", "publish regulatory content"],
|
|
52
|
+
safeProbeRules: ["stop before regulatory or safety submission", "use synthetic reports only"],
|
|
53
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
54
|
+
},
|
|
55
|
+
agriculture: {
|
|
56
|
+
industry: "agriculture",
|
|
57
|
+
importantScreenPatterns: ["crop", "field", "weather", "inventory", "marketplace", "logistics", "sensor", "subsidy"],
|
|
58
|
+
workflowGoals: ["map crop and field planning", "map inventory/marketplace flows", "capture logistics and sensor dashboards"],
|
|
59
|
+
piiRules: ["mask farmer identity", "mask location precision", "mask subsidy identifiers"],
|
|
60
|
+
riskyActions: ["place market order", "submit subsidy application", "change sensor/device control"],
|
|
61
|
+
safeProbeRules: ["avoid irreversible orders", "avoid device actuation", "use test farm data"],
|
|
62
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
63
|
+
},
|
|
64
|
+
"legacy-enterprise": {
|
|
65
|
+
industry: "legacy-enterprise",
|
|
66
|
+
importantScreenPatterns: ["frameset", "iframe", "popup", "table", "search", "admin", "report", "export"],
|
|
67
|
+
workflowGoals: ["map menu trees", "capture iframe and popup states", "map table filters and reports", "detect session timeout"],
|
|
68
|
+
piiRules: ["mask table cells that look like PII", "mask exported identifiers"],
|
|
69
|
+
riskyActions: ["bulk update", "delete record", "export production data", "submit admin change"],
|
|
70
|
+
safeProbeRules: ["read-only table probing", "never click delete/bulk actions", "capture popups without committing"],
|
|
71
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
72
|
+
},
|
|
73
|
+
saas: {
|
|
74
|
+
industry: "saas",
|
|
75
|
+
importantScreenPatterns: ["dashboard", "settings", "users", "billing", "integrations", "reports", "admin", "workspace"],
|
|
76
|
+
workflowGoals: ["map workspace navigation", "map settings and integrations", "map role/user management without changing state", "capture dashboards and reports"],
|
|
77
|
+
piiRules: ["mask emails", "mask tokens", "mask API keys", "mask billing data"],
|
|
78
|
+
riskyActions: ["delete workspace", "invite real user", "rotate key", "change billing plan"],
|
|
79
|
+
safeProbeRules: ["never submit destructive settings", "redact secrets immediately", "use sandbox users"],
|
|
80
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
81
|
+
},
|
|
82
|
+
unknown: {
|
|
83
|
+
industry: "unknown",
|
|
84
|
+
importantScreenPatterns: ["login", "dashboard", "settings", "search", "detail", "report", "form"],
|
|
85
|
+
workflowGoals: ["map top-level navigation", "capture forms and validation states", "discover hidden menus", "classify domain after first pages"],
|
|
86
|
+
piiRules: ["mask emails", "mask phone numbers", "mask long identifiers", "mask card-like numbers"],
|
|
87
|
+
riskyActions: ["delete", "submit payment", "transfer", "publish", "send notification"],
|
|
88
|
+
safeProbeRules: ["read-only by default", "stop before irreversible actions", "use synthetic data only"],
|
|
89
|
+
requiredArtifacts: COMMON_REQUIRED_ARTIFACTS
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const INDUSTRY_HINTS = [
|
|
93
|
+
{ industry: "ecommerce", terms: [/cart/i, /checkout/i, /product/i, /catalog/i, /sku/i, /wishlist/i, /order/i] },
|
|
94
|
+
{ industry: "banking", terms: [/account/i, /statement/i, /beneficiar/i, /transfer/i, /iban/i, /routing/i, /balance/i] },
|
|
95
|
+
{ industry: "fintech", terms: [/wallet/i, /kyc/i, /payout/i, /card/i, /payment/i, /risk/i, /dispute/i] },
|
|
96
|
+
{ industry: "healthcare", terms: [/patient/i, /provider/i, /appointment/i, /claim/i, /prescription/i, /medical/i, /lab result/i] },
|
|
97
|
+
{ industry: "pharma", terms: [/pharma/i, /dosage/i, /adverse/i, /batch/i, /regulatory/i, /clinical trial/i, /label/i] },
|
|
98
|
+
{ industry: "agriculture", terms: [/crop/i, /field/i, /farm/i, /harvest/i, /sensor/i, /weather/i, /subsidy/i] },
|
|
99
|
+
{ industry: "legacy-enterprise", terms: [/frameset/i, /iframe/i, /popup/i, /legacy/i, /report export/i, /mainframe/i] },
|
|
100
|
+
{ industry: "saas", terms: [/workspace/i, /tenant/i, /integration/i, /billing/i, /api key/i, /admin/i, /dashboard/i] }
|
|
101
|
+
];
|
|
102
|
+
function classifyDomainProfile(input) {
|
|
103
|
+
const haystack = [input.appUrl, input.projectName ?? "", input.appType ?? "", input.seedText ?? ""].join(" ");
|
|
104
|
+
const scores = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const { industry, terms } of INDUSTRY_HINTS) {
|
|
106
|
+
for (const term of terms) {
|
|
107
|
+
if (term.test(haystack)) {
|
|
108
|
+
const prev = scores.get(industry) ?? { score: 0, evidence: [] };
|
|
109
|
+
prev.score += 1;
|
|
110
|
+
prev.evidence.push(term.source);
|
|
111
|
+
scores.set(industry, prev);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
let selected = "unknown";
|
|
116
|
+
let best = 0;
|
|
117
|
+
let evidence = [];
|
|
118
|
+
for (const [industry, result] of scores) {
|
|
119
|
+
if (result.score > best) {
|
|
120
|
+
selected = industry;
|
|
121
|
+
best = result.score;
|
|
122
|
+
evidence = result.evidence;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const base = PROFILES[selected];
|
|
126
|
+
return {
|
|
127
|
+
...base,
|
|
128
|
+
confidence: best === 0 ? 0.25 : Math.min(0.95, 0.35 + best * 0.15),
|
|
129
|
+
evidence
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function getProfile(industry) {
|
|
133
|
+
const base = PROFILES[industry] ?? PROFILES.unknown;
|
|
134
|
+
return { ...base, confidence: 1, evidence: ["explicit"] };
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
classifyDomainProfile,
|
|
138
|
+
getProfile
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=domain-profile.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/profiles/domain-profile.ts"],
|
|
4
|
+
"sourcesContent": ["export type Industry =\n | 'ecommerce'\n | 'banking'\n | 'fintech'\n | 'healthcare'\n | 'pharma'\n | 'agriculture'\n | 'legacy-enterprise'\n | 'saas'\n | 'unknown';\n\nexport interface DomainProfile {\n industry: Industry;\n confidence: number;\n importantScreenPatterns: string[];\n workflowGoals: string[];\n piiRules: string[];\n riskyActions: string[];\n safeProbeRules: string[];\n requiredArtifacts: string[];\n evidence: string[];\n}\n\nconst COMMON_REQUIRED_ARTIFACTS = [\n 'screenshot',\n 'dom',\n 'markdown',\n 'accessibility',\n 'network',\n 'performance',\n];\n\nconst PROFILES: Record<Industry, Omit<DomainProfile, 'confidence' | 'evidence'>> = {\n ecommerce: {\n industry: 'ecommerce',\n importantScreenPatterns: ['catalog', 'product', 'search', 'filter', 'cart', 'checkout', 'payment', 'order', 'returns'],\n workflowGoals: ['discover product catalogue', 'map cart and checkout', 'capture order confirmation and failure states', 'map search/filter/sort'],\n piiRules: ['mask email', 'mask phone', 'mask address', 'mask card-like numbers'],\n riskyActions: ['place order', 'submit payment', 'save card', 'apply real coupon'],\n safeProbeRules: ['do not click final payment/order buttons', 'use invalid test card data only', 'stop before irreversible purchase'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n banking: {\n industry: 'banking',\n importantScreenPatterns: ['login', 'mfa', 'dashboard', 'accounts', 'transactions', 'transfer', 'beneficiary', 'statement', 'dispute'],\n workflowGoals: ['map account overview', 'map transactions and statements', 'map transfer setup without submitting', 'map MFA/session timeout states'],\n piiRules: ['mask account numbers', 'mask balances', 'mask names', 'mask addresses', 'mask tax identifiers'],\n riskyActions: ['transfer money', 'add beneficiary', 'submit dispute', 'download sensitive statement'],\n safeProbeRules: ['read-only by default', 'never submit transfer confirmation', 'never alter beneficiary data'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n fintech: {\n industry: 'fintech',\n importantScreenPatterns: ['kyc', 'wallet', 'transactions', 'cards', 'payments', 'payouts', 'risk', 'compliance', 'disputes'],\n workflowGoals: ['map KYC onboarding', 'map wallet/payment states', 'map failed payment and dispute flows', 'capture compliance screens'],\n piiRules: ['mask card numbers', 'mask bank accounts', 'mask identity documents', 'mask balances'],\n riskyActions: ['send payment', 'withdraw funds', 'change card status', 'submit KYC with real data'],\n safeProbeRules: ['sandbox data only', 'stop before money movement', 'never upload real identity documents'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n healthcare: {\n industry: 'healthcare',\n importantScreenPatterns: ['patient', 'appointment', 'claims', 'provider', 'prescription', 'records', 'lab', 'consent'],\n workflowGoals: ['map patient lookup safely', 'map appointment booking before final submit', 'map claims and prescription screens', 'capture consent and records UX'],\n piiRules: ['mask PHI', 'mask patient names', 'mask MRN', 'mask DOB', 'mask diagnosis', 'mask prescriptions'],\n riskyActions: ['schedule real appointment', 'submit claim', 'change medication', 'message provider with PHI'],\n safeProbeRules: ['use synthetic patients only', 'do not submit clinical changes', 'avoid external notifications'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n pharma: {\n industry: 'pharma',\n importantScreenPatterns: ['product', 'dosage', 'safety', 'batch', 'adverse-event', 'regulatory', 'trial', 'label'],\n workflowGoals: ['map product and safety content', 'map adverse event intake before submit', 'capture regulatory and batch workflows'],\n piiRules: ['mask patient reports', 'mask reporter contact', 'mask trial participant identifiers'],\n riskyActions: ['submit adverse event', 'change safety record', 'publish regulatory content'],\n safeProbeRules: ['stop before regulatory or safety submission', 'use synthetic reports only'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n agriculture: {\n industry: 'agriculture',\n importantScreenPatterns: ['crop', 'field', 'weather', 'inventory', 'marketplace', 'logistics', 'sensor', 'subsidy'],\n workflowGoals: ['map crop and field planning', 'map inventory/marketplace flows', 'capture logistics and sensor dashboards'],\n piiRules: ['mask farmer identity', 'mask location precision', 'mask subsidy identifiers'],\n riskyActions: ['place market order', 'submit subsidy application', 'change sensor/device control'],\n safeProbeRules: ['avoid irreversible orders', 'avoid device actuation', 'use test farm data'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n 'legacy-enterprise': {\n industry: 'legacy-enterprise',\n importantScreenPatterns: ['frameset', 'iframe', 'popup', 'table', 'search', 'admin', 'report', 'export'],\n workflowGoals: ['map menu trees', 'capture iframe and popup states', 'map table filters and reports', 'detect session timeout'],\n piiRules: ['mask table cells that look like PII', 'mask exported identifiers'],\n riskyActions: ['bulk update', 'delete record', 'export production data', 'submit admin change'],\n safeProbeRules: ['read-only table probing', 'never click delete/bulk actions', 'capture popups without committing'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n saas: {\n industry: 'saas',\n importantScreenPatterns: ['dashboard', 'settings', 'users', 'billing', 'integrations', 'reports', 'admin', 'workspace'],\n workflowGoals: ['map workspace navigation', 'map settings and integrations', 'map role/user management without changing state', 'capture dashboards and reports'],\n piiRules: ['mask emails', 'mask tokens', 'mask API keys', 'mask billing data'],\n riskyActions: ['delete workspace', 'invite real user', 'rotate key', 'change billing plan'],\n safeProbeRules: ['never submit destructive settings', 'redact secrets immediately', 'use sandbox users'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n unknown: {\n industry: 'unknown',\n importantScreenPatterns: ['login', 'dashboard', 'settings', 'search', 'detail', 'report', 'form'],\n workflowGoals: ['map top-level navigation', 'capture forms and validation states', 'discover hidden menus', 'classify domain after first pages'],\n piiRules: ['mask emails', 'mask phone numbers', 'mask long identifiers', 'mask card-like numbers'],\n riskyActions: ['delete', 'submit payment', 'transfer', 'publish', 'send notification'],\n safeProbeRules: ['read-only by default', 'stop before irreversible actions', 'use synthetic data only'],\n requiredArtifacts: COMMON_REQUIRED_ARTIFACTS,\n },\n};\n\nconst INDUSTRY_HINTS: Array<{ industry: Industry; terms: RegExp[] }> = [\n { industry: 'ecommerce', terms: [/cart/i, /checkout/i, /product/i, /catalog/i, /sku/i, /wishlist/i, /order/i] },\n { industry: 'banking', terms: [/account/i, /statement/i, /beneficiar/i, /transfer/i, /iban/i, /routing/i, /balance/i] },\n { industry: 'fintech', terms: [/wallet/i, /kyc/i, /payout/i, /card/i, /payment/i, /risk/i, /dispute/i] },\n { industry: 'healthcare', terms: [/patient/i, /provider/i, /appointment/i, /claim/i, /prescription/i, /medical/i, /lab result/i] },\n { industry: 'pharma', terms: [/pharma/i, /dosage/i, /adverse/i, /batch/i, /regulatory/i, /clinical trial/i, /label/i] },\n { industry: 'agriculture', terms: [/crop/i, /field/i, /farm/i, /harvest/i, /sensor/i, /weather/i, /subsidy/i] },\n { industry: 'legacy-enterprise', terms: [/frameset/i, /iframe/i, /popup/i, /legacy/i, /report export/i, /mainframe/i] },\n { industry: 'saas', terms: [/workspace/i, /tenant/i, /integration/i, /billing/i, /api key/i, /admin/i, /dashboard/i] },\n];\n\nexport function classifyDomainProfile(input: {\n appUrl: string;\n projectName?: string | null;\n appType?: string | null;\n seedText?: string;\n}): DomainProfile {\n const haystack = [input.appUrl, input.projectName ?? '', input.appType ?? '', input.seedText ?? ''].join(' ');\n const scores = new Map<Industry, { score: number; evidence: string[] }>();\n for (const { industry, terms } of INDUSTRY_HINTS) {\n for (const term of terms) {\n if (term.test(haystack)) {\n const prev = scores.get(industry) ?? { score: 0, evidence: [] };\n prev.score += 1;\n prev.evidence.push(term.source);\n scores.set(industry, prev);\n }\n }\n }\n let selected: Industry = 'unknown';\n let best = 0;\n let evidence: string[] = [];\n for (const [industry, result] of scores) {\n if (result.score > best) {\n selected = industry;\n best = result.score;\n evidence = result.evidence;\n }\n }\n const base = PROFILES[selected];\n return {\n ...base,\n confidence: best === 0 ? 0.25 : Math.min(0.95, 0.35 + best * 0.15),\n evidence,\n };\n}\n\nexport function getProfile(industry: Industry): DomainProfile {\n const base = PROFILES[industry] ?? PROFILES.unknown;\n return { ...base, confidence: 1, evidence: ['explicit'] };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free proxy auto-selection for bot-protection bypass.
|
|
3
|
+
*
|
|
4
|
+
* Source: proxifly/free-proxy-list (MIT) via jsDelivr CDN.
|
|
5
|
+
* These are datacenter proxies — they bypass simple IP blocks but NOT
|
|
6
|
+
* full Cloudflare Bot Manager / Akamai. For Cloudflare-protected sites,
|
|
7
|
+
* users need a residential proxy (Bright Data, Oxylabs, etc.).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Pick a working free proxy. Tests in parallel batches of 10 with a 4s timeout.
|
|
11
|
+
* Returns null if no working proxy found (not an error — crawl can proceed without proxy).
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickFreeProxy(): Promise<string | null>;
|
|
14
|
+
export type ProxyTier = 'free' | 'datacenter' | 'residential' | 'premium';
|
|
15
|
+
export interface ProxyTierConfig {
|
|
16
|
+
/** List of proxy URLs for this tier. E.g. ['http://user:pass@dc1.proxy:8080'] */
|
|
17
|
+
urls: string[];
|
|
18
|
+
tier: ProxyTier;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tiered proxy manager with per-domain error histograms.
|
|
22
|
+
*
|
|
23
|
+
* Tracks success/failure per domain and automatically escalates to higher proxy tiers
|
|
24
|
+
* on consecutive failures. Applies time-decay to probe returning to lower tiers.
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* const mgr = new TieredProxyManager([{ tier: 'datacenter', urls: [...] }]);
|
|
28
|
+
* const proxy = await mgr.selectProxy('api.salesforce.com');
|
|
29
|
+
* // ... make request ...
|
|
30
|
+
* mgr.recordResult('api.salesforce.com', success);
|
|
31
|
+
*/
|
|
32
|
+
export declare class TieredProxyManager {
|
|
33
|
+
private tiers;
|
|
34
|
+
private domainHistory;
|
|
35
|
+
private readonly ESCALATE_AFTER;
|
|
36
|
+
private readonly DECAY_AFTER_MS;
|
|
37
|
+
constructor(configs: ProxyTierConfig[]);
|
|
38
|
+
private getHistory;
|
|
39
|
+
private lowestAvailableTier;
|
|
40
|
+
/** Pick a proxy URL for this domain, based on its tier history. */
|
|
41
|
+
selectProxy(domain: string): string | null;
|
|
42
|
+
/** Record a request result for a domain. Triggers tier escalation on repeated failures. */
|
|
43
|
+
recordResult(domain: string, success: boolean): void;
|
|
44
|
+
stats(): Array<{
|
|
45
|
+
domain: string;
|
|
46
|
+
tier: ProxyTier;
|
|
47
|
+
successRate: number;
|
|
48
|
+
totalRequests: number;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createConnection } from "net";
|
|
2
|
+
const PROXY_LIST_URL = "https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/all/data.json";
|
|
3
|
+
async function fetchCandidates() {
|
|
4
|
+
const res = await fetch(PROXY_LIST_URL, {
|
|
5
|
+
signal: AbortSignal.timeout(1e4),
|
|
6
|
+
headers: { "Accept": "application/json" }
|
|
7
|
+
});
|
|
8
|
+
if (!res.ok) throw new Error(`proxy list fetch failed: ${res.status}`);
|
|
9
|
+
const raw = await res.json();
|
|
10
|
+
return raw.filter((p) => p.protocols?.includes("http") && p.port > 0 && (p.upTime ?? 0) >= 30).sort((a, b) => (a.speed ?? 99999) - (b.speed ?? 99999)).slice(0, 50);
|
|
11
|
+
}
|
|
12
|
+
function testProxy(ip, port, timeoutMs) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
let settled = false;
|
|
15
|
+
const settle = (ok) => {
|
|
16
|
+
if (settled) return;
|
|
17
|
+
settled = true;
|
|
18
|
+
resolve(ok);
|
|
19
|
+
try {
|
|
20
|
+
sock.destroy();
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const timer = setTimeout(() => settle(false), timeoutMs);
|
|
25
|
+
const sock = createConnection({ host: ip, port, timeout: timeoutMs });
|
|
26
|
+
sock.on("connect", () => {
|
|
27
|
+
sock.write("CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n");
|
|
28
|
+
sock.once("data", (data) => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
const ok = /^HTTP\/1\.[01] 200/.test(data.toString());
|
|
31
|
+
settle(ok);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
sock.on("error", () => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
settle(false);
|
|
37
|
+
});
|
|
38
|
+
sock.on("timeout", () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
settle(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function testBatch(candidates, start, batchSize, timeoutMs) {
|
|
45
|
+
const batch = candidates.slice(start, start + batchSize);
|
|
46
|
+
if (batch.length === 0) return null;
|
|
47
|
+
const results = await Promise.all(
|
|
48
|
+
batch.map(async (p) => {
|
|
49
|
+
const ok = await testProxy(p.ip, p.port, timeoutMs);
|
|
50
|
+
return ok ? `http://${p.ip}:${p.port}` : null;
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
return results.find((r) => r !== null) ?? null;
|
|
54
|
+
}
|
|
55
|
+
async function pickFreeProxy() {
|
|
56
|
+
let candidates;
|
|
57
|
+
try {
|
|
58
|
+
candidates = await fetchCandidates();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn("[proxy] failed to fetch proxy list:", err?.message ?? err);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const BATCH = 10;
|
|
64
|
+
const TIMEOUT = 4e3;
|
|
65
|
+
for (let i = 0; i < candidates.length; i += BATCH) {
|
|
66
|
+
const found = await testBatch(candidates, i, BATCH, TIMEOUT);
|
|
67
|
+
if (found) {
|
|
68
|
+
console.info(`[proxy] selected free proxy: ${found} (tested ${i + BATCH} candidates)`);
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.warn("[proxy] no working free proxy found in list");
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const TIER_ORDER = ["free", "datacenter", "residential", "premium"];
|
|
76
|
+
function nextTier(current) {
|
|
77
|
+
const idx = TIER_ORDER.indexOf(current);
|
|
78
|
+
return TIER_ORDER[Math.min(idx + 1, TIER_ORDER.length - 1)];
|
|
79
|
+
}
|
|
80
|
+
class TieredProxyManager {
|
|
81
|
+
tiers = /* @__PURE__ */ new Map();
|
|
82
|
+
domainHistory = /* @__PURE__ */ new Map();
|
|
83
|
+
ESCALATE_AFTER = 3;
|
|
84
|
+
// consecutive failures before escalation
|
|
85
|
+
DECAY_AFTER_MS = 30 * 60 * 1e3;
|
|
86
|
+
// 30 min: probe tier downgrade
|
|
87
|
+
constructor(configs) {
|
|
88
|
+
for (const cfg of configs) {
|
|
89
|
+
if (cfg.urls.length > 0) this.tiers.set(cfg.tier, cfg.urls);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
getHistory(domain) {
|
|
93
|
+
if (!this.domainHistory.has(domain)) {
|
|
94
|
+
this.domainHistory.set(domain, {
|
|
95
|
+
tier: this.lowestAvailableTier(),
|
|
96
|
+
consecutiveFailures: 0,
|
|
97
|
+
lastEscalatedAt: 0,
|
|
98
|
+
totalRequests: 0,
|
|
99
|
+
successRate: 1
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return this.domainHistory.get(domain);
|
|
103
|
+
}
|
|
104
|
+
lowestAvailableTier() {
|
|
105
|
+
for (const t of TIER_ORDER) {
|
|
106
|
+
if (this.tiers.has(t)) return t;
|
|
107
|
+
}
|
|
108
|
+
return "free";
|
|
109
|
+
}
|
|
110
|
+
/** Pick a proxy URL for this domain, based on its tier history. */
|
|
111
|
+
selectProxy(domain) {
|
|
112
|
+
const history = this.getHistory(domain);
|
|
113
|
+
if (history.consecutiveFailures === 0 && history.lastEscalatedAt > 0) {
|
|
114
|
+
const elapsed = Date.now() - history.lastEscalatedAt;
|
|
115
|
+
if (elapsed > this.DECAY_AFTER_MS) {
|
|
116
|
+
const currentIdx = TIER_ORDER.indexOf(history.tier);
|
|
117
|
+
if (currentIdx > 0) {
|
|
118
|
+
const lowerTier = TIER_ORDER[currentIdx - 1];
|
|
119
|
+
if (this.tiers.has(lowerTier)) {
|
|
120
|
+
history.tier = lowerTier;
|
|
121
|
+
history.lastEscalatedAt = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const urls = this.tiers.get(history.tier);
|
|
127
|
+
if (!urls || urls.length === 0) return null;
|
|
128
|
+
const idx = history.totalRequests % urls.length;
|
|
129
|
+
return urls[idx];
|
|
130
|
+
}
|
|
131
|
+
/** Record a request result for a domain. Triggers tier escalation on repeated failures. */
|
|
132
|
+
recordResult(domain, success) {
|
|
133
|
+
const history = this.getHistory(domain);
|
|
134
|
+
history.totalRequests++;
|
|
135
|
+
if (success) {
|
|
136
|
+
history.consecutiveFailures = 0;
|
|
137
|
+
history.successRate = history.successRate * 0.9 + 0.1;
|
|
138
|
+
} else {
|
|
139
|
+
history.consecutiveFailures++;
|
|
140
|
+
history.successRate = history.successRate * 0.9;
|
|
141
|
+
if (history.consecutiveFailures >= this.ESCALATE_AFTER) {
|
|
142
|
+
const elevated = nextTier(history.tier);
|
|
143
|
+
if (elevated !== history.tier && this.tiers.has(elevated)) {
|
|
144
|
+
console.warn(`[proxy-tier] domain=${domain} escalating ${history.tier}\u2192${elevated} after ${history.consecutiveFailures} failures`);
|
|
145
|
+
history.tier = elevated;
|
|
146
|
+
history.lastEscalatedAt = Date.now();
|
|
147
|
+
history.consecutiveFailures = 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
stats() {
|
|
153
|
+
return Array.from(this.domainHistory.entries()).map(([domain, h]) => ({
|
|
154
|
+
domain,
|
|
155
|
+
tier: h.tier,
|
|
156
|
+
successRate: Math.round(h.successRate * 100) / 100,
|
|
157
|
+
totalRequests: h.totalRequests
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
TieredProxyManager,
|
|
163
|
+
pickFreeProxy
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=proxy.js.map
|