@taodigital/crawler 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P1: URL Pattern LLM Completion
|
|
3
|
+
*
|
|
4
|
+
* After BFS crawl completes, runs a single LLM call to predict URLs that exist
|
|
5
|
+
* but were never linked from within the app. Validates each candidate with
|
|
6
|
+
* HTTP HEAD before emitting as crawl evidence (URL_CANDIDATES).
|
|
7
|
+
*
|
|
8
|
+
* Cost: ~1 LLM call + N HEAD requests. Takes ~5-30 seconds depending on
|
|
9
|
+
* candidate count. Does NOT launch a browser.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logger as rootLogger } from './logger.js';
|
|
13
|
+
|
|
14
|
+
const logger = rootLogger.child({ module: 'url-pattern-completer' });
|
|
15
|
+
|
|
16
|
+
export interface UrlCandidate {
|
|
17
|
+
url: string;
|
|
18
|
+
confidence: number;
|
|
19
|
+
reason: string;
|
|
20
|
+
status: 'hit' | 'miss' | 'error';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface LlmCfg {
|
|
24
|
+
provider: string;
|
|
25
|
+
model: string;
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Only these two providers are supported by this module (they use standard SDK calls).
|
|
31
|
+
// NVIDIA, groq, ollama, openrouter, etc. are skipped — fall through to env fallback.
|
|
32
|
+
const SUPPORTED = new Set(['openai', 'anthropic']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the effective LLM to use. Priority: openai → anthropic → skip others.
|
|
36
|
+
* Falls back to env vars when the platform-configured provider is unsupported.
|
|
37
|
+
*/
|
|
38
|
+
function resolveTextLLM(llmCfg: LlmCfg | null | undefined): { provider: 'openai' | 'anthropic'; apiKey: string; model: string; baseUrl?: string } | null {
|
|
39
|
+
// Use platform config if it's a supported provider with a key
|
|
40
|
+
if (llmCfg?.provider === 'openai' && llmCfg.apiKey) {
|
|
41
|
+
return { provider: 'openai', apiKey: llmCfg.apiKey, model: llmCfg.model, baseUrl: llmCfg.baseUrl };
|
|
42
|
+
}
|
|
43
|
+
if (llmCfg?.provider === 'anthropic' && llmCfg.apiKey) {
|
|
44
|
+
return { provider: 'anthropic', apiKey: llmCfg.apiKey, model: llmCfg.model };
|
|
45
|
+
}
|
|
46
|
+
// Platform provider is unsupported (nvidia, groq, etc.) or no key — fall back to env vars.
|
|
47
|
+
// OpenAI takes priority over Anthropic.
|
|
48
|
+
if (process.env.OPENAI_API_KEY) {
|
|
49
|
+
return { provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o-mini' };
|
|
50
|
+
}
|
|
51
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
52
|
+
return { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-haiku-4-5-20251001' };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns validated URL candidates (HEAD=200) predicted by LLM from
|
|
59
|
+
* the pattern of already-discovered URLs.
|
|
60
|
+
*/
|
|
61
|
+
export async function runUrlPatternCompletion(
|
|
62
|
+
discoveredUrls: string[],
|
|
63
|
+
appUrl: string,
|
|
64
|
+
llmCfg: LlmCfg | null | undefined,
|
|
65
|
+
): Promise<UrlCandidate[]> {
|
|
66
|
+
const effective = resolveTextLLM(llmCfg);
|
|
67
|
+
if (!effective || discoveredUrls.length < 2) return [];
|
|
68
|
+
|
|
69
|
+
const origin = (() => { try { return new URL(appUrl).origin; } catch { return ''; } })();
|
|
70
|
+
if (!origin) return [];
|
|
71
|
+
|
|
72
|
+
// Only send internal URLs (same origin, no assets)
|
|
73
|
+
const ASSET_EXT = /\.(png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf|eot|css|js|map|json|xml|txt|pdf)$/i;
|
|
74
|
+
const internalUrls = discoveredUrls
|
|
75
|
+
.filter(u => { try { return new URL(u).origin === origin && !ASSET_EXT.test(u); } catch { return false; } })
|
|
76
|
+
.slice(0, 60); // cap context size
|
|
77
|
+
|
|
78
|
+
if (internalUrls.length < 2) return [];
|
|
79
|
+
|
|
80
|
+
const systemPrompt = 'You are a URL pattern analyst. Always respond with valid JSON only.';
|
|
81
|
+
const userPrompt = `You are analyzing a web application's URL structure to find screens that exist but weren't discovered during automated crawling.
|
|
82
|
+
|
|
83
|
+
Discovered URLs:
|
|
84
|
+
${internalUrls.map(u => { try { return new URL(u).pathname + new URL(u).search; } catch { return u; } }).join('\n')}
|
|
85
|
+
|
|
86
|
+
Based on these URL patterns, suggest up to 15 additional URLs that likely exist in this application but weren't found — typically settings subpages, admin panels, profile variants, paginated views, or feature-gated routes.
|
|
87
|
+
|
|
88
|
+
Rules:
|
|
89
|
+
- Only suggest URLs on the same domain
|
|
90
|
+
- Use relative paths (starting with /)
|
|
91
|
+
- Skip generic guesses (/login, /register, /home) unless the pattern strongly implies them
|
|
92
|
+
- Focus on patterns YOU SEE in the discovered URLs (e.g. if /settings/profile exists, try /settings/security, /settings/billing)
|
|
93
|
+
- Confidence 0.0–1.0: how certain you are the page exists
|
|
94
|
+
|
|
95
|
+
Respond with ONLY valid JSON, no explanation:
|
|
96
|
+
{"candidates":[{"path":"/settings/billing","confidence":0.85,"reason":"settings subpage pattern from /settings/profile"},{"path":"/admin/users","confidence":0.6,"reason":"admin prefix implied by /admin/dashboard"}]}`;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
let text = '';
|
|
100
|
+
|
|
101
|
+
if (effective.provider === 'openai') {
|
|
102
|
+
const { OpenAI } = await import('openai');
|
|
103
|
+
const client = new OpenAI({ apiKey: effective.apiKey, ...(effective.baseUrl ? { baseURL: effective.baseUrl } : {}) });
|
|
104
|
+
const response = await client.chat.completions.create({
|
|
105
|
+
model: effective.model ?? 'gpt-4o-mini',
|
|
106
|
+
max_tokens: 1024,
|
|
107
|
+
temperature: 0,
|
|
108
|
+
messages: [
|
|
109
|
+
{ role: 'system', content: systemPrompt },
|
|
110
|
+
{ role: 'user', content: userPrompt },
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
text = response.choices[0]?.message?.content ?? '';
|
|
114
|
+
} else {
|
|
115
|
+
const Anthropic = (await import('@anthropic-ai/sdk')).default;
|
|
116
|
+
const client = new Anthropic({ apiKey: effective.apiKey });
|
|
117
|
+
const response = await client.messages.create({
|
|
118
|
+
model: effective.model ?? 'claude-haiku-4-5-20251001',
|
|
119
|
+
max_tokens: 1024,
|
|
120
|
+
temperature: 0,
|
|
121
|
+
// @ts-ignore: cache_control supported in beta
|
|
122
|
+
system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
|
|
123
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
124
|
+
});
|
|
125
|
+
text = (response.content[0] as any)?.text ?? '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
129
|
+
if (!jsonMatch) return [];
|
|
130
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
131
|
+
const rawCandidates: Array<{ path: string; confidence: number; reason: string }> =
|
|
132
|
+
parsed.candidates ?? [];
|
|
133
|
+
|
|
134
|
+
if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) return [];
|
|
135
|
+
|
|
136
|
+
// HEAD-validate each candidate
|
|
137
|
+
const results: UrlCandidate[] = [];
|
|
138
|
+
for (const c of rawCandidates.slice(0, 20)) {
|
|
139
|
+
if (!c.path || typeof c.path !== 'string') continue;
|
|
140
|
+
const fullUrl = c.path.startsWith('http') ? c.path : `${origin}${c.path.startsWith('/') ? '' : '/'}${c.path}`;
|
|
141
|
+
try {
|
|
142
|
+
const controller = new AbortController();
|
|
143
|
+
const timeout = setTimeout(() => controller.abort(), 6_000);
|
|
144
|
+
const res = await fetch(fullUrl, { method: 'HEAD', signal: controller.signal, redirect: 'follow' }).catch(() => null);
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
const status: UrlCandidate['status'] = res?.ok ? 'hit' : 'miss';
|
|
147
|
+
results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? '', status });
|
|
148
|
+
logger.info({ url: fullUrl, httpStatus: res?.status ?? 'timeout', status }, '[url-completer] candidate checked');
|
|
149
|
+
} catch {
|
|
150
|
+
results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? '', status: 'error' });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const hits = results.filter(r => r.status === 'hit');
|
|
155
|
+
logger.info({ total: results.length, hits: hits.length, provider: effective.provider }, '[url-completer] pattern completion done');
|
|
156
|
+
return results;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.warn({ err: String(err) }, '[url-completer] LLM call failed — skipping pattern completion');
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/vendor.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare module 'pixelmatch' {
|
|
2
|
+
function pixelmatch(
|
|
3
|
+
img1: Buffer | Uint8Array,
|
|
4
|
+
img2: Buffer | Uint8Array,
|
|
5
|
+
output: Buffer | Uint8Array | null,
|
|
6
|
+
width: number,
|
|
7
|
+
height: number,
|
|
8
|
+
options?: {
|
|
9
|
+
threshold?: number;
|
|
10
|
+
includeAA?: boolean;
|
|
11
|
+
alpha?: number;
|
|
12
|
+
aaColor?: [number, number, number];
|
|
13
|
+
diffColor?: [number, number, number];
|
|
14
|
+
diffColorAlt?: [number, number, number];
|
|
15
|
+
diffMask?: boolean;
|
|
16
|
+
}
|
|
17
|
+
): number;
|
|
18
|
+
export = pixelmatch;
|
|
19
|
+
export default pixelmatch;
|
|
20
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { pHashVerdict, mergeAiVisualVerdict } from './visual-diff-verdict.js';
|
|
4
|
+
|
|
5
|
+
test('pHashVerdict: IDENTICAL maps to NONE/APPROVE', () => {
|
|
6
|
+
const v = pHashVerdict(0, 'IDENTICAL', []);
|
|
7
|
+
assert.equal(v.changeType, 'NONE');
|
|
8
|
+
assert.equal(v.recommendation, 'APPROVE');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('pHashVerdict: MODERATE change level maps to SIGNIFICANT changeType', () => {
|
|
12
|
+
const v = pHashVerdict(12, 'MODERATE', []);
|
|
13
|
+
assert.equal(v.changeType, 'SIGNIFICANT');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('pHashVerdict: SIGNIFICANT change level maps to CRITICAL changeType (naming overlap between phash and VisualDiff scales)', () => {
|
|
17
|
+
const v = pHashVerdict(35, 'SIGNIFICANT', []);
|
|
18
|
+
assert.equal(v.changeType, 'CRITICAL');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('pHashVerdict: recommendation escalates with distance regardless of changeLevel', () => {
|
|
22
|
+
assert.equal(pHashVerdict(0, 'MINOR', []).recommendation, 'APPROVE');
|
|
23
|
+
assert.equal(pHashVerdict(10, 'MINOR', []).recommendation, 'REVIEW');
|
|
24
|
+
assert.equal(pHashVerdict(25, 'MINOR', []).recommendation, 'FLAG');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('mergeAiVisualVerdict: AI downgrading pHash CRITICAL to MINOR (anti-aliasing noise) produces no bug report draft', () => {
|
|
28
|
+
const pHash = pHashVerdict(35, 'SIGNIFICANT', [{ location: 'header', severity: 'Cosmetic', description: 'pixel shift' }]);
|
|
29
|
+
const merged = mergeAiVisualVerdict(
|
|
30
|
+
pHash,
|
|
31
|
+
{ changeType: 'MINOR', recommendation: 'APPROVE', changes: [], diffScore: 3 },
|
|
32
|
+
'Login Screen',
|
|
33
|
+
);
|
|
34
|
+
assert.equal(merged.changeType, 'MINOR');
|
|
35
|
+
assert.equal(merged.recommendation, 'APPROVE');
|
|
36
|
+
assert.equal(merged.bugReportDraft, undefined, 'a downgraded verdict must not carry a stale bug report draft');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('mergeAiVisualVerdict: AI confirming CRITICAL produces a bug report draft with the AI diff score, not the pHash one', () => {
|
|
40
|
+
const pHash = pHashVerdict(35, 'SIGNIFICANT', []);
|
|
41
|
+
const merged = mergeAiVisualVerdict(
|
|
42
|
+
pHash,
|
|
43
|
+
{ changeType: 'CRITICAL', recommendation: 'FLAG', changes: [{ location: 'nav', severity: 'Functional', description: 'Logo missing' }], diffScore: 62.4 },
|
|
44
|
+
'Dashboard',
|
|
45
|
+
);
|
|
46
|
+
assert.equal(merged.changeType, 'CRITICAL');
|
|
47
|
+
assert.ok(merged.bugReportDraft);
|
|
48
|
+
assert.equal(merged.bugReportDraft!.severity, 'High');
|
|
49
|
+
assert.match(merged.bugReportDraft!.actual, /62\.4%/);
|
|
50
|
+
assert.equal(merged.changes.length, 1);
|
|
51
|
+
assert.equal(merged.changes[0].description, 'Logo missing');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('mergeAiVisualVerdict: SIGNIFICANT (not just CRITICAL) also gets a bug report draft, with Medium severity', () => {
|
|
55
|
+
const pHash = pHashVerdict(20, 'MODERATE', []);
|
|
56
|
+
const merged = mergeAiVisualVerdict(
|
|
57
|
+
pHash,
|
|
58
|
+
{ changeType: 'SIGNIFICANT', recommendation: 'REVIEW', changes: [], diffScore: 18 },
|
|
59
|
+
'Checkout',
|
|
60
|
+
);
|
|
61
|
+
assert.ok(merged.bugReportDraft);
|
|
62
|
+
assert.equal(merged.bugReportDraft!.severity, 'Medium');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('mergeAiVisualVerdict: empty AI changes array keeps the pHash-derived changes instead of wiping them', () => {
|
|
66
|
+
const pHash = pHashVerdict(35, 'SIGNIFICANT', [{ location: 'footer', severity: 'Cosmetic', description: 'layout shift' }]);
|
|
67
|
+
const merged = mergeAiVisualVerdict(
|
|
68
|
+
pHash,
|
|
69
|
+
{ changeType: 'CRITICAL', recommendation: 'FLAG', changes: [], diffScore: 40 },
|
|
70
|
+
'Settings',
|
|
71
|
+
);
|
|
72
|
+
assert.equal(merged.changes.length, 1);
|
|
73
|
+
assert.equal(merged.changes[0].description, 'layout shift');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('mergeAiVisualVerdict: malformed AI result (no changeType) falls back to the pHash verdict untouched', () => {
|
|
77
|
+
const pHash = pHashVerdict(35, 'SIGNIFICANT', []);
|
|
78
|
+
const merged = mergeAiVisualVerdict(pHash, { changeType: '', recommendation: '', changes: [], diffScore: 0 }, 'Home');
|
|
79
|
+
assert.deepEqual(merged, pHash);
|
|
80
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure visual-diff verdict logic, split out of crawler.ts so it can be unit-tested
|
|
3
|
+
* without pulling in crawler.ts's full import graph (Playwright, Stagehand, and
|
|
4
|
+
* transitively @taodigital/database, which instantiates a PrismaClient at module load and
|
|
5
|
+
* so requires a live DATABASE_URL just to import the module).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface VisualDiffVerdict {
|
|
9
|
+
changeType: 'NONE' | 'MINOR' | 'SIGNIFICANT' | 'CRITICAL';
|
|
10
|
+
recommendation: 'APPROVE' | 'REVIEW' | 'FLAG' | 'BLOCK';
|
|
11
|
+
changes: Array<{ location: string; severity: string; description: string }>;
|
|
12
|
+
bugReportDraft?: { title: string; severity: string; steps: string[]; expected: string; actual: string };
|
|
13
|
+
aiSummary?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Cheap, always-on first-pass verdict from perceptual hash distance + layout/element diffs. */
|
|
17
|
+
export function pHashVerdict(
|
|
18
|
+
pHashDistance: number,
|
|
19
|
+
changeLevel: 'IDENTICAL' | 'MINOR' | 'MODERATE' | 'SIGNIFICANT',
|
|
20
|
+
changes: Array<{ location: string; severity: string; description: string }>,
|
|
21
|
+
): VisualDiffVerdict {
|
|
22
|
+
const changeType: VisualDiffVerdict['changeType'] = changeLevel === 'IDENTICAL' ? 'NONE'
|
|
23
|
+
: changeLevel === 'MINOR' ? 'MINOR'
|
|
24
|
+
: changeLevel === 'MODERATE' ? 'SIGNIFICANT'
|
|
25
|
+
: 'CRITICAL';
|
|
26
|
+
const approveThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_APPROVE_THRESHOLD ?? '') || 5;
|
|
27
|
+
const reviewThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_REVIEW_THRESHOLD ?? '') || 20;
|
|
28
|
+
const recommendation: VisualDiffVerdict['recommendation'] = pHashDistance === 0 ? 'APPROVE'
|
|
29
|
+
: pHashDistance < approveThreshold ? 'APPROVE'
|
|
30
|
+
: pHashDistance < reviewThreshold ? 'REVIEW'
|
|
31
|
+
: 'FLAG';
|
|
32
|
+
return { changeType, recommendation, changes };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Replaces a pHash-only verdict with the AI's semantic judgment once it's available,
|
|
36
|
+
* and drafts a bug report for anything still CRITICAL/SIGNIFICANT after AI review
|
|
37
|
+
* (pHash flags plenty of anti-aliasing/font-rendering noise the AI correctly discards
|
|
38
|
+
* as NONE/MINOR — only what survives AI review is worth a draft). */
|
|
39
|
+
export function mergeAiVisualVerdict(
|
|
40
|
+
pHashResult: VisualDiffVerdict,
|
|
41
|
+
aiResult: { changeType: string; changes: any[]; recommendation: string; diffScore: number; summary?: string },
|
|
42
|
+
screenName: string,
|
|
43
|
+
): VisualDiffVerdict {
|
|
44
|
+
if (!aiResult.changeType) return pHashResult;
|
|
45
|
+
const changeType = aiResult.changeType as VisualDiffVerdict['changeType'];
|
|
46
|
+
const recommendation = aiResult.recommendation as VisualDiffVerdict['recommendation'];
|
|
47
|
+
const changes = Array.isArray(aiResult.changes) && aiResult.changes.length > 0 ? aiResult.changes : pHashResult.changes;
|
|
48
|
+
const aiSummary = aiResult.summary ?? undefined;
|
|
49
|
+
const bugReportDraft = (changeType === 'CRITICAL' || changeType === 'SIGNIFICANT')
|
|
50
|
+
? {
|
|
51
|
+
title: `Visual regression on "${screenName}"`,
|
|
52
|
+
severity: changeType === 'CRITICAL' ? 'High' : 'Medium',
|
|
53
|
+
steps: ['Navigate to the screen', 'Compare current view to baseline screenshot'],
|
|
54
|
+
expected: 'Screen appearance should match the stored baseline.',
|
|
55
|
+
actual: aiSummary || `Screen has changed (diff score: ${aiResult.diffScore.toFixed(1)}%)`,
|
|
56
|
+
}
|
|
57
|
+
: undefined;
|
|
58
|
+
return { changeType, recommendation, changes, bugReportDraft, aiSummary };
|
|
59
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook notifier — fire HTTP POST callbacks on crawl lifecycle events.
|
|
3
|
+
*
|
|
4
|
+
* Supports: crawl.started, crawl.progress, crawl.completed, crawl.failed
|
|
5
|
+
* Payload format compatible with Slack, Discord, and generic REST webhooks.
|
|
6
|
+
* Retries 3x on failure with exponential backoff.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type CrawlEventType = 'crawl.started' | 'crawl.progress' | 'crawl.completed' | 'crawl.failed';
|
|
10
|
+
|
|
11
|
+
export interface WebhookPayload {
|
|
12
|
+
event: CrawlEventType;
|
|
13
|
+
jobId: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
tenantId: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
data: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WebhookNotifierOpts {
|
|
21
|
+
url: string;
|
|
22
|
+
secret?: string; // HMAC-SHA256 signing secret — adds X-ZeTa-Signature header
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
timeoutMs?: number; // default 10s
|
|
25
|
+
maxRetries?: number; // default 3
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function hmacSign(secret: string, payload: string): Promise<string> {
|
|
29
|
+
const key = await crypto.subtle.importKey(
|
|
30
|
+
'raw',
|
|
31
|
+
new TextEncoder().encode(secret),
|
|
32
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
33
|
+
false,
|
|
34
|
+
['sign'],
|
|
35
|
+
);
|
|
36
|
+
const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));
|
|
37
|
+
return Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class WebhookNotifier {
|
|
41
|
+
private readonly opts: Required<Omit<WebhookNotifierOpts, 'secret'>> & { secret?: string };
|
|
42
|
+
|
|
43
|
+
constructor(opts: WebhookNotifierOpts) {
|
|
44
|
+
this.opts = {
|
|
45
|
+
url: opts.url,
|
|
46
|
+
secret: opts.secret,
|
|
47
|
+
headers: opts.headers ?? {},
|
|
48
|
+
timeoutMs: opts.timeoutMs ?? 10_000,
|
|
49
|
+
maxRetries: opts.maxRetries ?? 3,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async notify(
|
|
54
|
+
event: CrawlEventType,
|
|
55
|
+
jobId: string,
|
|
56
|
+
projectId: string,
|
|
57
|
+
tenantId: string,
|
|
58
|
+
data: Record<string, unknown> = {},
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const payload: WebhookPayload = {
|
|
61
|
+
event,
|
|
62
|
+
jobId,
|
|
63
|
+
projectId,
|
|
64
|
+
tenantId,
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
data,
|
|
67
|
+
};
|
|
68
|
+
const body = JSON.stringify(payload);
|
|
69
|
+
const headers: Record<string, string> = {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
'User-Agent': 'ZeTa-Crawler/1.0',
|
|
72
|
+
...this.opts.headers,
|
|
73
|
+
};
|
|
74
|
+
if (this.opts.secret) {
|
|
75
|
+
headers['X-ZeTa-Signature'] = `sha256=${await hmacSign(this.opts.secret, body)}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let lastErr: any;
|
|
79
|
+
for (let attempt = 0; attempt <= this.opts.maxRetries; attempt++) {
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch(this.opts.url, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers,
|
|
84
|
+
body,
|
|
85
|
+
signal: AbortSignal.timeout(this.opts.timeoutMs),
|
|
86
|
+
});
|
|
87
|
+
if (res.ok) return;
|
|
88
|
+
lastErr = new Error(`Webhook HTTP ${res.status}`);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
lastErr = e;
|
|
91
|
+
}
|
|
92
|
+
if (attempt < this.opts.maxRetries) {
|
|
93
|
+
await new Promise(r => setTimeout(r, 1_000 * Math.pow(2, attempt)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.error(`[webhook] Failed to deliver ${event} after ${this.opts.maxRetries + 1} attempts:`, lastErr?.message);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Slack-compatible message format */
|
|
100
|
+
static slackPayload(event: CrawlEventType, data: Record<string, unknown>): Record<string, unknown> {
|
|
101
|
+
const emoji = event === 'crawl.completed' ? '✅' : event === 'crawl.failed' ? '❌' : '🔄';
|
|
102
|
+
return {
|
|
103
|
+
text: `${emoji} *ZeTa Crawl Event: ${event}*`,
|
|
104
|
+
blocks: [
|
|
105
|
+
{ type: 'section', text: { type: 'mrkdwn', text: `${emoji} *${event}*\n${Object.entries(data).map(([k, v]) => `• *${k}*: ${v}`).join('\n')}` } },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket detection — intercepts WS connections opened during page crawl.
|
|
3
|
+
* Records connection URLs, message schemas, and message counts.
|
|
4
|
+
* This lets ZeTa discover real-time API contracts (chat, notifications, presence)
|
|
5
|
+
* that would be invisible to HTTP-only crawlers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface WsConnection {
|
|
9
|
+
url: string;
|
|
10
|
+
framesSent: number;
|
|
11
|
+
framesReceived: number;
|
|
12
|
+
/** Sampled message bodies (first 10 per connection, truncated to 500 chars) */
|
|
13
|
+
samples: Array<{ direction: 'sent' | 'received'; payload: string; ts: string }>;
|
|
14
|
+
/** Inferred protocol: 'json-rpc' | 'socket.io' | 'graphql-ws' | 'raw' */
|
|
15
|
+
protocol: string;
|
|
16
|
+
openedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function inferProtocol(sample: string): string {
|
|
20
|
+
try {
|
|
21
|
+
const obj = JSON.parse(sample);
|
|
22
|
+
if (obj?.jsonrpc) return 'json-rpc';
|
|
23
|
+
if (obj?.type === 'connection_init' || obj?.type === 'subscribe') return 'graphql-ws';
|
|
24
|
+
if (typeof obj?.sid === 'string' || sample.startsWith('0{')) return 'socket.io';
|
|
25
|
+
return 'json';
|
|
26
|
+
} catch {
|
|
27
|
+
return 'raw';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function attachWebSocketDetector(cdpSession: any): {
|
|
32
|
+
getConnections(): WsConnection[];
|
|
33
|
+
detach(): void;
|
|
34
|
+
} {
|
|
35
|
+
const connections = new Map<string, WsConnection>();
|
|
36
|
+
|
|
37
|
+
const onCreated = (evt: any) => {
|
|
38
|
+
const { requestId, url, timestamp } = evt ?? {};
|
|
39
|
+
if (!requestId || !url) return;
|
|
40
|
+
connections.set(requestId, {
|
|
41
|
+
url,
|
|
42
|
+
framesSent: 0,
|
|
43
|
+
framesReceived: 0,
|
|
44
|
+
samples: [],
|
|
45
|
+
protocol: 'unknown',
|
|
46
|
+
openedAt: new Date(timestamp * 1000).toISOString(),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onFrame = (evt: any, direction: 'sent' | 'received') => {
|
|
51
|
+
const { requestId, response } = evt ?? {};
|
|
52
|
+
if (!requestId) return;
|
|
53
|
+
const conn = connections.get(requestId);
|
|
54
|
+
if (!conn) return;
|
|
55
|
+
if (direction === 'sent') conn.framesSent++;
|
|
56
|
+
else conn.framesReceived++;
|
|
57
|
+
if (conn.samples.length < 10 && response?.payloadData) {
|
|
58
|
+
const payload = String(response.payloadData).slice(0, 500);
|
|
59
|
+
conn.samples.push({ direction, payload, ts: new Date().toISOString() });
|
|
60
|
+
if (conn.protocol === 'unknown') conn.protocol = inferProtocol(payload);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const onSent = (evt: any) => onFrame(evt, 'sent');
|
|
65
|
+
const onReceived = (evt: any) => onFrame(evt, 'received');
|
|
66
|
+
|
|
67
|
+
if (cdpSession?.on) {
|
|
68
|
+
cdpSession.on('Network.webSocketCreated', onCreated);
|
|
69
|
+
cdpSession.on('Network.webSocketFrameSent', onSent);
|
|
70
|
+
cdpSession.on('Network.webSocketFrameReceived', onReceived);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
getConnections: () => [...connections.values()],
|
|
75
|
+
detach() {
|
|
76
|
+
if (cdpSession?.off) {
|
|
77
|
+
cdpSession.off('Network.webSocketCreated', onCreated);
|
|
78
|
+
cdpSession.off('Network.webSocketFrameSent', onSent);
|
|
79
|
+
cdpSession.off('Network.webSocketFrameReceived', onReceived);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone worker process entrypoint for a dedicated crawler/worker container,
|
|
3
|
+
* separate from the API server (see infrastructure/ecs-task-definitions/crawler.json).
|
|
4
|
+
*
|
|
5
|
+
* index.ts is a side-effect-free library barrel imported by @taodigital/api — it must NOT
|
|
6
|
+
* start any background loop itself, or importing a single helper from @taodigital/crawler
|
|
7
|
+
* would silently spin up a second worker inside the API process too. This file is
|
|
8
|
+
* the actual entrypoint that keeps a dedicated worker container's process alive and
|
|
9
|
+
* consuming jobs: `node dist/worker.js`.
|
|
10
|
+
*/
|
|
11
|
+
import { startJobWorkerLoop, processDataRetentionPurge } from './job-queue.js';
|
|
12
|
+
import { logger } from './logger.js';
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
logger.info('[worker] starting standalone crawler worker process');
|
|
16
|
+
|
|
17
|
+
if (process.env.JOB_QUEUE_BACKEND === 'sqs') {
|
|
18
|
+
// SQS mode: messages arrive via long-poll; claimSpecificJob atomically moves
|
|
19
|
+
// QUEUED→RUNNING before dispatch, so multiple worker replicas are safe.
|
|
20
|
+
// DB poll loop also runs as a safety net for jobs whose SQS message was lost.
|
|
21
|
+
const { startSqsWorker } = await import('./sqs-queue.js');
|
|
22
|
+
startSqsWorker().catch((err) => {
|
|
23
|
+
logger.error({ err: String(err) }, '[worker] SQS worker crashed');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// DB-backed poll loop — primary in non-SQS mode, safety net in SQS mode.
|
|
29
|
+
// Claims all job types. Safe to run in many worker replicas (FOR UPDATE SKIP LOCKED).
|
|
30
|
+
startJobWorkerLoop();
|
|
31
|
+
|
|
32
|
+
// Daily data-retention purge: runs immediately on startup then every 24 h.
|
|
33
|
+
// Only runs in the dedicated worker container (this file), not in the API
|
|
34
|
+
// process, so there is exactly one purge sweep regardless of API replica count.
|
|
35
|
+
const runPurge = () => {
|
|
36
|
+
processDataRetentionPurge().catch((err) => {
|
|
37
|
+
logger.error({ err: String(err) }, '[worker] data-retention purge failed');
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
runPurge();
|
|
41
|
+
setInterval(runPurge, 24 * 60 * 60 * 1000);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.on('SIGTERM', () => { logger.info('[worker] SIGTERM received, exiting'); process.exit(0); });
|
|
45
|
+
process.on('SIGINT', () => { logger.info('[worker] SIGINT received, exiting'); process.exit(0); });
|
|
46
|
+
|
|
47
|
+
main().catch((err) => {
|
|
48
|
+
logger.error({ err: String(err) }, '[worker] fatal startup error');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"composite": false,
|
|
9
|
+
"noEmitOnError": false
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts"],
|
|
12
|
+
"exclude": ["src/**/*.test.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility auditor — lightweight a11y checks using Playwright's aria snapshot
|
|
3
|
+
* and manual DOM queries. No axe-core dependency needed.
|
|
4
|
+
*
|
|
5
|
+
* Checks:
|
|
6
|
+
* 1. Images without alt text
|
|
7
|
+
* 2. Form inputs without associated labels
|
|
8
|
+
* 3. Buttons/links with no accessible name
|
|
9
|
+
* 4. Missing lang attribute on html element
|
|
10
|
+
* 5. Missing page title
|
|
11
|
+
* 6. Empty headings
|
|
12
|
+
* 7. Color contrast (limited — detects text with inline style color only)
|
|
13
|
+
* 8. Keyboard-unfocusable interactive elements (tabIndex=-1 on buttons)
|
|
14
|
+
*/
|
|
15
|
+
import type { Page } from 'playwright';
|
|
16
|
+
export interface A11yViolation {
|
|
17
|
+
rule: string;
|
|
18
|
+
severity: 'critical' | 'serious' | 'moderate' | 'minor';
|
|
19
|
+
count: number;
|
|
20
|
+
examples: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface A11yAuditResult {
|
|
23
|
+
url: string;
|
|
24
|
+
violations: A11yViolation[];
|
|
25
|
+
passCount: number;
|
|
26
|
+
score: number;
|
|
27
|
+
auditedAt: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function auditAccessibility(page: Page, url: string): Promise<A11yAuditResult>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility tree crawler — extract landmark, heading, and ARIA metadata from crawled pages.
|
|
3
|
+
*
|
|
4
|
+
* Captures: landmarks with interactive counts, heading hierarchy with violation detection,
|
|
5
|
+
* interactive elements missing accessible names, live regions, and skip-link presence.
|
|
6
|
+
*
|
|
7
|
+
* This data feeds ZeTa's accessibility test generator to surface WCAG 2.1 Level A/AA
|
|
8
|
+
* failures (missing names, heading skips, absent skip links, unlabeled live regions).
|
|
9
|
+
*/
|
|
10
|
+
export interface LandmarkInfo {
|
|
11
|
+
role: string;
|
|
12
|
+
label: string;
|
|
13
|
+
elementId: string;
|
|
14
|
+
interactiveCount: number;
|
|
15
|
+
}
|
|
16
|
+
export interface HeadingInfo {
|
|
17
|
+
level: number;
|
|
18
|
+
text: string;
|
|
19
|
+
elementId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface MissingNameEl {
|
|
22
|
+
tagName: string;
|
|
23
|
+
role: string;
|
|
24
|
+
elementId: string;
|
|
25
|
+
htmlSnippet: string;
|
|
26
|
+
}
|
|
27
|
+
export interface LiveRegionInfo {
|
|
28
|
+
role: string;
|
|
29
|
+
ariaLive: string;
|
|
30
|
+
ariaAtomic: string;
|
|
31
|
+
currentText: string;
|
|
32
|
+
}
|
|
33
|
+
export interface AccessibilityTreeResult {
|
|
34
|
+
landmarkCount: number;
|
|
35
|
+
landmarks: LandmarkInfo[];
|
|
36
|
+
headings: HeadingInfo[];
|
|
37
|
+
headingHierarchyViolations: string[];
|
|
38
|
+
missingNameElements: MissingNameEl[];
|
|
39
|
+
missingNameCount: number;
|
|
40
|
+
liveRegions: LiveRegionInfo[];
|
|
41
|
+
totalInteractiveCount: number;
|
|
42
|
+
hasSkipLink: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function crawlAccessibilityTree(page: any): Promise<AccessibilityTreeResult>;
|