@taodigital/crawler 0.0.0-stage → 1.2.1
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 +82 -0
- package/README.md +10 -2
- 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/accessibility-tree-crawler.d.ts +44 -0
- package/dist/accessibility-tree-crawler.js +156 -0
- package/dist/accessibility-tree-crawler.js.map +7 -0
- package/dist/advanced-ui-probes.d.ts +47 -0
- package/dist/advanced-ui-probes.js +310 -0
- package/dist/advanced-ui-probes.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +250 -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 +348 -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/crawl-session-manager.d.ts +44 -0
- package/dist/crawl-session-manager.js +145 -0
- package/dist/crawl-session-manager.js.map +7 -0
- package/dist/crawler.d.ts +233 -0
- package/dist/crawler.js +6239 -0
- package/dist/crawler.js.map +7 -0
- package/dist/crud-interaction.d.ts +31 -0
- package/dist/crud-interaction.js +264 -0
- package/dist/crud-interaction.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/dropdown-enumerator.d.ts +20 -0
- package/dist/dropdown-enumerator.js +230 -0
- package/dist/dropdown-enumerator.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 +64 -0
- package/dist/extractors/advanced.js +265 -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/extractors/branding-extractor.d.ts +46 -0
- package/dist/extractors/branding-extractor.js +104 -0
- package/dist/extractors/branding-extractor.js.map +7 -0
- package/dist/extractors/summary-extractor.d.ts +10 -0
- package/dist/extractors/summary-extractor.js +36 -0
- package/dist/extractors/summary-extractor.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +42 -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/filter-pagination-crawler.d.ts +52 -0
- package/dist/filter-pagination-crawler.js +299 -0
- package/dist/filter-pagination-crawler.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 +40 -0
- package/dist/frontier.js +103 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +36 -0
- package/dist/gap-pass.js +115 -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 +318 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +116 -0
- package/dist/incremental-diff.js +137 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +83 -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 +144 -0
- package/dist/interactive-probe.js +865 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +297 -0
- package/dist/job-queue.js +3812 -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 +105 -0
- package/dist/live-view.js.map +7 -0
- package/dist/locator-extractor.d.ts +46 -0
- package/dist/locator-extractor.js +220 -0
- package/dist/locator-extractor.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/multi-context-handler.d.ts +55 -0
- package/dist/multi-context-handler.js +182 -0
- package/dist/multi-context-handler.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/nested-window-crawler.d.ts +41 -0
- package/dist/nested-window-crawler.js +172 -0
- package/dist/nested-window-crawler.js.map +7 -0
- package/dist/network-action-correlator.d.ts +47 -0
- package/dist/network-action-correlator.js +209 -0
- package/dist/network-action-correlator.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-collector.d.ts +50 -0
- package/dist/performance-metrics-collector.js +155 -0
- package/dist/performance-metrics-collector.js.map +7 -0
- package/dist/performance-metrics.d.ts +52 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +50 -0
- package/dist/phash.js +136 -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 +41 -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 +160 -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 +15 -0
- package/dist/screen-reader.js +48 -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/service-worker-interceptor.d.ts +36 -0
- package/dist/service-worker-interceptor.js +136 -0
- package/dist/service-worker-interceptor.js.map +7 -0
- package/dist/session-capture.d.ts +39 -0
- package/dist/session-capture.js +274 -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/shadow-dom-crawler.d.ts +35 -0
- package/dist/shadow-dom-crawler.js +132 -0
- package/dist/shadow-dom-crawler.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 +160 -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 +13 -0
- package/dist/sqs-queue.js +115 -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/state-graph.d.ts +59 -0
- package/dist/state-graph.js +193 -0
- package/dist/state-graph.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +202 -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 +81 -0
- package/dist/test-executor.js +443 -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 +122 -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 +27 -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 +49 -4
- package/src/accessibility-auditor.ts +102 -0
- package/src/accessibility-tree-crawler.ts +246 -0
- package/src/advanced-ui-probes.ts +470 -0
- package/src/ai-login.ts +298 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +418 -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/crawl-session-manager.ts +231 -0
- package/src/crawler.ts +7123 -0
- package/src/crawler.ts.rej +12 -0
- package/src/crud-interaction.ts +386 -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/dropdown-enumerator.ts +312 -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 +309 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/extractors/branding-extractor.ts +159 -0
- package/src/extractors/summary-extractor.ts +49 -0
- package/src/failure-analysis-agent.ts +51 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/filter-pagination-crawler.ts +500 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +144 -0
- package/src/gap-pass.ts +165 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +453 -0
- package/src/incremental-diff.ts +259 -0
- package/src/index.ts +52 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +1136 -0
- package/src/job-queue.test.ts +187 -0
- package/src/job-queue.ts +4504 -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 +146 -0
- package/src/locator-extractor.ts +309 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/multi-context-handler.ts +296 -0
- package/src/native-crawler.ts +1108 -0
- package/src/nested-window-crawler.ts +310 -0
- package/src/network-action-correlator.ts +320 -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-collector.ts +243 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +184 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +52 -0
- package/src/profiles/domain-profile.ts +190 -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 +98 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/service-worker-interceptor.ts +185 -0
- package/src/session-capture.ts +335 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/shadow-dom-crawler.ts +198 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +127 -0
- package/src/sitemap.ts +233 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +129 -0
- package/src/sso-token.ts +120 -0
- package/src/state-graph.ts +280 -0
- package/src/stealth/human-behavior.ts +227 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +578 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +161 -0
- package/src/vendor.d.ts +20 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +59 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +50 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/accessibility-tree-crawler.d.ts +44 -0
- package/types/advanced-ui-probes.d.ts +47 -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/crawl-session-manager.d.ts +44 -0
- package/types/crawler.d.ts +233 -0
- package/types/crud-interaction.d.ts +31 -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/dropdown-enumerator.d.ts +20 -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 +64 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/extractors/branding-extractor.d.ts +46 -0
- package/types/extractors/summary-extractor.d.ts +10 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/filter-pagination-crawler.d.ts +52 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +40 -0
- package/types/gap-pass.d.ts +36 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +116 -0
- package/types/index.d.ts +52 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +144 -0
- package/types/job-queue.d.ts +308 -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/locator-extractor.d.ts +46 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/multi-context-handler.d.ts +55 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/nested-window-crawler.d.ts +41 -0
- package/types/network-action-correlator.d.ts +47 -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-collector.d.ts +50 -0
- package/types/performance-metrics.d.ts +52 -0
- package/types/phash.d.ts +50 -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 +15 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/service-worker-interceptor.d.ts +36 -0
- package/types/session-capture.d.ts +39 -0
- package/types/session-export.d.ts +49 -0
- package/types/shadow-dom-crawler.d.ts +35 -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 +13 -0
- package/types/sso-token.d.ts +29 -0
- package/types/state-graph.d.ts +59 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +84 -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,160 @@
|
|
|
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 BASE_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
|
+
const _extraHints = (() => {
|
|
103
|
+
const raw = process.env.EXTRA_INDUSTRY_HINTS_JSON;
|
|
104
|
+
if (!raw) return [];
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(raw);
|
|
107
|
+
return parsed.map((h) => ({ industry: h.industry, terms: h.terms.map((t) => new RegExp(t, "i")) }));
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
})();
|
|
112
|
+
const INDUSTRY_HINTS = [...BASE_INDUSTRY_HINTS, ..._extraHints];
|
|
113
|
+
const _profileOverrides = (() => {
|
|
114
|
+
const raw = process.env.DOMAIN_PROFILE_OVERRIDES_JSON;
|
|
115
|
+
if (!raw) return {};
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(raw);
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
function classifyDomainProfile(input) {
|
|
123
|
+
const haystack = [input.appUrl, input.projectName ?? "", input.appType ?? "", input.seedText ?? ""].join(" ");
|
|
124
|
+
const scores = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const { industry, terms } of INDUSTRY_HINTS) {
|
|
126
|
+
for (const term of terms) {
|
|
127
|
+
if (term.test(haystack)) {
|
|
128
|
+
const prev = scores.get(industry) ?? { score: 0, evidence: [] };
|
|
129
|
+
prev.score += 1;
|
|
130
|
+
prev.evidence.push(term.source);
|
|
131
|
+
scores.set(industry, prev);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let selected = "unknown";
|
|
136
|
+
let best = 0;
|
|
137
|
+
let evidence = [];
|
|
138
|
+
for (const [industry, result] of scores) {
|
|
139
|
+
if (result.score > best) {
|
|
140
|
+
selected = industry;
|
|
141
|
+
best = result.score;
|
|
142
|
+
evidence = result.evidence;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const base = PROFILES[selected];
|
|
146
|
+
return {
|
|
147
|
+
...base,
|
|
148
|
+
confidence: best === 0 ? 0.25 : Math.min(0.95, 0.35 + best * 0.15),
|
|
149
|
+
evidence
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function getProfile(industry) {
|
|
153
|
+
const base = PROFILES[industry] ?? PROFILES.unknown;
|
|
154
|
+
return { ...base, confidence: 1, evidence: ["explicit"] };
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
classifyDomainProfile,
|
|
158
|
+
getProfile
|
|
159
|
+
};
|
|
160
|
+
//# 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 BASE_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\n// Allow additional industry hint keywords via ENV without replacing the baseline.\n// Format: JSON array of { industry: string; terms: string[] } \u2014 terms are treated as case-insensitive regex.\n// Example: EXTRA_INDUSTRY_HINTS_JSON='[{\"industry\":\"saas\",\"terms\":[\"platform\",\"multitenant\"]}]'\nconst _extraHints: Array<{ industry: Industry; terms: RegExp[] }> = (() => {\n const raw = process.env.EXTRA_INDUSTRY_HINTS_JSON;\n if (!raw) return [];\n try {\n const parsed: Array<{ industry: string; terms: string[] }> = JSON.parse(raw);\n return parsed.map(h => ({ industry: h.industry as Industry, terms: h.terms.map(t => new RegExp(t, 'i')) }));\n } catch { return []; }\n})();\n\nconst INDUSTRY_HINTS = [...BASE_INDUSTRY_HINTS, ..._extraHints];\n\n// Allow patching profile fields (riskyActions, piiRules, etc.) per industry via ENV.\n// Format: JSON object keyed by industry, value is partial DomainProfile fields to merge.\n// Example: DOMAIN_PROFILE_OVERRIDES_JSON='{\"saas\":{\"riskyActions\":[\"delete workspace\",\"export all data\"]}}'\nconst _profileOverrides: Partial<Record<Industry, Partial<Omit<DomainProfile, 'confidence' | 'evidence'>>>> = (() => {\n const raw = process.env.DOMAIN_PROFILE_OVERRIDES_JSON;\n if (!raw) return {};\n try { return JSON.parse(raw); } catch { return {}; }\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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/proxy.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Free proxy auto-selection for bot-protection bypass.\n *\n * Source: proxifly/free-proxy-list (MIT) via jsDelivr CDN.\n * These are datacenter proxies \u2014 they bypass simple IP blocks but NOT\n * full Cloudflare Bot Manager / Akamai. For Cloudflare-protected sites,\n * users need a residential proxy (Bright Data, Oxylabs, etc.).\n */\n\nimport { createConnection } from 'net';\n\nconst PROXY_LIST_URL =\n 'https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/all/data.json';\n\ninterface ProxyEntry {\n ip: string;\n port: number;\n protocols: string[];\n upTime?: number;\n speed?: number;\n}\n\n/** Fetch and filter proxy list. Returns http proxies sorted by speed (fastest first). */\nasync function fetchCandidates(): Promise<ProxyEntry[]> {\n const res = await fetch(PROXY_LIST_URL, {\n signal: AbortSignal.timeout(10_000),\n headers: { 'Accept': 'application/json' },\n });\n if (!res.ok) throw new Error(`proxy list fetch failed: ${res.status}`);\n const raw: ProxyEntry[] = await res.json();\n return raw\n .filter((p) => p.protocols?.includes('http') && p.port > 0 && (p.upTime ?? 0) >= 30)\n .sort((a, b) => (a.speed ?? 99999) - (b.speed ?? 99999))\n .slice(0, 50); // test at most 50 candidates\n}\n\n/** TCP-level proxy liveness check via HTTP CONNECT tunnel. No extra dependencies. */\nfunction testProxy(ip: string, port: number, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n let settled = false;\n const settle = (ok: boolean) => {\n if (settled) return;\n settled = true;\n resolve(ok);\n try { sock.destroy(); } catch { /* ignore */ }\n };\n\n const timer = setTimeout(() => settle(false), timeoutMs);\n const sock = createConnection({ host: ip, port, timeout: timeoutMs });\n\n sock.on('connect', () => {\n sock.write('CONNECT example.com:80 HTTP/1.1\\r\\nHost: example.com:80\\r\\n\\r\\n');\n sock.once('data', (data) => {\n clearTimeout(timer);\n const ok = /^HTTP\\/1\\.[01] 200/.test(data.toString());\n settle(ok);\n });\n });\n sock.on('error', () => { clearTimeout(timer); settle(false); });\n sock.on('timeout', () => { clearTimeout(timer); settle(false); });\n });\n}\n\n/** Test `batchSize` proxies concurrently and return first working one. */\nasync function testBatch(\n candidates: ProxyEntry[],\n start: number,\n batchSize: number,\n timeoutMs: number,\n): Promise<string | null> {\n const batch = candidates.slice(start, start + batchSize);\n if (batch.length === 0) return null;\n\n const results = await Promise.all(\n batch.map(async (p) => {\n const ok = await testProxy(p.ip, p.port, timeoutMs);\n return ok ? `http://${p.ip}:${p.port}` : null;\n }),\n );\n\n return results.find((r) => r !== null) ?? null;\n}\n\n/**\n * Pick a working free proxy. Tests in parallel batches of 10 with a 4s timeout.\n * Returns null if no working proxy found (not an error \u2014 crawl can proceed without proxy).\n */\nexport async function pickFreeProxy(): Promise<string | null> {\n let candidates: ProxyEntry[];\n try {\n candidates = await fetchCandidates();\n } catch (err: any) {\n console.warn('[proxy] failed to fetch proxy list:', err?.message ?? err);\n return null;\n }\n\n const BATCH = 10;\n const TIMEOUT = 4_000;\n\n for (let i = 0; i < candidates.length; i += BATCH) {\n const found = await testBatch(candidates, i, BATCH, TIMEOUT);\n if (found) {\n console.info(`[proxy] selected free proxy: ${found} (tested ${i + BATCH} candidates)`);\n return found;\n }\n }\n\n console.warn('[proxy] no working free proxy found in list');\n return null;\n}\n\n// \u2500\u2500 Tiered Proxy Rotation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type ProxyTier = 'free' | 'datacenter' | 'residential' | 'premium';\n\nexport interface ProxyTierConfig {\n /** List of proxy URLs for this tier. E.g. ['http://user:pass@dc1.proxy:8080'] */\n urls: string[];\n tier: ProxyTier;\n}\n\ninterface DomainHistory {\n tier: ProxyTier;\n consecutiveFailures: number;\n lastEscalatedAt: number;\n totalRequests: number;\n successRate: number;\n}\n\nconst TIER_ORDER: ProxyTier[] = ['free', 'datacenter', 'residential', 'premium'];\n\nfunction nextTier(current: ProxyTier): ProxyTier {\n const idx = TIER_ORDER.indexOf(current);\n return TIER_ORDER[Math.min(idx + 1, TIER_ORDER.length - 1)];\n}\n\n/**\n * Tiered proxy manager with per-domain error histograms.\n *\n * Tracks success/failure per domain and automatically escalates to higher proxy tiers\n * on consecutive failures. Applies time-decay to probe returning to lower tiers.\n *\n * Usage:\n * const mgr = new TieredProxyManager([{ tier: 'datacenter', urls: [...] }]);\n * const proxy = await mgr.selectProxy('api.salesforce.com');\n * // ... make request ...\n * mgr.recordResult('api.salesforce.com', success);\n */\nexport class TieredProxyManager {\n private tiers: Map<ProxyTier, string[]> = new Map();\n private domainHistory: Map<string, DomainHistory> = new Map();\n private readonly ESCALATE_AFTER = 3; // consecutive failures before escalation\n private readonly DECAY_AFTER_MS = 30 * 60 * 1000; // 30 min: probe tier downgrade\n\n constructor(configs: ProxyTierConfig[]) {\n for (const cfg of configs) {\n if (cfg.urls.length > 0) this.tiers.set(cfg.tier, cfg.urls);\n }\n }\n\n private getHistory(domain: string): DomainHistory {\n if (!this.domainHistory.has(domain)) {\n this.domainHistory.set(domain, {\n tier: this.lowestAvailableTier(),\n consecutiveFailures: 0,\n lastEscalatedAt: 0,\n totalRequests: 0,\n successRate: 1,\n });\n }\n return this.domainHistory.get(domain)!;\n }\n\n private lowestAvailableTier(): ProxyTier {\n for (const t of TIER_ORDER) {\n if (this.tiers.has(t)) return t;\n }\n return 'free';\n }\n\n /** Pick a proxy URL for this domain, based on its tier history. */\n selectProxy(domain: string): string | null {\n const history = this.getHistory(domain);\n\n // Time-decay: if we've been on elevated tier for DECAY_AFTER_MS, try stepping down\n if (history.consecutiveFailures === 0 && history.lastEscalatedAt > 0) {\n const elapsed = Date.now() - history.lastEscalatedAt;\n if (elapsed > this.DECAY_AFTER_MS) {\n const currentIdx = TIER_ORDER.indexOf(history.tier);\n if (currentIdx > 0) {\n const lowerTier = TIER_ORDER[currentIdx - 1];\n if (this.tiers.has(lowerTier)) {\n history.tier = lowerTier;\n history.lastEscalatedAt = 0;\n }\n }\n }\n }\n\n const urls = this.tiers.get(history.tier);\n if (!urls || urls.length === 0) return null;\n // Round-robin within tier\n const idx = history.totalRequests % urls.length;\n return urls[idx];\n }\n\n /** Record a request result for a domain. Triggers tier escalation on repeated failures. */\n recordResult(domain: string, success: boolean): void {\n const history = this.getHistory(domain);\n history.totalRequests++;\n if (success) {\n history.consecutiveFailures = 0;\n // Update EMA success rate\n history.successRate = history.successRate * 0.9 + 0.1;\n } else {\n history.consecutiveFailures++;\n history.successRate = history.successRate * 0.9;\n if (history.consecutiveFailures >= this.ESCALATE_AFTER) {\n const elevated = nextTier(history.tier);\n if (elevated !== history.tier && this.tiers.has(elevated)) {\n console.warn(`[proxy-tier] domain=${domain} escalating ${history.tier}\u2192${elevated} after ${history.consecutiveFailures} failures`);\n history.tier = elevated;\n history.lastEscalatedAt = Date.now();\n history.consecutiveFailures = 0;\n }\n }\n }\n }\n\n stats(): Array<{ domain: string; tier: ProxyTier; successRate: number; totalRequests: number }> {\n return Array.from(this.domainHistory.entries()).map(([domain, h]) => ({\n domain,\n tier: h.tier,\n successRate: Math.round(h.successRate * 100) / 100,\n totalRequests: h.totalRequests,\n }));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-aware rate limiter — ensures the crawler respects per-domain rate limits.
|
|
3
|
+
*
|
|
4
|
+
* Uses a token bucket algorithm per domain. When the bucket is empty, requests
|
|
5
|
+
* wait until a token is available. Tokens refill at a configurable rate.
|
|
6
|
+
*
|
|
7
|
+
* Default: 2 requests/second per domain, burst of 5.
|
|
8
|
+
* Can be configured per-domain via the rateMap (domain → requests/second).
|
|
9
|
+
*/
|
|
10
|
+
export interface RateLimiterOpts {
|
|
11
|
+
defaultRps?: number;
|
|
12
|
+
burstSize?: number;
|
|
13
|
+
rateMap?: Record<string, number>;
|
|
14
|
+
}
|
|
15
|
+
export declare class DomainRateLimiter {
|
|
16
|
+
private buckets;
|
|
17
|
+
private readonly defaultRps;
|
|
18
|
+
private readonly burstSize;
|
|
19
|
+
private readonly rateMap;
|
|
20
|
+
constructor(opts?: RateLimiterOpts);
|
|
21
|
+
private getBucket;
|
|
22
|
+
private refill;
|
|
23
|
+
/** Wait until a token is available for the given URL's domain. */
|
|
24
|
+
throttle(url: string): Promise<void>;
|
|
25
|
+
/** Override rate for a specific domain at runtime (e.g. after seeing 429). */
|
|
26
|
+
setRate(domain: string, rps: number): void;
|
|
27
|
+
stats(): Record<string, {
|
|
28
|
+
tokens: number;
|
|
29
|
+
rps: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function extractDomain(url) {
|
|
2
|
+
try {
|
|
3
|
+
return new URL(url).hostname;
|
|
4
|
+
} catch {
|
|
5
|
+
return url;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
class DomainRateLimiter {
|
|
9
|
+
buckets = /* @__PURE__ */ new Map();
|
|
10
|
+
defaultRps;
|
|
11
|
+
burstSize;
|
|
12
|
+
rateMap;
|
|
13
|
+
constructor(opts = {}) {
|
|
14
|
+
this.defaultRps = opts.defaultRps ?? (parseFloat(process.env.CRAWLER_RATE_LIMIT_DEFAULT_RPS ?? "") || 2);
|
|
15
|
+
this.burstSize = opts.burstSize ?? (parseInt(process.env.CRAWLER_RATE_LIMIT_BURST_SIZE ?? "") || 5);
|
|
16
|
+
this.rateMap = opts.rateMap ?? {};
|
|
17
|
+
}
|
|
18
|
+
getBucket(domain) {
|
|
19
|
+
if (!this.buckets.has(domain)) {
|
|
20
|
+
this.buckets.set(domain, { tokens: this.burstSize, lastRefill: Date.now() });
|
|
21
|
+
}
|
|
22
|
+
return this.buckets.get(domain);
|
|
23
|
+
}
|
|
24
|
+
refill(domain, bucket) {
|
|
25
|
+
const rps = this.rateMap[domain] ?? this.defaultRps;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
const elapsed = (now - bucket.lastRefill) / 1e3;
|
|
28
|
+
bucket.tokens = Math.min(this.burstSize, bucket.tokens + elapsed * rps);
|
|
29
|
+
bucket.lastRefill = now;
|
|
30
|
+
}
|
|
31
|
+
/** Wait until a token is available for the given URL's domain. */
|
|
32
|
+
async throttle(url) {
|
|
33
|
+
const domain = extractDomain(url);
|
|
34
|
+
const bucket = this.getBucket(domain);
|
|
35
|
+
this.refill(domain, bucket);
|
|
36
|
+
if (bucket.tokens >= 1) {
|
|
37
|
+
bucket.tokens -= 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const rps = this.rateMap[domain] ?? this.defaultRps;
|
|
41
|
+
const waitMs = Math.ceil((1 - bucket.tokens) / rps * 1e3);
|
|
42
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
43
|
+
bucket.tokens = 0;
|
|
44
|
+
bucket.lastRefill = Date.now();
|
|
45
|
+
}
|
|
46
|
+
/** Override rate for a specific domain at runtime (e.g. after seeing 429). */
|
|
47
|
+
setRate(domain, rps) {
|
|
48
|
+
this.rateMap[domain] = rps;
|
|
49
|
+
const bucket = this.getBucket(domain);
|
|
50
|
+
bucket.tokens = 0;
|
|
51
|
+
}
|
|
52
|
+
stats() {
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const [domain, bucket] of this.buckets) {
|
|
55
|
+
this.refill(domain, bucket);
|
|
56
|
+
result[domain] = { tokens: Math.round(bucket.tokens * 100) / 100, rps: this.rateMap[domain] ?? this.defaultRps };
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
DomainRateLimiter
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/rate-limiter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Domain-aware rate limiter \u2014 ensures the crawler respects per-domain rate limits.\n *\n * Uses a token bucket algorithm per domain. When the bucket is empty, requests\n * wait until a token is available. Tokens refill at a configurable rate.\n *\n * Default: 2 requests/second per domain, burst of 5.\n * Can be configured per-domain via the rateMap (domain \u2192 requests/second).\n */\n\nexport interface RateLimiterOpts {\n defaultRps?: number; // requests per second (default 2)\n burstSize?: number; // max burst (default 5)\n rateMap?: Record<string, number>; // domain-specific overrides\n}\n\ninterface Bucket {\n tokens: number;\n lastRefill: number; // Date.now()\n}\n\nfunction extractDomain(url: string): string {\n try { return new URL(url).hostname; } catch { return url; }\n}\n\nexport class DomainRateLimiter {\n private buckets = new Map<string, Bucket>();\n private readonly defaultRps: number;\n private readonly burstSize: number;\n private readonly rateMap: Record<string, number>;\n\n constructor(opts: RateLimiterOpts = {}) {\n this.defaultRps = opts.defaultRps ?? (parseFloat(process.env.CRAWLER_RATE_LIMIT_DEFAULT_RPS ?? '') || 2);\n this.burstSize = opts.burstSize ?? (parseInt(process.env.CRAWLER_RATE_LIMIT_BURST_SIZE ?? '') || 5);\n this.rateMap = opts.rateMap ?? {};\n }\n\n private getBucket(domain: string): Bucket {\n if (!this.buckets.has(domain)) {\n this.buckets.set(domain, { tokens: this.burstSize, lastRefill: Date.now() });\n }\n return this.buckets.get(domain)!;\n }\n\n private refill(domain: string, bucket: Bucket): void {\n const rps = this.rateMap[domain] ?? this.defaultRps;\n const now = Date.now();\n const elapsed = (now - bucket.lastRefill) / 1000;\n bucket.tokens = Math.min(this.burstSize, bucket.tokens + elapsed * rps);\n bucket.lastRefill = now;\n }\n\n /** Wait until a token is available for the given URL's domain. */\n async throttle(url: string): Promise<void> {\n const domain = extractDomain(url);\n const bucket = this.getBucket(domain);\n this.refill(domain, bucket);\n\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n return;\n }\n\n // Calculate wait time until next token\n const rps = this.rateMap[domain] ?? this.defaultRps;\n const waitMs = Math.ceil((1 - bucket.tokens) / rps * 1000);\n await new Promise(r => setTimeout(r, waitMs));\n bucket.tokens = 0;\n bucket.lastRefill = Date.now();\n }\n\n /** Override rate for a specific domain at runtime (e.g. after seeing 429). */\n setRate(domain: string, rps: number): void {\n this.rateMap[domain] = rps;\n const bucket = this.getBucket(domain);\n bucket.tokens = 0; // drain immediately on rate change\n }\n\n stats(): Record<string, { tokens: number; rps: number }> {\n const result: Record<string, any> = {};\n for (const [domain, bucket] of this.buckets) {\n this.refill(domain, bucket);\n result[domain] = { tokens: Math.round(bucket.tokens * 100) / 100, rps: this.rateMap[domain] ?? this.defaultRps };\n }\n return result;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAqBA,SAAS,cAAc,KAAqB;AAC1C,MAAI;AAAE,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EAAU,QAAQ;AAAE,WAAO;AAAA,EAAK;AAC5D;AAEO,MAAM,kBAAkB;AAAA,EACrB,UAAU,oBAAI,IAAoB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,OAAwB,CAAC,GAAG;AACtC,SAAK,aAAa,KAAK,eAAe,WAAW,QAAQ,IAAI,kCAAkC,EAAE,KAAK;AACtG,SAAK,YAAY,KAAK,cAAc,SAAS,QAAQ,IAAI,iCAAiC,EAAE,KAAK;AACjG,SAAK,UAAU,KAAK,WAAW,CAAC;AAAA,EAClC;AAAA,EAEQ,UAAU,QAAwB;AACxC,QAAI,CAAC,KAAK,QAAQ,IAAI,MAAM,GAAG;AAC7B,WAAK,QAAQ,IAAI,QAAQ,EAAE,QAAQ,KAAK,WAAW,YAAY,KAAK,IAAI,EAAE,CAAC;AAAA,IAC7E;AACA,WAAO,KAAK,QAAQ,IAAI,MAAM;AAAA,EAChC;AAAA,EAEQ,OAAO,QAAgB,QAAsB;AACnD,UAAM,MAAM,KAAK,QAAQ,MAAM,KAAK,KAAK;AACzC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,MAAM,OAAO,cAAc;AAC5C,WAAO,SAAS,KAAK,IAAI,KAAK,WAAW,OAAO,SAAS,UAAU,GAAG;AACtE,WAAO,aAAa;AAAA,EACtB;AAAA;AAAA,EAGA,MAAM,SAAS,KAA4B;AACzC,UAAM,SAAS,cAAc,GAAG;AAChC,UAAM,SAAS,KAAK,UAAU,MAAM;AACpC,SAAK,OAAO,QAAQ,MAAM;AAE1B,QAAI,OAAO,UAAU,GAAG;AACtB,aAAO,UAAU;AACjB;AAAA,IACF;AAGA,UAAM,MAAM,KAAK,QAAQ,MAAM,KAAK,KAAK;AACzC,UAAM,SAAS,KAAK,MAAM,IAAI,OAAO,UAAU,MAAM,GAAI;AACzD,UAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,MAAM,CAAC;AAC5C,WAAO,SAAS;AAChB,WAAO,aAAa,KAAK,IAAI;AAAA,EAC/B;AAAA;AAAA,EAGA,QAAQ,QAAgB,KAAmB;AACzC,SAAK,QAAQ,MAAM,IAAI;AACvB,UAAM,SAAS,KAAK,UAAU,MAAM;AACpC,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,QAAyD;AACvD,UAAM,SAA8B,CAAC;AACrC,eAAW,CAAC,QAAQ,MAAM,KAAK,KAAK,SAAS;AAC3C,WAAK,OAAO,QAAQ,MAAM;AAC1B,aAAO,MAAM,IAAI,EAAE,QAAQ,KAAK,MAAM,OAAO,SAAS,GAAG,IAAI,KAAK,KAAK,KAAK,QAAQ,MAAM,KAAK,KAAK,WAAW;AAAA,IACjH;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect chain tracker — records the full redirect chain for crawled URLs.
|
|
3
|
+
*
|
|
4
|
+
* Why: redirect chains (A→B→C) slow down pages; circular redirects crash crawlers;
|
|
5
|
+
* redirect loops indicate config errors. ZeTa records chains for SEO/perf audit.
|
|
6
|
+
*
|
|
7
|
+
* Integration: hook into Playwright's page.on('response') to see each redirect hop.
|
|
8
|
+
*/
|
|
9
|
+
export interface RedirectHop {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
statusCode: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
}
|
|
15
|
+
export interface RedirectChain {
|
|
16
|
+
originalUrl: string;
|
|
17
|
+
finalUrl: string;
|
|
18
|
+
hops: RedirectHop[];
|
|
19
|
+
isCircular: boolean;
|
|
20
|
+
hopCount: number;
|
|
21
|
+
totalDurationMs: number;
|
|
22
|
+
}
|
|
23
|
+
import type { Page } from 'playwright';
|
|
24
|
+
export declare function trackRedirects(page: Page, url: string): Promise<RedirectChain>;
|
|
25
|
+
export declare class RedirectRegistry {
|
|
26
|
+
private chains;
|
|
27
|
+
register(chain: RedirectChain): void;
|
|
28
|
+
get(url: string): RedirectChain | undefined;
|
|
29
|
+
getAll(): RedirectChain[];
|
|
30
|
+
getProblematic(): RedirectChain[];
|
|
31
|
+
stats(): {
|
|
32
|
+
total: number;
|
|
33
|
+
withRedirects: number;
|
|
34
|
+
circular: number;
|
|
35
|
+
deepChains: number;
|
|
36
|
+
};
|
|
37
|
+
}
|