@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,209 @@
|
|
|
1
|
+
function extractUrlPattern(url) {
|
|
2
|
+
try {
|
|
3
|
+
const u = new URL(url);
|
|
4
|
+
const path = u.pathname.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ":uuid").replace(/\/[0-9a-f]{24}(\/|$)/g, "/:mongoId$1").replace(/\/\d{6,}(\/|$)/g, "/:id$1").replace(/\/\d{1,5}(\/|$)/g, "/:id$1");
|
|
5
|
+
return path;
|
|
6
|
+
} catch {
|
|
7
|
+
return url.slice(0, 80);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function extractBodySchema(bodyText) {
|
|
11
|
+
if (!bodyText || bodyText.length > 5e4) return null;
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(bodyText);
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const schema = {};
|
|
19
|
+
let keyCount = 0;
|
|
20
|
+
function traverse(value, prefix, depth) {
|
|
21
|
+
if (keyCount >= 30 || depth > 3) return;
|
|
22
|
+
if (value === null || value === void 0) return;
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
schema[prefix] = "array";
|
|
25
|
+
keyCount++;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "object") {
|
|
29
|
+
for (const [k, v] of Object.entries(value)) {
|
|
30
|
+
if (keyCount >= 30) break;
|
|
31
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
32
|
+
if (v === null) {
|
|
33
|
+
schema[key] = "null";
|
|
34
|
+
keyCount++;
|
|
35
|
+
} else if (Array.isArray(v)) {
|
|
36
|
+
schema[key] = "array";
|
|
37
|
+
keyCount++;
|
|
38
|
+
} else if (typeof v === "object") {
|
|
39
|
+
traverse(v, key, depth + 1);
|
|
40
|
+
} else {
|
|
41
|
+
schema[key] = typeof v;
|
|
42
|
+
keyCount++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (prefix) {
|
|
48
|
+
schema[prefix] = typeof value;
|
|
49
|
+
keyCount++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
traverse(parsed, "", 0);
|
|
53
|
+
return keyCount > 0 ? schema : null;
|
|
54
|
+
}
|
|
55
|
+
const ANALYTICS_DOMAINS = [
|
|
56
|
+
"google-analytics",
|
|
57
|
+
"doubleclick",
|
|
58
|
+
"hotjar",
|
|
59
|
+
"mixpanel",
|
|
60
|
+
"segment",
|
|
61
|
+
"sentry",
|
|
62
|
+
"datadog",
|
|
63
|
+
"newrelic",
|
|
64
|
+
"amplitude",
|
|
65
|
+
"fullstory"
|
|
66
|
+
];
|
|
67
|
+
const API_PATH_PATTERNS = [
|
|
68
|
+
"/api/",
|
|
69
|
+
"/graphql",
|
|
70
|
+
"/v1/",
|
|
71
|
+
"/v2/",
|
|
72
|
+
"/rest/",
|
|
73
|
+
"/query",
|
|
74
|
+
"/rpc",
|
|
75
|
+
"/trpc"
|
|
76
|
+
];
|
|
77
|
+
const EXCLUDED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["image", "font", "stylesheet", "media", "websocket"]);
|
|
78
|
+
function isApiRequest(url, resourceType) {
|
|
79
|
+
if (EXCLUDED_RESOURCE_TYPES.has(resourceType)) return false;
|
|
80
|
+
try {
|
|
81
|
+
const hostname = new URL(url).hostname;
|
|
82
|
+
if (ANALYTICS_DOMAINS.some((d) => hostname.includes(d))) return false;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (resourceType === "xhr" || resourceType === "fetch") return true;
|
|
87
|
+
const lower = url.toLowerCase();
|
|
88
|
+
return API_PATH_PATTERNS.some((p) => lower.includes(p));
|
|
89
|
+
}
|
|
90
|
+
function createCorrelator() {
|
|
91
|
+
let currentLabel = null;
|
|
92
|
+
let currentRequests = [];
|
|
93
|
+
const correlations = [];
|
|
94
|
+
return {
|
|
95
|
+
startWindow(actionLabel) {
|
|
96
|
+
currentLabel = actionLabel;
|
|
97
|
+
currentRequests = [];
|
|
98
|
+
},
|
|
99
|
+
recordRequest(rec) {
|
|
100
|
+
if (currentLabel === null) return;
|
|
101
|
+
currentRequests.push({ ...rec, correlatedActionLabel: currentLabel });
|
|
102
|
+
},
|
|
103
|
+
endWindow() {
|
|
104
|
+
const label = currentLabel ?? "(unknown)";
|
|
105
|
+
const correlation = {
|
|
106
|
+
actionLabel: label,
|
|
107
|
+
requests: currentRequests
|
|
108
|
+
};
|
|
109
|
+
correlations.push(correlation);
|
|
110
|
+
currentLabel = null;
|
|
111
|
+
currentRequests = [];
|
|
112
|
+
return correlation;
|
|
113
|
+
},
|
|
114
|
+
getResult() {
|
|
115
|
+
const uniqueEndpoints = [
|
|
116
|
+
...new Set(
|
|
117
|
+
correlations.flatMap((c) => c.requests.map((r) => r.urlPattern))
|
|
118
|
+
)
|
|
119
|
+
];
|
|
120
|
+
const graphqlOperations = [
|
|
121
|
+
...new Set(
|
|
122
|
+
correlations.flatMap((c) => c.requests).filter((r) => r.isGraphQL && r.graphQLOperation).map((r) => r.graphQLOperation)
|
|
123
|
+
)
|
|
124
|
+
];
|
|
125
|
+
const totalRequestsCaptured = correlations.reduce(
|
|
126
|
+
(sum, c) => sum + c.requests.length,
|
|
127
|
+
0
|
|
128
|
+
);
|
|
129
|
+
const coverage = correlations.length === 0 ? 0 : correlations.filter((c) => c.requests.length >= 1).length / correlations.length;
|
|
130
|
+
return {
|
|
131
|
+
correlations,
|
|
132
|
+
uniqueEndpoints,
|
|
133
|
+
graphqlOperations,
|
|
134
|
+
totalRequestsCaptured,
|
|
135
|
+
coverage
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function attachNetworkInterceptor(page, correlator) {
|
|
141
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
142
|
+
const onRequest = (req) => {
|
|
143
|
+
const url = req.url();
|
|
144
|
+
const resourceType = req.resourceType();
|
|
145
|
+
if (!isApiRequest(url, resourceType)) return;
|
|
146
|
+
let bodyText = "";
|
|
147
|
+
try {
|
|
148
|
+
bodyText = req.postData() ?? "";
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
pendingRequests.set(url, { bodyText, resourceType });
|
|
152
|
+
};
|
|
153
|
+
const onResponse = async (res) => {
|
|
154
|
+
const url = res.url();
|
|
155
|
+
const pending = pendingRequests.get(url);
|
|
156
|
+
if (!pending) return;
|
|
157
|
+
pendingRequests.delete(url);
|
|
158
|
+
const method = res.request().method();
|
|
159
|
+
let bodySchema = null;
|
|
160
|
+
try {
|
|
161
|
+
bodySchema = extractBodySchema(pending.bodyText);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
let responseSchema = null;
|
|
165
|
+
let isGraphQL = false;
|
|
166
|
+
let graphQLOperation;
|
|
167
|
+
const contentType = res.headers()["content-type"] ?? "";
|
|
168
|
+
if (contentType.includes("application/json")) {
|
|
169
|
+
try {
|
|
170
|
+
const respText = await res.text();
|
|
171
|
+
if (respText.length < 2e4) {
|
|
172
|
+
responseSchema = extractBodySchema(respText);
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const bodyParsed = JSON.parse(pending.bodyText);
|
|
179
|
+
if (typeof bodyParsed?.operationName === "string") {
|
|
180
|
+
isGraphQL = true;
|
|
181
|
+
graphQLOperation = bodyParsed.operationName;
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
const rec = {
|
|
186
|
+
url,
|
|
187
|
+
urlPattern: extractUrlPattern(url),
|
|
188
|
+
method,
|
|
189
|
+
bodySchema,
|
|
190
|
+
status: res.status(),
|
|
191
|
+
responseSchema,
|
|
192
|
+
isGraphQL,
|
|
193
|
+
graphQLOperation,
|
|
194
|
+
resourceType: pending.resourceType
|
|
195
|
+
};
|
|
196
|
+
correlator.recordRequest(rec);
|
|
197
|
+
};
|
|
198
|
+
page.on("request", onRequest);
|
|
199
|
+
page.on("response", onResponse);
|
|
200
|
+
return () => {
|
|
201
|
+
page.off("request", onRequest);
|
|
202
|
+
page.off("response", onResponse);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
attachNetworkInterceptor,
|
|
207
|
+
createCorrelator
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=network-action-correlator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/network-action-correlator.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Network-action correlator \u2014 captures XHR/fetch requests that occur during\n * a named UI action (click, form submit, navigation) and maps them to API\n * endpoint contracts.\n *\n * Usage:\n * const correlator = createCorrelator();\n * const detach = attachNetworkInterceptor(page, correlator);\n * correlator.startWindow('Submit login form');\n * await page.click('#login-btn');\n * const correlation = correlator.endWindow();\n * detach();\n * const result = correlator.getResult();\n */\n\nimport type { Page, Request, Response } from 'playwright';\n\nexport interface NetworkRequestRecord {\n url: string;\n urlPattern: string;\n method: string;\n bodySchema: Record<string, string> | null;\n status: number | null;\n responseSchema: Record<string, string> | null;\n isGraphQL: boolean;\n graphQLOperation?: string;\n resourceType: string;\n correlatedActionLabel?: string;\n}\n\nexport interface ActionCorrelation {\n actionLabel: string;\n requests: NetworkRequestRecord[];\n}\n\nexport interface NetworkCorrelationResult {\n correlations: ActionCorrelation[];\n uniqueEndpoints: string[];\n graphqlOperations: string[];\n totalRequestsCaptured: number;\n coverage: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction extractUrlPattern(url: string): string {\n try {\n const u = new URL(url);\n const path = u.pathname\n .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, ':uuid')\n .replace(/\\/[0-9a-f]{24}(\\/|$)/g, '/:mongoId$1')\n .replace(/\\/\\d{6,}(\\/|$)/g, '/:id$1')\n .replace(/\\/\\d{1,5}(\\/|$)/g, '/:id$1');\n return path;\n } catch {\n return url.slice(0, 80);\n }\n}\n\nfunction extractBodySchema(bodyText: string): Record<string, string> | null {\n if (!bodyText || bodyText.length > 50_000) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyText);\n } catch {\n return null;\n }\n\n const schema: Record<string, string> = {};\n let keyCount = 0;\n\n function traverse(value: unknown, prefix: string, depth: number): void {\n if (keyCount >= 30 || depth > 3) return;\n if (value === null || value === undefined) return;\n\n if (Array.isArray(value)) {\n schema[prefix] = 'array';\n keyCount++;\n return;\n }\n\n if (typeof value === 'object') {\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (keyCount >= 30) break;\n const key = prefix ? `${prefix}.${k}` : k;\n if (v === null) {\n schema[key] = 'null';\n keyCount++;\n } else if (Array.isArray(v)) {\n schema[key] = 'array';\n keyCount++;\n } else if (typeof v === 'object') {\n traverse(v, key, depth + 1);\n } else {\n schema[key] = typeof v;\n keyCount++;\n }\n }\n return;\n }\n\n // Primitive at top level\n if (prefix) {\n schema[prefix] = typeof value;\n keyCount++;\n }\n }\n\n traverse(parsed, '', 0);\n return keyCount > 0 ? schema : null;\n}\n\nconst ANALYTICS_DOMAINS = [\n 'google-analytics',\n 'doubleclick',\n 'hotjar',\n 'mixpanel',\n 'segment',\n 'sentry',\n 'datadog',\n 'newrelic',\n 'amplitude',\n 'fullstory',\n];\n\nconst API_PATH_PATTERNS = [\n '/api/',\n '/graphql',\n '/v1/',\n '/v2/',\n '/rest/',\n '/query',\n '/rpc',\n '/trpc',\n];\n\nconst EXCLUDED_RESOURCE_TYPES = new Set(['image', 'font', 'stylesheet', 'media', 'websocket']);\n\nfunction isApiRequest(url: string, resourceType: string): boolean {\n if (EXCLUDED_RESOURCE_TYPES.has(resourceType)) return false;\n\n try {\n const hostname = new URL(url).hostname;\n if (ANALYTICS_DOMAINS.some((d) => hostname.includes(d))) return false;\n } catch {\n return false;\n }\n\n if (resourceType === 'xhr' || resourceType === 'fetch') return true;\n\n const lower = url.toLowerCase();\n return API_PATH_PATTERNS.some((p) => lower.includes(p));\n}\n\n// ---------------------------------------------------------------------------\n// Correlator\n// ---------------------------------------------------------------------------\n\ninterface NetworkCorrelator {\n startWindow(actionLabel: string): void;\n recordRequest(rec: NetworkRequestRecord): void;\n endWindow(): ActionCorrelation;\n getResult(): NetworkCorrelationResult;\n}\n\nexport function createCorrelator(): NetworkCorrelator {\n let currentLabel: string | null = null;\n let currentRequests: NetworkRequestRecord[] = [];\n const correlations: ActionCorrelation[] = [];\n\n return {\n startWindow(actionLabel: string): void {\n currentLabel = actionLabel;\n currentRequests = [];\n },\n\n recordRequest(rec: NetworkRequestRecord): void {\n if (currentLabel === null) return;\n currentRequests.push({ ...rec, correlatedActionLabel: currentLabel });\n },\n\n endWindow(): ActionCorrelation {\n const label = currentLabel ?? '(unknown)';\n const correlation: ActionCorrelation = {\n actionLabel: label,\n requests: currentRequests,\n };\n correlations.push(correlation);\n currentLabel = null;\n currentRequests = [];\n return correlation;\n },\n\n getResult(): NetworkCorrelationResult {\n const uniqueEndpoints = [\n ...new Set(\n correlations.flatMap((c) => c.requests.map((r) => r.urlPattern)),\n ),\n ];\n\n const graphqlOperations = [\n ...new Set(\n correlations\n .flatMap((c) => c.requests)\n .filter((r) => r.isGraphQL && r.graphQLOperation)\n .map((r) => r.graphQLOperation as string),\n ),\n ];\n\n const totalRequestsCaptured = correlations.reduce(\n (sum, c) => sum + c.requests.length,\n 0,\n );\n\n const coverage =\n correlations.length === 0\n ? 0\n : correlations.filter((c) => c.requests.length >= 1).length /\n correlations.length;\n\n return {\n correlations,\n uniqueEndpoints,\n graphqlOperations,\n totalRequestsCaptured,\n coverage,\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Page interceptor\n// ---------------------------------------------------------------------------\n\nexport function attachNetworkInterceptor(\n page: Page,\n correlator: ReturnType<typeof createCorrelator>,\n): () => void {\n const pendingRequests = new Map<string, { bodyText: string; resourceType: string }>();\n\n const onRequest = (req: Request): void => {\n const url = req.url();\n const resourceType = req.resourceType();\n if (!isApiRequest(url, resourceType)) return;\n let bodyText = '';\n try {\n bodyText = req.postData() ?? '';\n } catch {\n // postData() may throw on non-POST requests in some Playwright versions\n }\n pendingRequests.set(url, { bodyText, resourceType });\n };\n\n const onResponse = async (res: Response): Promise<void> => {\n const url = res.url();\n const pending = pendingRequests.get(url);\n if (!pending) return;\n pendingRequests.delete(url);\n\n const method = res.request().method();\n\n let bodySchema: Record<string, string> | null = null;\n try {\n bodySchema = extractBodySchema(pending.bodyText);\n } catch {\n // ignore schema extraction failures\n }\n\n let responseSchema: Record<string, string> | null = null;\n let isGraphQL = false;\n let graphQLOperation: string | undefined;\n\n const contentType = res.headers()['content-type'] ?? '';\n if (contentType.includes('application/json')) {\n try {\n const respText = await res.text();\n if (respText.length < 20_000) {\n responseSchema = extractBodySchema(respText);\n }\n } catch {\n // response body may no longer be available\n }\n }\n\n // Detect GraphQL from request body\n try {\n const bodyParsed = JSON.parse(pending.bodyText) as Record<string, unknown>;\n if (typeof bodyParsed?.operationName === 'string') {\n isGraphQL = true;\n graphQLOperation = bodyParsed.operationName;\n }\n } catch {\n // not JSON or no operationName\n }\n\n const rec: NetworkRequestRecord = {\n url,\n urlPattern: extractUrlPattern(url),\n method,\n bodySchema,\n status: res.status(),\n responseSchema,\n isGraphQL,\n graphQLOperation,\n resourceType: pending.resourceType,\n };\n correlator.recordRequest(rec);\n };\n\n page.on('request', onRequest);\n page.on('response', onResponse);\n\n return () => {\n page.off('request', onRequest);\n page.off('response', onResponse);\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BrowserContext } from 'playwright';
|
|
2
|
+
export interface ApiEndpointObservationInput {
|
|
3
|
+
method: string;
|
|
4
|
+
urlPattern: string;
|
|
5
|
+
statusCode?: number;
|
|
6
|
+
requestKind?: string;
|
|
7
|
+
responseShape?: unknown;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
errorBody?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function attachNetworkObserver(context: BrowserContext, sink: ApiEndpointObservationInput[], onPageUrlDiscovered?: (relPath: string) => void): void;
|
|
12
|
+
export interface HarEntry {
|
|
13
|
+
startedAt: string;
|
|
14
|
+
method: string;
|
|
15
|
+
url: string;
|
|
16
|
+
status: number;
|
|
17
|
+
statusText: string;
|
|
18
|
+
requestHeaders: Record<string, string>;
|
|
19
|
+
responseHeaders: Record<string, string>;
|
|
20
|
+
contentType: string;
|
|
21
|
+
bodySize: number;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
timings: {
|
|
24
|
+
send: number;
|
|
25
|
+
wait: number;
|
|
26
|
+
receive: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface HarLog {
|
|
30
|
+
version: '1.2';
|
|
31
|
+
creator: {
|
|
32
|
+
name: 'ZeTa-Crawler';
|
|
33
|
+
version: '1.0';
|
|
34
|
+
};
|
|
35
|
+
pages: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
startedAt: string;
|
|
39
|
+
}>;
|
|
40
|
+
entries: HarEntry[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Full HAR capture via CDP — intercepts ALL network traffic, not just API endpoints.
|
|
44
|
+
* Returns a controller with getHar() and detach().
|
|
45
|
+
*/
|
|
46
|
+
export declare function attachHarCapture(cdpSession: any, pageTitle?: string): {
|
|
47
|
+
getHar(): HarLog;
|
|
48
|
+
detach(): void;
|
|
49
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { redactJson } from "./policies/crawl-safety.js";
|
|
2
|
+
const API_PATTERN = /\/(api|graphql|gql|rest|v\d+)\//i;
|
|
3
|
+
const ASSET_EXT = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json\.map)(\?|$)/i;
|
|
4
|
+
function normalizeApiUrl(url) {
|
|
5
|
+
const u = new URL(url);
|
|
6
|
+
return u.pathname.replace(/\/[a-f0-9-]{8,}(?=\/|$)/gi, "/{id}").replace(/\/c[a-z0-9]{20,}(?=\/|$)/g, "/{id}").replace(/\/\d+(?=\/|$)/g, "/{id}");
|
|
7
|
+
}
|
|
8
|
+
function inferShape(value) {
|
|
9
|
+
if (Array.isArray(value)) return value.length > 0 ? [inferShape(value[0])] : [];
|
|
10
|
+
if (value && typeof value === "object") {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const [key, item] of Object.entries(value).slice(0, 40)) out[key] = inferShape(item);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
return typeof value;
|
|
16
|
+
}
|
|
17
|
+
function extractPageUrls(jsonStr) {
|
|
18
|
+
const PAGE_URL = /"(?:url|href|path|link|route|to|src|redirect)"\s*:\s*"(\/[\w\-\/\.~@%]+)"/g;
|
|
19
|
+
const ASSET = /\.(png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|css|js|map|json)$/i;
|
|
20
|
+
const API_PATH = /^\/(api|v\d+|graphql|gql|rest|rpc|static|_next|assets)\//i;
|
|
21
|
+
const found = /* @__PURE__ */ new Set();
|
|
22
|
+
let m;
|
|
23
|
+
PAGE_URL.lastIndex = 0;
|
|
24
|
+
while ((m = PAGE_URL.exec(jsonStr)) !== null) {
|
|
25
|
+
const u = m[1];
|
|
26
|
+
if (u && u.length > 1 && u.length < 120 && !ASSET.test(u) && !API_PATH.test(u)) found.add(u);
|
|
27
|
+
}
|
|
28
|
+
return [...found];
|
|
29
|
+
}
|
|
30
|
+
function attachNetworkObserver(context, sink, onPageUrlDiscovered) {
|
|
31
|
+
const seen = /* @__PURE__ */ new Map();
|
|
32
|
+
const requestTimes = /* @__PURE__ */ new Map();
|
|
33
|
+
context.on("request", (request) => {
|
|
34
|
+
try {
|
|
35
|
+
const url = request.url();
|
|
36
|
+
const rtype = request.resourceType();
|
|
37
|
+
if (rtype !== "xhr" && rtype !== "fetch" || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;
|
|
38
|
+
const method = request.method();
|
|
39
|
+
const normalized = normalizeApiUrl(url);
|
|
40
|
+
const key = `${method}:${normalized}`;
|
|
41
|
+
requestTimes.set(url, Date.now());
|
|
42
|
+
if (!seen.has(key)) {
|
|
43
|
+
const row = { method, urlPattern: normalized, requestKind: rtype };
|
|
44
|
+
seen.set(key, row);
|
|
45
|
+
sink.push(row);
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
context.on("response", async (response) => {
|
|
51
|
+
try {
|
|
52
|
+
const request = response.request();
|
|
53
|
+
const url = request.url();
|
|
54
|
+
const rtype = request.resourceType();
|
|
55
|
+
if (rtype !== "xhr" && rtype !== "fetch" || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;
|
|
56
|
+
const normalized = normalizeApiUrl(url);
|
|
57
|
+
const key = `${request.method()}:${normalized}`;
|
|
58
|
+
const durationMs = Date.now() - (requestTimes.get(url) ?? Date.now());
|
|
59
|
+
let row = seen.get(key);
|
|
60
|
+
if (!row) {
|
|
61
|
+
row = { method: request.method(), urlPattern: normalized, requestKind: rtype };
|
|
62
|
+
seen.set(key, row);
|
|
63
|
+
sink.push(row);
|
|
64
|
+
}
|
|
65
|
+
row.statusCode = response.status();
|
|
66
|
+
row.durationMs = durationMs;
|
|
67
|
+
const contentType = response.headers()["content-type"] ?? "";
|
|
68
|
+
if (contentType.includes("application/json")) {
|
|
69
|
+
const body = await response.json().catch(() => void 0);
|
|
70
|
+
if (body !== void 0) {
|
|
71
|
+
row.responseShape = redactJson(inferShape(body));
|
|
72
|
+
if (onPageUrlDiscovered) {
|
|
73
|
+
for (const u of extractPageUrls(JSON.stringify(body))) onPageUrlDiscovered(u);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (response.status() >= 400) {
|
|
78
|
+
try {
|
|
79
|
+
const errText = await response.text();
|
|
80
|
+
row.errorBody = errText.slice(0, 300);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function attachHarCapture(cdpSession, pageTitle = "Page") {
|
|
89
|
+
const entries = [];
|
|
90
|
+
const pending = /* @__PURE__ */ new Map();
|
|
91
|
+
const pageId = `page_${String(Date.now())}`;
|
|
92
|
+
const pageStarted = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
|
+
const onRequest = (evt) => {
|
|
94
|
+
const { requestId, request } = evt ?? {};
|
|
95
|
+
if (!requestId || !request) return;
|
|
96
|
+
const headers = {};
|
|
97
|
+
for (const h of request.headers ?? []) if (h.name) headers[String(h.name).toLowerCase()] = String(h.value ?? "");
|
|
98
|
+
pending.set(requestId, { method: request.method ?? "GET", url: request.url ?? "", headers, startMs: Date.now() });
|
|
99
|
+
};
|
|
100
|
+
const onResponse = (evt) => {
|
|
101
|
+
const { requestId, response } = evt ?? {};
|
|
102
|
+
if (!requestId || !response) return;
|
|
103
|
+
const req = pending.get(requestId);
|
|
104
|
+
if (!req) return;
|
|
105
|
+
pending.delete(requestId);
|
|
106
|
+
const durationMs = Date.now() - req.startMs;
|
|
107
|
+
const responseHeaders = {};
|
|
108
|
+
for (const h of response.headers ?? []) if (h.name) responseHeaders[String(h.name).toLowerCase()] = String(h.value ?? "");
|
|
109
|
+
entries.push({
|
|
110
|
+
startedAt: new Date(req.startMs).toISOString(),
|
|
111
|
+
method: req.method,
|
|
112
|
+
url: req.url,
|
|
113
|
+
status: response.status ?? 0,
|
|
114
|
+
statusText: response.statusText ?? "",
|
|
115
|
+
requestHeaders: req.headers,
|
|
116
|
+
responseHeaders,
|
|
117
|
+
contentType: responseHeaders["content-type"] ?? "",
|
|
118
|
+
bodySize: response.encodedDataLength ?? -1,
|
|
119
|
+
durationMs,
|
|
120
|
+
timings: { send: 0, wait: Math.round(durationMs * 0.9), receive: Math.round(durationMs * 0.1) }
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
if (cdpSession?.on) {
|
|
124
|
+
cdpSession.on("Network.requestWillBeSent", onRequest);
|
|
125
|
+
cdpSession.on("Network.responseReceived", onResponse);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
getHar() {
|
|
129
|
+
return {
|
|
130
|
+
version: "1.2",
|
|
131
|
+
creator: { name: "ZeTa-Crawler", version: "1.0" },
|
|
132
|
+
pages: [{ id: pageId, title: pageTitle, startedAt: pageStarted }],
|
|
133
|
+
entries: [...entries]
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
detach() {
|
|
137
|
+
if (cdpSession?.off) {
|
|
138
|
+
cdpSession.off("Network.requestWillBeSent", onRequest);
|
|
139
|
+
cdpSession.off("Network.responseReceived", onResponse);
|
|
140
|
+
}
|
|
141
|
+
pending.clear();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
attachHarCapture,
|
|
147
|
+
attachNetworkObserver
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=network-observer.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/network-observer.ts"],
|
|
4
|
+
"sourcesContent": ["import type { BrowserContext } from 'playwright';\nimport { redactJson } from './policies/crawl-safety.js';\n\nexport interface ApiEndpointObservationInput {\n method: string;\n urlPattern: string;\n statusCode?: number;\n requestKind?: string;\n responseShape?: unknown;\n durationMs?: number;\n errorBody?: string;\n}\n\nconst API_PATTERN = /\\/(api|graphql|gql|rest|v\\d+)\\//i;\nconst ASSET_EXT = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json\\.map)(\\?|$)/i;\n\nfunction normalizeApiUrl(url: string): string {\n const u = new URL(url);\n return u.pathname\n .replace(/\\/[a-f0-9-]{8,}(?=\\/|$)/gi, '/{id}')\n .replace(/\\/c[a-z0-9]{20,}(?=\\/|$)/g, '/{id}')\n .replace(/\\/\\d+(?=\\/|$)/g, '/{id}');\n}\n\nfunction inferShape(value: unknown): unknown {\n if (Array.isArray(value)) return value.length > 0 ? [inferShape(value[0])] : [];\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value).slice(0, 40)) out[key] = inferShape(item);\n return out;\n }\n return typeof value;\n}\n\n/** P4: Extract page-URL candidates from a JSON response body string. */\nfunction extractPageUrls(jsonStr: string): string[] {\n const PAGE_URL = /\"(?:url|href|path|link|route|to|src|redirect)\"\\s*:\\s*\"(\\/[\\w\\-\\/\\.~@%]+)\"/g;\n const ASSET = /\\.(png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|css|js|map|json)$/i;\n const API_PATH = /^\\/(api|v\\d+|graphql|gql|rest|rpc|static|_next|assets)\\//i;\n const found = new Set<string>();\n let m: RegExpExecArray | null;\n PAGE_URL.lastIndex = 0;\n while ((m = PAGE_URL.exec(jsonStr)) !== null) {\n const u = m[1];\n if (u && u.length > 1 && u.length < 120 && !ASSET.test(u) && !API_PATH.test(u)) found.add(u);\n }\n return [...found];\n}\n\nexport function attachNetworkObserver(\n context: BrowserContext,\n sink: ApiEndpointObservationInput[],\n onPageUrlDiscovered?: (relPath: string) => void,\n) {\n const seen = new Map<string, ApiEndpointObservationInput>();\n const requestTimes = new Map<string, number>();\n\n context.on('request', (request) => {\n try {\n const url = request.url();\n const rtype = request.resourceType();\n if ((rtype !== 'xhr' && rtype !== 'fetch') || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;\n const method = request.method();\n const normalized = normalizeApiUrl(url);\n const key = `${method}:${normalized}`;\n requestTimes.set(url, Date.now());\n if (!seen.has(key)) {\n const row = { method, urlPattern: normalized, requestKind: rtype };\n seen.set(key, row);\n sink.push(row);\n }\n } catch {\n // non-fatal\n }\n });\n\n context.on('response', async (response) => {\n try {\n const request = response.request();\n const url = request.url();\n const rtype = request.resourceType();\n if ((rtype !== 'xhr' && rtype !== 'fetch') || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;\n const normalized = normalizeApiUrl(url);\n const key = `${request.method()}:${normalized}`;\n const durationMs = Date.now() - (requestTimes.get(url) ?? Date.now());\n let row = seen.get(key);\n if (!row) {\n row = { method: request.method(), urlPattern: normalized, requestKind: rtype };\n seen.set(key, row);\n sink.push(row);\n }\n row.statusCode = response.status();\n row.durationMs = durationMs;\n const contentType = response.headers()['content-type'] ?? '';\n if (contentType.includes('application/json')) {\n const body = await response.json().catch(() => undefined);\n if (body !== undefined) {\n row.responseShape = redactJson(inferShape(body));\n // P4: scan JSON body for page-URL candidates (non-API paths)\n if (onPageUrlDiscovered) {\n for (const u of extractPageUrls(JSON.stringify(body))) onPageUrlDiscovered(u);\n }\n }\n }\n if (response.status() >= 400) {\n try {\n const errText = await response.text();\n row.errorBody = errText.slice(0, 300);\n } catch { /* non-fatal */ }\n }\n } catch {\n // non-fatal\n }\n });\n}\n\n// \u2500\u2500 Full HAR capture \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\u2500\u2500\u2500\u2500\u2500\n\nexport interface HarEntry {\n startedAt: string;\n method: string;\n url: string;\n status: number;\n statusText: string;\n requestHeaders: Record<string, string>;\n responseHeaders: Record<string, string>;\n contentType: string;\n bodySize: number;\n durationMs: number;\n timings: { send: number; wait: number; receive: number };\n}\n\nexport interface HarLog {\n version: '1.2';\n creator: { name: 'ZeTa-Crawler'; version: '1.0' };\n pages: Array<{ id: string; title: string; startedAt: string }>;\n entries: HarEntry[];\n}\n\n/**\n * Full HAR capture via CDP \u2014 intercepts ALL network traffic, not just API endpoints.\n * Returns a controller with getHar() and detach().\n */\nexport function attachHarCapture(cdpSession: any, pageTitle = 'Page'): { getHar(): HarLog; detach(): void } {\n const entries: HarEntry[] = [];\n const pending = new Map<string, { method: string; url: string; headers: Record<string, string>; startMs: number }>();\n const pageId = `page_${String(Date.now())}`;\n const pageStarted = new Date().toISOString();\n\n const onRequest = (evt: any) => {\n const { requestId, request } = evt ?? {};\n if (!requestId || !request) return;\n const headers: Record<string, string> = {};\n for (const h of (request.headers ?? [])) if (h.name) headers[String(h.name).toLowerCase()] = String(h.value ?? '');\n pending.set(requestId, { method: request.method ?? 'GET', url: request.url ?? '', headers, startMs: Date.now() });\n };\n\n const onResponse = (evt: any) => {\n const { requestId, response } = evt ?? {};\n if (!requestId || !response) return;\n const req = pending.get(requestId);\n if (!req) return;\n pending.delete(requestId);\n const durationMs = Date.now() - req.startMs;\n const responseHeaders: Record<string, string> = {};\n for (const h of (response.headers ?? [])) if (h.name) responseHeaders[String(h.name).toLowerCase()] = String(h.value ?? '');\n entries.push({\n startedAt: new Date(req.startMs).toISOString(),\n method: req.method,\n url: req.url,\n status: response.status ?? 0,\n statusText: response.statusText ?? '',\n requestHeaders: req.headers,\n responseHeaders,\n contentType: responseHeaders['content-type'] ?? '',\n bodySize: response.encodedDataLength ?? -1,\n durationMs,\n timings: { send: 0, wait: Math.round(durationMs * 0.9), receive: Math.round(durationMs * 0.1) },\n });\n };\n\n if (cdpSession?.on) {\n cdpSession.on('Network.requestWillBeSent', onRequest);\n cdpSession.on('Network.responseReceived', onResponse);\n }\n\n return {\n getHar(): HarLog {\n return {\n version: '1.2',\n creator: { name: 'ZeTa-Crawler', version: '1.0' },\n pages: [{ id: pageId, title: pageTitle, startedAt: pageStarted }],\n entries: [...entries],\n };\n },\n detach() {\n if (cdpSession?.off) {\n cdpSession.off('Network.requestWillBeSent', onRequest);\n cdpSession.off('Network.responseReceived', onResponse);\n }\n pending.clear();\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network throttle simulation via CDP.
|
|
3
|
+
*
|
|
4
|
+
* Simulates different network conditions during crawl to test how the app
|
|
5
|
+
* behaves under slow connections. Useful for detecting loading states, error
|
|
6
|
+
* handling, and performance degradation on poor networks.
|
|
7
|
+
*
|
|
8
|
+
* Conditions mirror Chrome DevTools presets:
|
|
9
|
+
* - slow3g: ~500kbps download, high latency
|
|
10
|
+
* - fast3g: ~1.6Mbps download, medium latency
|
|
11
|
+
* - 4g: ~9Mbps download, low latency
|
|
12
|
+
* - offline: no connectivity
|
|
13
|
+
* - none: no throttle (default)
|
|
14
|
+
*/
|
|
15
|
+
export type NetworkConditionPreset = 'none' | 'slow3g' | 'fast3g' | '4g' | 'offline';
|
|
16
|
+
export declare function applyNetworkThrottle(cdpSession: any, preset: NetworkConditionPreset): Promise<void>;
|
|
17
|
+
export declare function clearNetworkThrottle(cdpSession: any): Promise<void>;
|
|
18
|
+
export interface ThrottledCrawlOpts {
|
|
19
|
+
/** Network preset to apply during this URL's crawl */
|
|
20
|
+
preset: NetworkConditionPreset;
|
|
21
|
+
/** Restore to 'none' after navigation (default: true) */
|
|
22
|
+
restoreAfter?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Apply throttle, run fn, restore. Returns fn's result. */
|
|
25
|
+
export declare function withNetworkThrottle<T>(cdpSession: any, preset: NetworkConditionPreset, fn: () => Promise<T>, restoreAfter?: boolean): Promise<T>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const PRESETS = {
|
|
2
|
+
none: { offline: false, downloadThroughput: -1, uploadThroughput: -1, latency: 0 },
|
|
3
|
+
slow3g: { offline: false, downloadThroughput: 500 * 1024 / 8, uploadThroughput: 500 * 1024 / 8, latency: 2e3 },
|
|
4
|
+
fast3g: { offline: false, downloadThroughput: 1.6 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 562.5 },
|
|
5
|
+
"4g": { offline: false, downloadThroughput: 9 * 1024 * 1024 / 8, uploadThroughput: 2.67 * 1024 * 1024 / 8, latency: 85 },
|
|
6
|
+
offline: { offline: true, downloadThroughput: 0, uploadThroughput: 0, latency: 0 }
|
|
7
|
+
};
|
|
8
|
+
async function applyNetworkThrottle(cdpSession, preset) {
|
|
9
|
+
if (!cdpSession?.send) return;
|
|
10
|
+
const condition = PRESETS[preset];
|
|
11
|
+
try {
|
|
12
|
+
await cdpSession.send("Network.enable");
|
|
13
|
+
await cdpSession.send("Network.emulateNetworkConditions", condition);
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function clearNetworkThrottle(cdpSession) {
|
|
18
|
+
await applyNetworkThrottle(cdpSession, "none");
|
|
19
|
+
}
|
|
20
|
+
async function withNetworkThrottle(cdpSession, preset, fn, restoreAfter = true) {
|
|
21
|
+
await applyNetworkThrottle(cdpSession, preset);
|
|
22
|
+
try {
|
|
23
|
+
return await fn();
|
|
24
|
+
} finally {
|
|
25
|
+
if (restoreAfter) await clearNetworkThrottle(cdpSession).catch(() => {
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
applyNetworkThrottle,
|
|
31
|
+
clearNetworkThrottle,
|
|
32
|
+
withNetworkThrottle
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=network-throttle.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/network-throttle.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Network throttle simulation via CDP.\n *\n * Simulates different network conditions during crawl to test how the app\n * behaves under slow connections. Useful for detecting loading states, error\n * handling, and performance degradation on poor networks.\n *\n * Conditions mirror Chrome DevTools presets:\n * - slow3g: ~500kbps download, high latency\n * - fast3g: ~1.6Mbps download, medium latency\n * - 4g: ~9Mbps download, low latency\n * - offline: no connectivity\n * - none: no throttle (default)\n */\n\nexport type NetworkConditionPreset = 'none' | 'slow3g' | 'fast3g' | '4g' | 'offline';\n\ninterface NetworkCondition {\n offline: boolean;\n downloadThroughput: number; // bytes/s\n uploadThroughput: number; // bytes/s\n latency: number; // ms\n}\n\nconst PRESETS: Record<NetworkConditionPreset, NetworkCondition> = {\n none: { offline: false, downloadThroughput: -1, uploadThroughput: -1, latency: 0 },\n slow3g: { offline: false, downloadThroughput: 500 * 1024 / 8, uploadThroughput: 500 * 1024 / 8, latency: 2_000 },\n fast3g: { offline: false, downloadThroughput: 1.6 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 562.5 },\n '4g': { offline: false, downloadThroughput: 9 * 1024 * 1024 / 8, uploadThroughput: 2.67 * 1024 * 1024 / 8, latency: 85 },\n offline: { offline: true, downloadThroughput: 0, uploadThroughput: 0, latency: 0 },\n};\n\nexport async function applyNetworkThrottle(\n cdpSession: any,\n preset: NetworkConditionPreset,\n): Promise<void> {\n if (!cdpSession?.send) return;\n const condition = PRESETS[preset];\n try {\n await cdpSession.send('Network.enable');\n await cdpSession.send('Network.emulateNetworkConditions', condition);\n } catch { /* non-fatal \u2014 CDP may not be available */ }\n}\n\nexport async function clearNetworkThrottle(cdpSession: any): Promise<void> {\n await applyNetworkThrottle(cdpSession, 'none');\n}\n\nexport interface ThrottledCrawlOpts {\n /** Network preset to apply during this URL's crawl */\n preset: NetworkConditionPreset;\n /** Restore to 'none' after navigation (default: true) */\n restoreAfter?: boolean;\n}\n\n/** Apply throttle, run fn, restore. Returns fn's result. */\nexport async function withNetworkThrottle<T>(\n cdpSession: any,\n preset: NetworkConditionPreset,\n fn: () => Promise<T>,\n restoreAfter = true,\n): Promise<T> {\n await applyNetworkThrottle(cdpSession, preset);\n try {\n return await fn();\n } finally {\n if (restoreAfter) await clearNetworkThrottle(cdpSession).catch(() => {});\n }\n}\n"],
|
|
5
|
+
"mappings": "AAwBA,MAAM,UAA4D;AAAA,EAChE,MAAS,EAAE,SAAS,OAAO,oBAAoB,IAAI,kBAAkB,IAAI,SAAS,EAAE;AAAA,EACpF,QAAS,EAAE,SAAS,OAAO,oBAAoB,MAAM,OAAO,GAAG,kBAAkB,MAAM,OAAO,GAAG,SAAS,IAAM;AAAA,EAChH,QAAS,EAAE,SAAS,OAAO,oBAAoB,MAAM,OAAO,OAAO,GAAG,kBAAkB,MAAM,OAAO,GAAG,SAAS,MAAM;AAAA,EACvH,MAAS,EAAE,SAAS,OAAO,oBAAoB,IAAI,OAAO,OAAO,GAAG,kBAAkB,OAAO,OAAO,OAAO,GAAG,SAAS,GAAG;AAAA,EAC1H,SAAS,EAAE,SAAS,MAAM,oBAAoB,GAAG,kBAAkB,GAAG,SAAS,EAAE;AACnF;AAEA,eAAsB,qBACpB,YACA,QACe;AACf,MAAI,CAAC,YAAY,KAAM;AACvB,QAAM,YAAY,QAAQ,MAAM;AAChC,MAAI;AACF,UAAM,WAAW,KAAK,gBAAgB;AACtC,UAAM,WAAW,KAAK,oCAAoC,SAAS;AAAA,EACrE,QAAQ;AAAA,EAA6C;AACvD;AAEA,eAAsB,qBAAqB,YAAgC;AACzE,QAAM,qBAAqB,YAAY,MAAM;AAC/C;AAUA,eAAsB,oBACpB,YACA,QACA,IACA,eAAe,MACH;AACZ,QAAM,qBAAqB,YAAY,MAAM;AAC7C,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,QAAI,aAAc,OAAM,qBAAqB,UAAU,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI 3.0 spec generator from observed network traffic.
|
|
3
|
+
*
|
|
4
|
+
* Takes the ApiEndpointObservationInput[] collected by the network observer
|
|
5
|
+
* during crawl and generates a minimal but valid OpenAPI 3.0 spec.
|
|
6
|
+
*
|
|
7
|
+
* The spec captures: endpoints, methods, observed response shapes, status codes.
|
|
8
|
+
* It does NOT capture request bodies (we'd need to intercept requests too) — that's
|
|
9
|
+
* marked as a TODO. But it's enough for generating smoke-test suites.
|
|
10
|
+
*/
|
|
11
|
+
import type { ApiEndpointObservationInput } from './network-observer.js';
|
|
12
|
+
export interface OpenApiSpec {
|
|
13
|
+
openapi: '3.0.3';
|
|
14
|
+
info: {
|
|
15
|
+
title: string;
|
|
16
|
+
version: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
};
|
|
19
|
+
servers: Array<{
|
|
20
|
+
url: string;
|
|
21
|
+
}>;
|
|
22
|
+
paths: Record<string, Record<string, OpenApiOperation>>;
|
|
23
|
+
}
|
|
24
|
+
interface OpenApiOperation {
|
|
25
|
+
summary: string;
|
|
26
|
+
parameters?: OpenApiParam[];
|
|
27
|
+
responses: Record<string, {
|
|
28
|
+
description: string;
|
|
29
|
+
content?: Record<string, {
|
|
30
|
+
schema: unknown;
|
|
31
|
+
}>;
|
|
32
|
+
}>;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
}
|
|
35
|
+
interface OpenApiParam {
|
|
36
|
+
name: string;
|
|
37
|
+
in: 'path' | 'query';
|
|
38
|
+
required: boolean;
|
|
39
|
+
schema: {
|
|
40
|
+
type: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export declare function generateOpenApiSpec(observations: ApiEndpointObservationInput[], opts?: {
|
|
44
|
+
title?: string;
|
|
45
|
+
baseUrl?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
}): OpenApiSpec;
|
|
48
|
+
export declare function specToYaml(spec: OpenApiSpec): string;
|
|
49
|
+
export {};
|