@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70
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/AUTHORING.md +943 -51
- package/CHANGELOG.md +273 -1
- package/LICENSE +21 -0
- package/README.md +117 -29
- package/SUBMISSION.md +3 -2
- package/bin/apifuse-check.ts +293 -13
- package/bin/apifuse-dev.ts +42 -19
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +19 -6
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +736 -38
- package/bin/apifuse-perf.ts +59 -23
- package/bin/apifuse-record.ts +923 -94
- package/bin/apifuse-submit-check.ts +2313 -281
- package/bin/apifuse-test.ts +3 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +30 -0
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +8 -2
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/local-connection.d.ts +111 -0
- package/dist/cli/local-connection.js +151 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-operation-declaration.d.ts +70 -0
- package/dist/cli/migrate-operation-declaration.js +2206 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +56 -0
- package/dist/cli/migrate-provider-shape.js +580 -0
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +41 -19
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +202 -16
- package/dist/config/loader.js +491 -141
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +34 -0
- package/dist/declaration-validation.js +305 -0
- package/dist/define.d.ts +71 -29
- package/dist/define.js +807 -69
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +8 -1
- package/dist/engine.d.ts +129 -0
- package/dist/engine.js +209 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +18 -0
- package/dist/error-resolution.js +243 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +99 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +371 -0
- package/dist/handle-meta.d.ts +90 -0
- package/dist/handle-meta.js +73 -0
- package/dist/handle.d.ts +226 -0
- package/dist/handle.js +345 -0
- package/dist/health-scenario.d.ts +1901 -0
- package/dist/health-scenario.js +653 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/i18n/operation-locale-namespace.d.ts +2 -0
- package/dist/i18n/operation-locale-namespace.js +7 -0
- package/dist/index.d.ts +40 -15
- package/dist/index.js +29 -11
- package/dist/lint.d.ts +43 -11
- package/dist/lint.js +2512 -177
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +18 -8
- package/dist/provider.js +11 -6
- package/dist/runtime/auth-flow.d.ts +6 -1
- package/dist/runtime/auth-flow.js +8 -0
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +4 -0
- package/dist/runtime/browser.js +688 -83
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.d.ts +3 -0
- package/dist/runtime/cache.js +245 -23
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +14 -0
- package/dist/runtime/diagnostic-env.js +44 -0
- package/dist/runtime/diagnostic-redactor.d.ts +54 -0
- package/dist/runtime/diagnostic-redactor.js +571 -0
- package/dist/runtime/egress-lease.d.ts +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/env.js +14 -1
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +23 -26
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/handle-wordlist.js +256 -0
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +908 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +34 -0
- package/dist/runtime/http.js +680 -121
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +462 -19
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +105 -0
- package/dist/runtime/native-network.js +1433 -0
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +468 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +398 -16
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +147 -0
- package/dist/runtime/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/proxy-telemetry.d.ts +133 -3
- package/dist/runtime/proxy-telemetry.js +218 -70
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +61 -0
- package/dist/runtime/redirects.js +149 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +75 -1
- package/dist/runtime/request-options.js +588 -0
- package/dist/runtime/request-telemetry.d.ts +70 -0
- package/dist/runtime/request-telemetry.js +229 -0
- package/dist/runtime/resolver-config.d.ts +13 -0
- package/dist/runtime/resolver-config.js +14 -0
- package/dist/runtime/resolver-public.d.ts +3 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +24 -0
- package/dist/runtime/resolver-shared.js +69 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-telemetry.d.ts +135 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +32 -0
- package/dist/runtime/resolver-usage.js +54 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
- package/dist/runtime/resolver-vendors/bindings.js +45 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +423 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +544 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
- package/dist/runtime/resolver-vendors/hosts.js +68 -0
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
- package/dist/runtime/resolver-vendors/types.d.ts +125 -0
- package/dist/runtime/resolver-vendors/types.js +106 -0
- package/dist/runtime/resolver.d.ts +79 -0
- package/dist/runtime/resolver.js +1094 -0
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +10 -1
- package/dist/runtime/state.js +322 -82
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth-cookies.d.ts +24 -0
- package/dist/runtime/stealth-cookies.js +131 -0
- package/dist/runtime/stealth-owned-headers.d.ts +39 -0
- package/dist/runtime/stealth-owned-headers.js +57 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +45 -6
- package/dist/runtime/stealth.js +1775 -320
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +18 -19
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +17 -0
- package/dist/runtime/trace.js +176 -25
- package/dist/runtime-boundary-lint.d.ts +137 -0
- package/dist/runtime-boundary-lint.js +590 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +72 -2
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +21 -2
- package/dist/server/index.js +11 -2
- package/dist/server/runtime-diagnostics.d.ts +165 -0
- package/dist/server/runtime-diagnostics.js +360 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +18 -3
- package/dist/server/self-test.js +139 -76
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +137 -0
- package/dist/server/serve-implementation.d.ts +285 -0
- package/dist/server/serve-implementation.js +3172 -0
- package/dist/server/serve.d.ts +1 -75
- package/dist/server/serve.js +1 -1170
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/server/trace-output.d.ts +7 -0
- package/dist/server/trace-output.js +111 -0
- package/dist/server/types.d.ts +37 -5
- package/dist/server/types.js +34 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
- package/dist/stateful/stateful-provider-runtime-context.js +61 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +15 -4
- package/dist/stealth/profiles.js +169 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +509 -25
- package/dist/trace-sanitization.d.ts +6 -0
- package/dist/trace-sanitization.js +68 -0
- package/dist/types.d.ts +899 -210
- package/dist/types.js +1 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +52 -6
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/commands.ts +37 -0
- package/src/cli/create.ts +8 -2
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/local-connection.ts +221 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-operation-declaration.ts +3026 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +778 -0
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +41 -19
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +774 -181
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +376 -0
- package/src/define.ts +1105 -131
- package/src/dev.ts +13 -1
- package/src/engine.ts +390 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +269 -0
- package/src/errors.ts +157 -0
- package/src/fixture-sanitization.ts +422 -0
- package/src/handle-meta.ts +143 -0
- package/src/handle.ts +694 -0
- package/src/health-scenario.ts +960 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/i18n/operation-locale-namespace.ts +10 -0
- package/src/index.ts +390 -23
- package/src/lint.ts +2975 -234
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +207 -27
- package/src/runtime/auth-flow.ts +15 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +882 -99
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +258 -22
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +65 -0
- package/src/runtime/diagnostic-redactor.ts +668 -0
- package/src/runtime/egress-lease.ts +341 -0
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/env.ts +15 -1
- package/src/runtime/executor.ts +39 -32
- package/src/runtime/handle-wordlist.ts +266 -0
- package/src/runtime/handle.ts +1180 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +896 -150
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +628 -28
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1853 -0
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +564 -0
- package/src/runtime/otlp.ts +468 -21
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +222 -0
- package/src/runtime/proxy-retry-policy.ts +12 -1
- package/src/runtime/proxy-telemetry.ts +358 -103
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +215 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +728 -1
- package/src/runtime/request-telemetry.ts +375 -0
- package/src/runtime/resolver-config.ts +18 -0
- package/src/runtime/resolver-public.ts +44 -0
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +113 -0
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +99 -0
- package/src/runtime/resolver-vendors/bindings.ts +61 -0
- package/src/runtime/resolver-vendors/browser.ts +591 -0
- package/src/runtime/resolver-vendors/capsolver.ts +727 -0
- package/src/runtime/resolver-vendors/hosts.ts +81 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
- package/src/runtime/resolver-vendors/types.ts +261 -0
- package/src/runtime/resolver.ts +1543 -0
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +435 -89
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth-cookies.ts +155 -0
- package/src/runtime/stealth-owned-headers.ts +67 -0
- package/src/runtime/stealth-telemetry.ts +309 -0
- package/src/runtime/stealth.ts +2275 -372
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +96 -91
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +257 -35
- package/src/runtime-boundary-lint.ts +707 -0
- package/src/schema.ts +80 -2
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +184 -2
- package/src/server/runtime-diagnostics.ts +522 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +205 -89
- package/src/server/sensitive-values.ts +211 -0
- package/src/server/serve-implementation.ts +4596 -0
- package/src/server/serve.ts +1 -1685
- package/src/server/state-backend-report.ts +185 -0
- package/src/server/trace-output.ts +189 -0
- package/src/server/types.ts +34 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
- package/src/stateful/stateful-provider-runtime-context.ts +96 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +212 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +685 -19
- package/src/trace-sanitization.ts +110 -0
- package/src/types.ts +1014 -273
- package/src/typescript-module.ts +100 -0
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice.d.ts +0 -15
- package/dist/runtime/choice.js +0 -413
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -686
package/dist/runtime/browser.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import { readDiagnosticEnv, registerDiagnosticValue } from "./diagnostic-env.js";
|
|
2
|
+
import { AsyncResource } from "node:async_hooks";
|
|
3
|
+
import { observeTelemetryCallback as observeBrowserTelemetry } from "./http-telemetry-guard.js";
|
|
4
|
+
import { cookieValuesFromHeaders } from "./stealth-cookies.js";
|
|
5
|
+
function browserTelemetryErrorCode(error) {
|
|
6
|
+
const code = error && typeof error === "object" && "code" in error
|
|
7
|
+
? error.code
|
|
8
|
+
: undefined;
|
|
9
|
+
if (code === "BROWSER_CDP_POOL_REQUIRED" ||
|
|
10
|
+
code === "BROWSER_PROXY_INVALID" ||
|
|
11
|
+
code === "BROWSER_RUNTIME_UNSUPPORTED")
|
|
12
|
+
return code;
|
|
13
|
+
if (typeof code === "number")
|
|
14
|
+
return "BROWSER_CDP_POOL_ERROR";
|
|
15
|
+
return "other";
|
|
16
|
+
}
|
|
1
17
|
import { createRequire } from "node:module";
|
|
2
18
|
import { ProviderError } from "../errors.js";
|
|
3
19
|
const require = createRequire(import.meta.url);
|
|
@@ -15,7 +31,7 @@ function toResourceBody(body) {
|
|
|
15
31
|
return Buffer.from(body);
|
|
16
32
|
}
|
|
17
33
|
function isResourceMethod(method) {
|
|
18
|
-
return method === "GET" || method === "HEAD";
|
|
34
|
+
return method === "GET" || method === "HEAD" || method === "POST";
|
|
19
35
|
}
|
|
20
36
|
async function toResourceRequest(request) {
|
|
21
37
|
const method = request.method().toUpperCase();
|
|
@@ -59,6 +75,68 @@ function getCdpPausedRequestId(params) {
|
|
|
59
75
|
}
|
|
60
76
|
return params.requestId;
|
|
61
77
|
}
|
|
78
|
+
function isCdpResponseStage(params) {
|
|
79
|
+
return (isRecord(params) &&
|
|
80
|
+
(typeof params.responseStatusCode === "number" ||
|
|
81
|
+
typeof params.responseErrorReason === "string"));
|
|
82
|
+
}
|
|
83
|
+
function toCdpPausedDocumentResponse(params) {
|
|
84
|
+
if (!isRecord(params) ||
|
|
85
|
+
typeof params.requestId !== "string" ||
|
|
86
|
+
typeof params.responseStatusCode !== "number" ||
|
|
87
|
+
params.resourceType !== "Document" ||
|
|
88
|
+
!isRecord(params.request)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const responseCode = params.responseStatusCode;
|
|
92
|
+
const responseHeaders = Array.isArray(params.responseHeaders)
|
|
93
|
+
? params.responseHeaders.flatMap((header) => {
|
|
94
|
+
if (!isRecord(header) ||
|
|
95
|
+
typeof header.name !== "string" ||
|
|
96
|
+
typeof header.value !== "string") {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return [{ name: header.name, value: header.value }];
|
|
100
|
+
})
|
|
101
|
+
: [];
|
|
102
|
+
return {
|
|
103
|
+
hasBody: String(params.request.method ?? "").toUpperCase() !== "HEAD" &&
|
|
104
|
+
responseCode >= 200 &&
|
|
105
|
+
(responseCode < 300 || responseCode >= 400) &&
|
|
106
|
+
responseCode !== 204 &&
|
|
107
|
+
responseCode !== 205 &&
|
|
108
|
+
responseCode !== 304,
|
|
109
|
+
requestId: params.requestId,
|
|
110
|
+
responseCode,
|
|
111
|
+
responseHeaders,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function toCdpDocumentFulfillParams(response, body, contentSecurityPolicy) {
|
|
115
|
+
const responseBody = typeof body.body === "string" ? body.body : "";
|
|
116
|
+
return {
|
|
117
|
+
body: body.base64Encoded ? responseBody : Buffer.from(responseBody).toString("base64"),
|
|
118
|
+
requestId: response.requestId,
|
|
119
|
+
responseCode: response.responseCode,
|
|
120
|
+
responseHeaders: [
|
|
121
|
+
...response.responseHeaders,
|
|
122
|
+
{ name: "Content-Security-Policy", value: contentSecurityPolicy },
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function resourcePolicyFetchPatterns(policy) {
|
|
127
|
+
return [
|
|
128
|
+
{ requestStage: "Request", urlPattern: "*" },
|
|
129
|
+
...(policy.documentContentSecurityPolicy
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
requestStage: "Response",
|
|
133
|
+
resourceType: "Document",
|
|
134
|
+
urlPattern: "*",
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
: []),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
62
140
|
function toCdpResourceHeaders(value) {
|
|
63
141
|
if (!isRecord(value)) {
|
|
64
142
|
return {};
|
|
@@ -104,8 +182,129 @@ async function fulfillResourceRoute(route, decision) {
|
|
|
104
182
|
status: decision.status ?? 200,
|
|
105
183
|
});
|
|
106
184
|
}
|
|
185
|
+
async function decideResourceRequest(policy, request) {
|
|
186
|
+
for (const resourceRoute of policy.routes) {
|
|
187
|
+
if (matchesResourceRoute(resourceRoute.match, request)) {
|
|
188
|
+
return await resourceRoute.handle(request);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { action: "block" };
|
|
192
|
+
}
|
|
193
|
+
async function abortResourceRoute(route) {
|
|
194
|
+
await route.abort("blockedbyclient");
|
|
195
|
+
}
|
|
196
|
+
async function handleCdpResourceRequest(session, policy, allowedMethods, params) {
|
|
197
|
+
const paused = toCdpResourceRequest(params);
|
|
198
|
+
const requestId = paused?.requestId ?? getCdpPausedRequestId(params);
|
|
199
|
+
if (!requestId)
|
|
200
|
+
return;
|
|
201
|
+
try {
|
|
202
|
+
if (!paused || !allowedMethods.has(paused.request.method)) {
|
|
203
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const decision = await decideResourceRequest(policy, paused.request);
|
|
207
|
+
switch (decision.action) {
|
|
208
|
+
case "continue":
|
|
209
|
+
await session.send("Fetch.continueRequest", { requestId });
|
|
210
|
+
return;
|
|
211
|
+
case "fulfill":
|
|
212
|
+
await session.send("Fetch.fulfillRequest", toCdpFulfillParams(requestId, decision));
|
|
213
|
+
return;
|
|
214
|
+
case "block":
|
|
215
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
await session
|
|
221
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
222
|
+
.catch(() => undefined);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function handleCdpDocumentResponse(session, contentSecurityPolicy, params) {
|
|
226
|
+
const requestId = getCdpPausedRequestId(params);
|
|
227
|
+
if (!requestId)
|
|
228
|
+
return;
|
|
229
|
+
try {
|
|
230
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
231
|
+
if (!response)
|
|
232
|
+
throw new Error("CDP document response metadata is unavailable");
|
|
233
|
+
if (!response.hasBody) {
|
|
234
|
+
await session.send("Fetch.continueResponse", {
|
|
235
|
+
requestId,
|
|
236
|
+
responseCode: response.responseCode,
|
|
237
|
+
responseHeaders: [...response.responseHeaders],
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const body = await session.send("Fetch.getResponseBody", {
|
|
242
|
+
requestId: response.requestId,
|
|
243
|
+
});
|
|
244
|
+
await session.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, contentSecurityPolicy));
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
await session
|
|
248
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
249
|
+
.catch(() => undefined);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function getCdpAuthRequiredRequestId(params) {
|
|
253
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return params.requestId;
|
|
257
|
+
}
|
|
258
|
+
function isCdpProxyAuthChallenge(params) {
|
|
259
|
+
return (isRecord(params) && isRecord(params.authChallenge) && params.authChallenge.source === "Proxy");
|
|
260
|
+
}
|
|
261
|
+
async function handleCdpAuthRequired(session, proxy, authAttempts, params, telemetry) {
|
|
262
|
+
const requestId = getCdpAuthRequiredRequestId(params);
|
|
263
|
+
if (!requestId)
|
|
264
|
+
return;
|
|
265
|
+
// Chromium can emit another challenge after rejected credentials. Cancel a
|
|
266
|
+
// retry so incorrect credentials fail promptly instead of looping forever.
|
|
267
|
+
const response = isCdpProxyAuthChallenge(params) && !authAttempts.has(requestId)
|
|
268
|
+
? (() => {
|
|
269
|
+
authAttempts.add(requestId);
|
|
270
|
+
observeBrowserTelemetry(telemetry, () => telemetry?.recordProxyAuthChallenge());
|
|
271
|
+
return {
|
|
272
|
+
authChallengeResponse: {
|
|
273
|
+
password: proxy.password,
|
|
274
|
+
response: "ProvideCredentials",
|
|
275
|
+
username: proxy.username,
|
|
276
|
+
},
|
|
277
|
+
requestId,
|
|
278
|
+
};
|
|
279
|
+
})()
|
|
280
|
+
: {
|
|
281
|
+
authChallengeResponse: { response: "CancelAuth" },
|
|
282
|
+
requestId,
|
|
283
|
+
};
|
|
284
|
+
await session.send("Fetch.continueWithAuth", response).catch(() => undefined);
|
|
285
|
+
}
|
|
286
|
+
function createResourcePolicyHandler(policy, allowedMethods) {
|
|
287
|
+
return async (route) => {
|
|
288
|
+
const request = await toResourceRequest(route.request());
|
|
289
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
290
|
+
await abortResourceRoute(route);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const decision = await decideResourceRequest(policy, request);
|
|
294
|
+
switch (decision.action) {
|
|
295
|
+
case "continue":
|
|
296
|
+
await route.continue();
|
|
297
|
+
return;
|
|
298
|
+
case "fulfill":
|
|
299
|
+
await fulfillResourceRoute(route, decision);
|
|
300
|
+
return;
|
|
301
|
+
case "block":
|
|
302
|
+
await abortResourceRoute(route);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
107
306
|
function getDefaultCdpPoolUrl(env = process.env) {
|
|
108
|
-
return env
|
|
307
|
+
return readDiagnosticEnv("APIFUSE__CDP_POOL__URL", env);
|
|
109
308
|
}
|
|
110
309
|
async function importOptionalModule(moduleName) {
|
|
111
310
|
return (await import(moduleName));
|
|
@@ -139,12 +338,62 @@ function formatExpression(fn) {
|
|
|
139
338
|
}
|
|
140
339
|
return `(${fn.toString()})()`;
|
|
141
340
|
}
|
|
142
|
-
function toLaunchOptions(options) {
|
|
341
|
+
function toLaunchOptions(options, proxy) {
|
|
143
342
|
return {
|
|
144
|
-
|
|
343
|
+
// `extraArgs` is optional, but playwright-extra's stealth evasions mutate
|
|
344
|
+
// `options.args` unguarded (navigator.webdriver does
|
|
345
|
+
// `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
|
|
346
|
+
// therefore crashes every stealth launch that omits extraArgs with
|
|
347
|
+
// "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
|
|
348
|
+
// Always hand the launcher a concrete array.
|
|
349
|
+
args: options.extraArgs ?? [],
|
|
145
350
|
executablePath: options.executablePath,
|
|
146
351
|
headless: options.headless ?? true,
|
|
147
|
-
proxy
|
|
352
|
+
proxy,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function hasProxyCredentials(proxy) {
|
|
356
|
+
return proxy?.username !== undefined && proxy.password !== undefined;
|
|
357
|
+
}
|
|
358
|
+
function toPlaywrightProxy(proxy) {
|
|
359
|
+
if (!proxy)
|
|
360
|
+
return undefined;
|
|
361
|
+
const schemeEnd = proxy.indexOf("://");
|
|
362
|
+
const authorityStart = schemeEnd >= 0 ? schemeEnd + 3 : 0;
|
|
363
|
+
const remainder = proxy.slice(authorityStart);
|
|
364
|
+
const authorityEnd = remainder.search(/[/?#]/);
|
|
365
|
+
const authority = remainder.slice(0, authorityEnd >= 0 ? authorityEnd : undefined);
|
|
366
|
+
if (!authority.includes("@")) {
|
|
367
|
+
return { server: proxy };
|
|
368
|
+
}
|
|
369
|
+
let parsed;
|
|
370
|
+
try {
|
|
371
|
+
parsed = new URL(proxy);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
throw new ProviderError("Browser proxy URL is invalid", {
|
|
375
|
+
code: "BROWSER_PROXY_INVALID",
|
|
376
|
+
fix: "Use a valid proxy URL.",
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
let username;
|
|
380
|
+
let password;
|
|
381
|
+
try {
|
|
382
|
+
username = decodeURIComponent(parsed.username);
|
|
383
|
+
password = decodeURIComponent(parsed.password);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
throw new ProviderError("Browser proxy credentials use invalid percent-encoding", {
|
|
387
|
+
code: "BROWSER_PROXY_INVALID",
|
|
388
|
+
fix: "Percent-encode the proxy username and password as URL userinfo.",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
registerDiagnosticValue(username);
|
|
392
|
+
registerDiagnosticValue(password);
|
|
393
|
+
return {
|
|
394
|
+
server: `${parsed.protocol}//${parsed.host}`,
|
|
395
|
+
username,
|
|
396
|
+
password,
|
|
148
397
|
};
|
|
149
398
|
}
|
|
150
399
|
class PlaywrightBrowserLocator {
|
|
@@ -309,13 +558,25 @@ async function loadSeleniumBase() {
|
|
|
309
558
|
}
|
|
310
559
|
class PlaywrightBrowserPage {
|
|
311
560
|
page;
|
|
561
|
+
proxy;
|
|
562
|
+
telemetry;
|
|
312
563
|
id = "main";
|
|
313
564
|
pageId;
|
|
314
|
-
constructor(page) {
|
|
565
|
+
constructor(page, proxy = undefined, telemetry) {
|
|
315
566
|
this.page = page;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
567
|
+
this.proxy = proxy;
|
|
568
|
+
this.telemetry = telemetry;
|
|
569
|
+
}
|
|
570
|
+
async goto(url, options) {
|
|
571
|
+
const response = await this.page.goto(url, options);
|
|
572
|
+
const headers = await response?.headersArray().catch(() => []);
|
|
573
|
+
for (const header of headers ?? []) {
|
|
574
|
+
if (header.name.toLowerCase() === "set-cookie") {
|
|
575
|
+
registerDiagnosticValue(header.value);
|
|
576
|
+
for (const value of cookieValuesFromHeaders(header.value))
|
|
577
|
+
registerDiagnosticValue(value);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
319
580
|
}
|
|
320
581
|
async evaluate(fn) {
|
|
321
582
|
if (typeof fn === "string") {
|
|
@@ -323,6 +584,9 @@ class PlaywrightBrowserPage {
|
|
|
323
584
|
}
|
|
324
585
|
return await this.page.evaluate(fn);
|
|
325
586
|
}
|
|
587
|
+
async userAgent() {
|
|
588
|
+
return await this.evaluate("navigator.userAgent");
|
|
589
|
+
}
|
|
326
590
|
async waitForSelector(selector, options) {
|
|
327
591
|
await this.page.waitForSelector(selector, options);
|
|
328
592
|
}
|
|
@@ -357,42 +621,110 @@ class PlaywrightBrowserPage {
|
|
|
357
621
|
async close() {
|
|
358
622
|
await this.page.close();
|
|
359
623
|
}
|
|
624
|
+
async cookies() {
|
|
625
|
+
const cookies = (await this.page.context().cookies()).map(toBrowserCookie);
|
|
626
|
+
for (const cookie of cookies)
|
|
627
|
+
registerDiagnosticValue(cookie.value);
|
|
628
|
+
return cookies;
|
|
629
|
+
}
|
|
360
630
|
async withResourcePolicy(policy, run) {
|
|
361
631
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
632
|
+
const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
|
|
633
|
+
const context = this.page.context();
|
|
362
634
|
const handler = async (route) => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
635
|
+
try {
|
|
636
|
+
// Context routing sees a popup's first request before the late `page`
|
|
637
|
+
// event. Fail every secondary page closed so no redirect can escape
|
|
638
|
+
// the per-page CDP interception while that session is being attached.
|
|
639
|
+
if (route.request().frame().page() !== this.page) {
|
|
640
|
+
await abortResourceRoute(route);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
await abortResourceRoute(route);
|
|
366
646
|
return;
|
|
367
647
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
648
|
+
await policyHandler(route);
|
|
649
|
+
};
|
|
650
|
+
const sessions = new Set();
|
|
651
|
+
const pendingAttachments = new Set();
|
|
652
|
+
const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
|
|
653
|
+
let active = true;
|
|
654
|
+
const attachPage = async (page) => {
|
|
655
|
+
const session = await context.newCDPSession(page);
|
|
656
|
+
if (!active) {
|
|
657
|
+
await session.detach();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const onRequestPaused = (params) => {
|
|
661
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
662
|
+
void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
|
|
663
|
+
return;
|
|
371
664
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
665
|
+
void handleCdpResourceRequest(session, policy, allowedMethods, params);
|
|
666
|
+
};
|
|
667
|
+
const authAttempts = new Set();
|
|
668
|
+
const onAuthRequired = (params) => {
|
|
669
|
+
if (!proxy)
|
|
670
|
+
return;
|
|
671
|
+
void handleCdpAuthRequired(session, proxy, authAttempts, params, this.telemetry);
|
|
672
|
+
};
|
|
673
|
+
session.on("Fetch.requestPaused", onRequestPaused);
|
|
674
|
+
if (proxy) {
|
|
675
|
+
session.on("Fetch.authRequired", onAuthRequired);
|
|
676
|
+
}
|
|
677
|
+
try {
|
|
678
|
+
await session.send("Fetch.enable", {
|
|
679
|
+
...(proxy ? { handleAuthRequests: true } : {}),
|
|
680
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
681
|
+
});
|
|
682
|
+
sessions.add(session);
|
|
683
|
+
}
|
|
684
|
+
catch (error) {
|
|
685
|
+
session.off("Fetch.requestPaused", onRequestPaused);
|
|
686
|
+
if (proxy) {
|
|
687
|
+
session.off("Fetch.authRequired", onAuthRequired);
|
|
380
688
|
}
|
|
689
|
+
await session.detach().catch(() => undefined);
|
|
690
|
+
throw error;
|
|
381
691
|
}
|
|
382
|
-
await route.abort("blockedbyclient");
|
|
383
692
|
};
|
|
384
|
-
|
|
693
|
+
const onPage = (page) => {
|
|
694
|
+
const attachment = attachPage(page)
|
|
695
|
+
.catch(async () => {
|
|
696
|
+
await page.close().catch(() => undefined);
|
|
697
|
+
})
|
|
698
|
+
.finally(() => pendingAttachments.delete(attachment));
|
|
699
|
+
pendingAttachments.add(attachment);
|
|
700
|
+
};
|
|
701
|
+
await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
385
702
|
try {
|
|
386
|
-
|
|
703
|
+
context.on("page", onPage);
|
|
704
|
+
try {
|
|
705
|
+
await attachPage(this.page);
|
|
706
|
+
return await run();
|
|
707
|
+
}
|
|
708
|
+
finally {
|
|
709
|
+
context.off("page", onPage);
|
|
710
|
+
}
|
|
387
711
|
}
|
|
388
712
|
finally {
|
|
389
|
-
|
|
713
|
+
active = false;
|
|
714
|
+
await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
715
|
+
await Promise.allSettled(pendingAttachments);
|
|
716
|
+
await Promise.all([...sessions].map(async (session) => {
|
|
717
|
+
await session.send("Fetch.disable").catch(() => undefined);
|
|
718
|
+
await session.detach().catch(() => undefined);
|
|
719
|
+
}));
|
|
390
720
|
}
|
|
391
721
|
}
|
|
392
722
|
}
|
|
393
723
|
class PlaywrightBrowserClient {
|
|
394
724
|
options;
|
|
395
725
|
browser = null;
|
|
726
|
+
parsedProxy;
|
|
727
|
+
proxyParsed = false;
|
|
396
728
|
engine = "playwright-stealth";
|
|
397
729
|
constructor(options = {}) {
|
|
398
730
|
this.options = options;
|
|
@@ -402,13 +734,19 @@ class PlaywrightBrowserClient {
|
|
|
402
734
|
return this.browser;
|
|
403
735
|
}
|
|
404
736
|
const chromium = await loadChromiumLauncher(this.options);
|
|
405
|
-
|
|
737
|
+
if (!this.proxyParsed) {
|
|
738
|
+
this.parsedProxy = toPlaywrightProxy(this.options.proxy);
|
|
739
|
+
this.proxyParsed = true;
|
|
740
|
+
}
|
|
741
|
+
this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
|
|
406
742
|
return this.browser;
|
|
407
743
|
}
|
|
408
744
|
async newPage() {
|
|
745
|
+
if (!this.options.cdpUrl)
|
|
746
|
+
observeBrowserTelemetry(this.options.telemetry, () => this.options.telemetry?.recordPoolAcquire("not_configured"));
|
|
409
747
|
const browser = await this.ensureBrowser();
|
|
410
748
|
const page = await browser.newPage();
|
|
411
|
-
return new PlaywrightBrowserPage(page);
|
|
749
|
+
return new PlaywrightBrowserPage(page, this.parsedProxy, this.options.telemetry);
|
|
412
750
|
}
|
|
413
751
|
async rawPage() {
|
|
414
752
|
throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
|
|
@@ -417,10 +755,12 @@ class PlaywrightBrowserClient {
|
|
|
417
755
|
});
|
|
418
756
|
}
|
|
419
757
|
async withIsolatedContext(handler) {
|
|
758
|
+
if (!this.options.cdpUrl)
|
|
759
|
+
observeBrowserTelemetry(this.options.telemetry, () => this.options.telemetry?.recordPoolAcquire("not_configured"));
|
|
420
760
|
const browser = await this.ensureBrowser();
|
|
421
|
-
const context = await browser.newContext();
|
|
761
|
+
const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
|
|
422
762
|
const page = await context.newPage();
|
|
423
|
-
const browserPage = new PlaywrightBrowserPage(page);
|
|
763
|
+
const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy, this.options.telemetry);
|
|
424
764
|
try {
|
|
425
765
|
return await handler(browserPage);
|
|
426
766
|
}
|
|
@@ -457,13 +797,14 @@ function normalizeWebSocketEndpoint(endpoint) {
|
|
|
457
797
|
}
|
|
458
798
|
throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
|
|
459
799
|
}
|
|
460
|
-
class
|
|
800
|
+
class WebSocketCommandClient {
|
|
461
801
|
nextId = 1;
|
|
462
802
|
endpoint;
|
|
463
803
|
listeners = new Map();
|
|
464
804
|
pending = new Map();
|
|
465
805
|
socket;
|
|
466
806
|
socketPromise;
|
|
807
|
+
closed = false;
|
|
467
808
|
constructor(endpoint) {
|
|
468
809
|
this.endpoint = normalizeWebSocketEndpoint(endpoint);
|
|
469
810
|
}
|
|
@@ -480,18 +821,16 @@ class JsonRpcWebSocketClient {
|
|
|
480
821
|
}
|
|
481
822
|
async send(method, params = {}) {
|
|
482
823
|
const socket = await this.getSocket();
|
|
824
|
+
if (this.closed)
|
|
825
|
+
throw new Error(`WebSocket closed: ${this.endpoint}`);
|
|
483
826
|
const id = this.nextId++;
|
|
484
827
|
return await new Promise((resolve, reject) => {
|
|
485
828
|
this.pending.set(id, { resolve, reject });
|
|
486
|
-
socket.send(JSON.stringify(
|
|
487
|
-
id,
|
|
488
|
-
jsonrpc: "2.0",
|
|
489
|
-
method,
|
|
490
|
-
params,
|
|
491
|
-
}));
|
|
829
|
+
socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
|
|
492
830
|
});
|
|
493
831
|
}
|
|
494
832
|
async close() {
|
|
833
|
+
this.closed = true;
|
|
495
834
|
for (const pending of this.pending.values()) {
|
|
496
835
|
pending.reject(new Error(`WebSocket closed: ${this.endpoint}`));
|
|
497
836
|
}
|
|
@@ -502,6 +841,8 @@ class JsonRpcWebSocketClient {
|
|
|
502
841
|
this.socketPromise = undefined;
|
|
503
842
|
}
|
|
504
843
|
async getSocket() {
|
|
844
|
+
if (this.closed)
|
|
845
|
+
throw new Error(`WebSocket closed: ${this.endpoint}`);
|
|
505
846
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
506
847
|
return this.socket;
|
|
507
848
|
}
|
|
@@ -511,6 +852,11 @@ class JsonRpcWebSocketClient {
|
|
|
511
852
|
this.socketPromise = new Promise((resolve, reject) => {
|
|
512
853
|
const socket = new WebSocket(this.endpoint);
|
|
513
854
|
socket.addEventListener("open", () => {
|
|
855
|
+
if (this.closed) {
|
|
856
|
+
socket.close();
|
|
857
|
+
reject(new Error(`WebSocket closed: ${this.endpoint}`));
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
514
860
|
this.socket = socket;
|
|
515
861
|
resolve(socket);
|
|
516
862
|
});
|
|
@@ -526,7 +872,11 @@ class JsonRpcWebSocketClient {
|
|
|
526
872
|
}
|
|
527
873
|
this.pending.delete(payload.id);
|
|
528
874
|
if (payload.error) {
|
|
529
|
-
|
|
875
|
+
const error = new Error(payload.error.message ?? "JSON-RPC command failed");
|
|
876
|
+
if (typeof payload.error.code === "number") {
|
|
877
|
+
Object.assign(error, { code: payload.error.code });
|
|
878
|
+
}
|
|
879
|
+
pending.reject(error);
|
|
530
880
|
return;
|
|
531
881
|
}
|
|
532
882
|
pending.resolve(payload.result ?? {});
|
|
@@ -554,6 +904,26 @@ class JsonRpcWebSocketClient {
|
|
|
554
904
|
return this.socketPromise;
|
|
555
905
|
}
|
|
556
906
|
}
|
|
907
|
+
class CdpWebSocketClient extends WebSocketCommandClient {
|
|
908
|
+
sessionId;
|
|
909
|
+
constructor(endpoint, sessionId) {
|
|
910
|
+
super(endpoint);
|
|
911
|
+
this.sessionId = sessionId;
|
|
912
|
+
}
|
|
913
|
+
createCommandFrame(id, method, params) {
|
|
914
|
+
return {
|
|
915
|
+
id,
|
|
916
|
+
method,
|
|
917
|
+
params,
|
|
918
|
+
...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
class JsonRpcWebSocketClient extends WebSocketCommandClient {
|
|
923
|
+
createCommandFrame(id, method, params) {
|
|
924
|
+
return { id, jsonrpc: "2.0", method, params };
|
|
925
|
+
}
|
|
926
|
+
}
|
|
557
927
|
function flattenCdpFrameTree(node, out = []) {
|
|
558
928
|
if (!node) {
|
|
559
929
|
return out;
|
|
@@ -567,6 +937,48 @@ function flattenCdpFrameTree(node, out = []) {
|
|
|
567
937
|
function isRecord(value) {
|
|
568
938
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
569
939
|
}
|
|
940
|
+
function isBrowserCookieSameSite(value) {
|
|
941
|
+
return value === "Strict" || value === "Lax" || value === "None";
|
|
942
|
+
}
|
|
943
|
+
function toBrowserCookie(cookie) {
|
|
944
|
+
return {
|
|
945
|
+
name: cookie.name,
|
|
946
|
+
value: cookie.value,
|
|
947
|
+
domain: cookie.domain,
|
|
948
|
+
path: cookie.path,
|
|
949
|
+
...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
|
|
950
|
+
httpOnly: cookie.httpOnly,
|
|
951
|
+
secure: cookie.secure,
|
|
952
|
+
...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function parseCdpCookies(value) {
|
|
956
|
+
if (!Array.isArray(value)) {
|
|
957
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie list");
|
|
958
|
+
}
|
|
959
|
+
return value.map((cookie) => {
|
|
960
|
+
if (!isRecord(cookie) ||
|
|
961
|
+
typeof cookie.name !== "string" ||
|
|
962
|
+
typeof cookie.value !== "string" ||
|
|
963
|
+
typeof cookie.domain !== "string" ||
|
|
964
|
+
typeof cookie.path !== "string" ||
|
|
965
|
+
typeof cookie.expires !== "number" ||
|
|
966
|
+
typeof cookie.httpOnly !== "boolean" ||
|
|
967
|
+
typeof cookie.secure !== "boolean") {
|
|
968
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie");
|
|
969
|
+
}
|
|
970
|
+
return toBrowserCookie({
|
|
971
|
+
name: cookie.name,
|
|
972
|
+
value: cookie.value,
|
|
973
|
+
domain: cookie.domain,
|
|
974
|
+
path: cookie.path,
|
|
975
|
+
expires: cookie.expires,
|
|
976
|
+
httpOnly: cookie.httpOnly,
|
|
977
|
+
secure: cookie.secure,
|
|
978
|
+
sameSite: cookie.sameSite,
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
}
|
|
570
982
|
function parsePoolAcquireResponse(value) {
|
|
571
983
|
if (!isRecord(value) ||
|
|
572
984
|
typeof value.pageId !== "string" ||
|
|
@@ -586,6 +998,33 @@ function parsePoolAcquireResponse(value) {
|
|
|
586
998
|
wsEndpoint: value.wsEndpoint,
|
|
587
999
|
};
|
|
588
1000
|
}
|
|
1001
|
+
function poolAcquireOutcome(error) {
|
|
1002
|
+
const code = error instanceof Error && typeof error.code === "number"
|
|
1003
|
+
? error.code
|
|
1004
|
+
: undefined;
|
|
1005
|
+
switch (code) {
|
|
1006
|
+
case -32001:
|
|
1007
|
+
return "queue_full";
|
|
1008
|
+
case -32002:
|
|
1009
|
+
return "timed_out";
|
|
1010
|
+
case -32003:
|
|
1011
|
+
return "shutting_down";
|
|
1012
|
+
case -32004:
|
|
1013
|
+
return "unknown_lease";
|
|
1014
|
+
case -32005:
|
|
1015
|
+
return "unknown_method";
|
|
1016
|
+
case -32006:
|
|
1017
|
+
return "missing_allowed_hosts";
|
|
1018
|
+
}
|
|
1019
|
+
if (error instanceof Error && /Unable to connect|WebSocket closed/.test(error.message))
|
|
1020
|
+
return "transport_failure";
|
|
1021
|
+
return "other";
|
|
1022
|
+
}
|
|
1023
|
+
function poolAcquireCode(error) {
|
|
1024
|
+
return error instanceof Error && typeof error.code === "number"
|
|
1025
|
+
? error.code
|
|
1026
|
+
: undefined;
|
|
1027
|
+
}
|
|
589
1028
|
function parseCdpFrameTreeNode(value) {
|
|
590
1029
|
if (!isRecord(value) || !isRecord(value.frame)) {
|
|
591
1030
|
return undefined;
|
|
@@ -620,6 +1059,15 @@ function getCdpExecutionContext(params) {
|
|
|
620
1059
|
id: typeof contextId === "number" ? contextId : undefined,
|
|
621
1060
|
};
|
|
622
1061
|
}
|
|
1062
|
+
function getCdpDestroyedExecutionContextId(params) {
|
|
1063
|
+
if (!isRecord(params)) {
|
|
1064
|
+
return undefined;
|
|
1065
|
+
}
|
|
1066
|
+
return typeof params.executionContextId === "number" ? params.executionContextId : undefined;
|
|
1067
|
+
}
|
|
1068
|
+
function isMissingExecutionContextError(error) {
|
|
1069
|
+
return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
|
|
1070
|
+
}
|
|
623
1071
|
class CdpBrowserLocator {
|
|
624
1072
|
frame;
|
|
625
1073
|
selector;
|
|
@@ -711,7 +1159,7 @@ class CdpPoolBrowserPage {
|
|
|
711
1159
|
browserContextId;
|
|
712
1160
|
pageClient;
|
|
713
1161
|
release;
|
|
714
|
-
|
|
1162
|
+
closePromise;
|
|
715
1163
|
initialized = false;
|
|
716
1164
|
frameExecutionContexts = new Map();
|
|
717
1165
|
constructor(pageId, browserContextId, pageClient, release) {
|
|
@@ -723,16 +1171,22 @@ class CdpPoolBrowserPage {
|
|
|
723
1171
|
get id() {
|
|
724
1172
|
return this.pageId;
|
|
725
1173
|
}
|
|
726
|
-
async goto(url) {
|
|
1174
|
+
async goto(url, options) {
|
|
727
1175
|
await this.initialize();
|
|
728
1176
|
const startedAt = Date.now();
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
|
|
1177
|
+
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
1178
|
+
const waitUntil = options?.waitUntil ?? "load";
|
|
1179
|
+
let expectedEventSeen = false;
|
|
1180
|
+
const eventName = waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
|
|
1181
|
+
const unsubscribe = this.pageClient.on(eventName, () => {
|
|
1182
|
+
expectedEventSeen = true;
|
|
732
1183
|
});
|
|
733
1184
|
try {
|
|
734
|
-
await this.pageClient.send("Page.navigate", { url });
|
|
735
|
-
|
|
1185
|
+
const navigation = (await this.pageClient.send("Page.navigate", { url }));
|
|
1186
|
+
if (typeof navigation.errorText === "string" && navigation.errorText.length > 0) {
|
|
1187
|
+
throw new Error(`Page.navigate failed: ${navigation.errorText} at ${url}`);
|
|
1188
|
+
}
|
|
1189
|
+
await this.waitForDocumentReady(startedAt + timeout, () => expectedEventSeen, waitUntil);
|
|
736
1190
|
}
|
|
737
1191
|
finally {
|
|
738
1192
|
unsubscribe();
|
|
@@ -742,10 +1196,25 @@ class CdpPoolBrowserPage {
|
|
|
742
1196
|
await this.initialize();
|
|
743
1197
|
return await this.evaluateWithContext(fn);
|
|
744
1198
|
}
|
|
1199
|
+
async userAgent() {
|
|
1200
|
+
return await this.evaluate("navigator.userAgent");
|
|
1201
|
+
}
|
|
745
1202
|
async evaluateInFrame(frameId, fn) {
|
|
746
1203
|
await this.initialize();
|
|
747
1204
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
748
|
-
|
|
1205
|
+
try {
|
|
1206
|
+
return await this.evaluateWithContext(fn, contextId);
|
|
1207
|
+
}
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
if (!isMissingExecutionContextError(error)) {
|
|
1210
|
+
throw error;
|
|
1211
|
+
}
|
|
1212
|
+
if (this.frameExecutionContexts.get(frameId) === contextId) {
|
|
1213
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1214
|
+
}
|
|
1215
|
+
const refreshedContextId = await this.getFrameExecutionContextId(frameId);
|
|
1216
|
+
return await this.evaluateWithContext(fn, refreshedContextId);
|
|
1217
|
+
}
|
|
749
1218
|
}
|
|
750
1219
|
async waitForSelectorInFrame(frameId, selector, options) {
|
|
751
1220
|
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
@@ -845,20 +1314,35 @@ class CdpPoolBrowserPage {
|
|
|
845
1314
|
});
|
|
846
1315
|
return Buffer.from(String(result.data ?? ""), "base64");
|
|
847
1316
|
}
|
|
1317
|
+
async cookies() {
|
|
1318
|
+
await this.initialize();
|
|
1319
|
+
const result = await this.pageClient.send("Network.getCookies");
|
|
1320
|
+
const cookies = parseCdpCookies(result.cookies);
|
|
1321
|
+
for (const cookie of cookies)
|
|
1322
|
+
registerDiagnosticValue(cookie.value);
|
|
1323
|
+
return cookies;
|
|
1324
|
+
}
|
|
848
1325
|
async close() {
|
|
849
|
-
|
|
1326
|
+
// Only the first caller observes the release outcome; repeat calls stay
|
|
1327
|
+
// idempotent (as they were before the promise was memoized) instead of
|
|
1328
|
+
// replaying a cached rejection, and never re-issue a release against a
|
|
1329
|
+
// target the pool has already disposed.
|
|
1330
|
+
if (this.closePromise) {
|
|
1331
|
+
await this.closePromise.catch(() => undefined);
|
|
850
1332
|
return;
|
|
851
1333
|
}
|
|
852
|
-
this.
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1334
|
+
this.closePromise = (async () => {
|
|
1335
|
+
try {
|
|
1336
|
+
await this.release({
|
|
1337
|
+
...(this.browserContextId ? { browserContextId: this.browserContextId } : {}),
|
|
1338
|
+
pageId: this.pageId,
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
finally {
|
|
1342
|
+
await this.pageClient.close();
|
|
1343
|
+
}
|
|
1344
|
+
})();
|
|
1345
|
+
await this.closePromise;
|
|
862
1346
|
}
|
|
863
1347
|
async withResourcePolicy(policy, run) {
|
|
864
1348
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
@@ -868,7 +1352,7 @@ class CdpPoolBrowserPage {
|
|
|
868
1352
|
const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
|
|
869
1353
|
try {
|
|
870
1354
|
await this.pageClient.send("Fetch.enable", {
|
|
871
|
-
patterns:
|
|
1355
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
872
1356
|
});
|
|
873
1357
|
}
|
|
874
1358
|
catch (error) {
|
|
@@ -883,8 +1367,21 @@ class CdpPoolBrowserPage {
|
|
|
883
1367
|
return await run();
|
|
884
1368
|
}
|
|
885
1369
|
finally {
|
|
886
|
-
|
|
887
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
if (this.closePromise) {
|
|
1372
|
+
// Keep interception active until release settles. The release failure
|
|
1373
|
+
// stays observable through close() itself; swallowing it here keeps
|
|
1374
|
+
// this finally from replacing run()'s own outcome, and still avoids a
|
|
1375
|
+
// reconnect or a disable against a disposed target.
|
|
1376
|
+
await this.closePromise.catch(() => undefined);
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
await this.pageClient.send("Fetch.disable");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
finally {
|
|
1383
|
+
unsubscribe();
|
|
1384
|
+
}
|
|
888
1385
|
}
|
|
889
1386
|
}
|
|
890
1387
|
async handleResourcePolicyPausedRequest(params, policy, allowedMethods) {
|
|
@@ -893,6 +1390,24 @@ class CdpPoolBrowserPage {
|
|
|
893
1390
|
return;
|
|
894
1391
|
}
|
|
895
1392
|
try {
|
|
1393
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
1394
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
1395
|
+
if (!response)
|
|
1396
|
+
throw new Error("CDP document response metadata is unavailable");
|
|
1397
|
+
if (!response.hasBody) {
|
|
1398
|
+
await this.pageClient.send("Fetch.continueResponse", {
|
|
1399
|
+
requestId,
|
|
1400
|
+
responseCode: response.responseCode,
|
|
1401
|
+
responseHeaders: [...response.responseHeaders],
|
|
1402
|
+
});
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const body = await this.pageClient.send("Fetch.getResponseBody", {
|
|
1406
|
+
requestId: response.requestId,
|
|
1407
|
+
});
|
|
1408
|
+
await this.pageClient.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy));
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
896
1411
|
const parsed = toCdpResourceRequest(params);
|
|
897
1412
|
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
898
1413
|
await this.failCdpResourceRequest(requestId);
|
|
@@ -904,6 +1419,11 @@ class CdpPoolBrowserPage {
|
|
|
904
1419
|
}
|
|
905
1420
|
const decision = await resourceRoute.handle(parsed.request);
|
|
906
1421
|
switch (decision.action) {
|
|
1422
|
+
case "continue":
|
|
1423
|
+
await this.pageClient.send("Fetch.continueRequest", {
|
|
1424
|
+
requestId: parsed.requestId,
|
|
1425
|
+
});
|
|
1426
|
+
return;
|
|
907
1427
|
case "fulfill":
|
|
908
1428
|
await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
|
|
909
1429
|
return;
|
|
@@ -941,8 +1461,45 @@ class CdpPoolBrowserPage {
|
|
|
941
1461
|
this.frameExecutionContexts.set(context.frameId, context.id);
|
|
942
1462
|
}
|
|
943
1463
|
});
|
|
1464
|
+
this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
|
|
1465
|
+
const destroyedContextId = getCdpDestroyedExecutionContextId(params);
|
|
1466
|
+
if (destroyedContextId === undefined) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
for (const [frameId, contextId] of this.frameExecutionContexts) {
|
|
1470
|
+
if (contextId === destroyedContextId) {
|
|
1471
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
this.pageClient.on("Runtime.executionContextsCleared", () => {
|
|
1476
|
+
this.frameExecutionContexts.clear();
|
|
1477
|
+
});
|
|
1478
|
+
const registerResponseCookies = (params) => {
|
|
1479
|
+
if (!isRecord(params))
|
|
1480
|
+
return;
|
|
1481
|
+
const headers = Array.isArray(params.responseHeaders)
|
|
1482
|
+
? params.responseHeaders
|
|
1483
|
+
: isRecord(params.headers)
|
|
1484
|
+
? Object.entries(params.headers).map(([name, value]) => ({ name, value }))
|
|
1485
|
+
: [];
|
|
1486
|
+
for (const header of headers) {
|
|
1487
|
+
if (isRecord(header) &&
|
|
1488
|
+
typeof header.name === "string" &&
|
|
1489
|
+
header.name.toLowerCase() === "set-cookie" &&
|
|
1490
|
+
typeof header.value === "string") {
|
|
1491
|
+
registerDiagnosticValue(header.value);
|
|
1492
|
+
for (const value of cookieValuesFromHeaders(header.value))
|
|
1493
|
+
registerDiagnosticValue(value);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
const scopedCookieRegistration = AsyncResource.bind(registerResponseCookies);
|
|
1498
|
+
this.pageClient.on("Network.responseReceived", scopedCookieRegistration);
|
|
1499
|
+
this.pageClient.on("Network.responseReceivedExtraInfo", scopedCookieRegistration);
|
|
944
1500
|
await this.pageClient.send("Page.enable");
|
|
945
1501
|
await this.pageClient.send("Runtime.enable");
|
|
1502
|
+
await this.pageClient.send("Network.enable").catch(() => undefined);
|
|
946
1503
|
this.initialized = true;
|
|
947
1504
|
}
|
|
948
1505
|
async getFrameExecutionContextId(frameId) {
|
|
@@ -962,11 +1519,13 @@ class CdpPoolBrowserPage {
|
|
|
962
1519
|
this.frameExecutionContexts.set(frameId, contextId);
|
|
963
1520
|
return contextId;
|
|
964
1521
|
}
|
|
965
|
-
async waitForDocumentReady(deadline,
|
|
1522
|
+
async waitForDocumentReady(deadline, isExpectedEventSeen, waitUntil) {
|
|
966
1523
|
while (Date.now() < deadline) {
|
|
967
1524
|
const readyState = await this.evaluate("document.readyState");
|
|
968
|
-
|
|
969
|
-
|
|
1525
|
+
const documentReady = readyState === "complete" ||
|
|
1526
|
+
(waitUntil === "domcontentloaded" && readyState === "interactive");
|
|
1527
|
+
if (documentReady) {
|
|
1528
|
+
if (isExpectedEventSeen() || readyState === "complete") {
|
|
970
1529
|
return;
|
|
971
1530
|
}
|
|
972
1531
|
}
|
|
@@ -978,6 +1537,7 @@ class CdpPoolBrowserPage {
|
|
|
978
1537
|
class CdpPoolBrowserClient {
|
|
979
1538
|
allowedHosts;
|
|
980
1539
|
poolClient;
|
|
1540
|
+
telemetry;
|
|
981
1541
|
engine = "playwright-stealth";
|
|
982
1542
|
constructor(options) {
|
|
983
1543
|
if (!options.cdpUrl) {
|
|
@@ -986,17 +1546,30 @@ class CdpPoolBrowserClient {
|
|
|
986
1546
|
});
|
|
987
1547
|
}
|
|
988
1548
|
this.allowedHosts = [...new Set(options.allowedHosts ?? [])];
|
|
1549
|
+
this.telemetry = options.telemetry;
|
|
989
1550
|
this.poolClient = new JsonRpcWebSocketClient(options.cdpUrl);
|
|
990
1551
|
}
|
|
991
1552
|
async newPage() {
|
|
992
1553
|
return await this.acquirePage({ isolatedContext: true });
|
|
993
1554
|
}
|
|
994
1555
|
async acquirePage(options) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1556
|
+
let acquireResult;
|
|
1557
|
+
try {
|
|
1558
|
+
acquireResult = parsePoolAcquireResponse(await this.poolClient.send("acquire", {
|
|
1559
|
+
...(this.allowedHosts.length > 0 ? { allowedHosts: this.allowedHosts } : {}),
|
|
1560
|
+
...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
|
|
1561
|
+
}));
|
|
1562
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire("ok"));
|
|
1563
|
+
}
|
|
1564
|
+
catch (error) {
|
|
1565
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire(poolAcquireOutcome(error)));
|
|
1566
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_CDP_POOL_ERROR"));
|
|
1567
|
+
const code = poolAcquireCode(error);
|
|
1568
|
+
if (code !== undefined && ![-32001, -32002, -32003, -32004, -32005, -32006].includes(code))
|
|
1569
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquireUnknownCode?.(code));
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1572
|
+
const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
|
|
1000
1573
|
const page = new CdpPoolBrowserPage(acquireResult.pageId, acquireResult.browserContextId, pageClient, async (request) => {
|
|
1001
1574
|
await this.poolClient.send("release", request);
|
|
1002
1575
|
});
|
|
@@ -1068,6 +1641,7 @@ export class BrowserClient {
|
|
|
1068
1641
|
activePage;
|
|
1069
1642
|
activePages = new Set();
|
|
1070
1643
|
_engine;
|
|
1644
|
+
telemetry;
|
|
1071
1645
|
constructor(options = {}) {
|
|
1072
1646
|
const resolvedOptions = {
|
|
1073
1647
|
...options,
|
|
@@ -1075,6 +1649,7 @@ export class BrowserClient {
|
|
|
1075
1649
|
};
|
|
1076
1650
|
const engine = resolvedOptions.engine ?? "playwright-stealth";
|
|
1077
1651
|
this._engine = engine;
|
|
1652
|
+
this.telemetry = resolvedOptions.telemetry;
|
|
1078
1653
|
this.cdpUrl = resolvedOptions.cdpUrl;
|
|
1079
1654
|
if (resolvedOptions.requireCdpPool && !resolvedOptions.cdpUrl) {
|
|
1080
1655
|
throw new ProviderError("Managed CDP Pool is required for browser providers in production", {
|
|
@@ -1099,20 +1674,37 @@ export class BrowserClient {
|
|
|
1099
1674
|
return this._engine;
|
|
1100
1675
|
}
|
|
1101
1676
|
async newPage() {
|
|
1102
|
-
|
|
1103
|
-
|
|
1677
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
|
|
1678
|
+
try {
|
|
1679
|
+
const page = await this.client.newPage();
|
|
1680
|
+
return this.activatePage(page);
|
|
1681
|
+
}
|
|
1682
|
+
catch (error) {
|
|
1683
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError(browserTelemetryErrorCode(error)));
|
|
1684
|
+
throw error;
|
|
1685
|
+
}
|
|
1104
1686
|
}
|
|
1105
1687
|
async rawPage() {
|
|
1688
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
|
|
1106
1689
|
if (!this.cdpUrl) {
|
|
1690
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordPoolAcquire("not_configured"));
|
|
1691
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_RUNTIME_UNSUPPORTED"));
|
|
1107
1692
|
throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
|
|
1108
1693
|
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1109
1694
|
fix: "Set APIFUSE__CDP_POOL__URL. The SDK escape hatch is CDP pool-backed only and never launches local Chromium.",
|
|
1110
1695
|
});
|
|
1111
1696
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1697
|
+
try {
|
|
1698
|
+
const page = await this.client.rawPage();
|
|
1699
|
+
return this.activatePage(page);
|
|
1700
|
+
}
|
|
1701
|
+
catch (error) {
|
|
1702
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError(browserTelemetryErrorCode(error)));
|
|
1703
|
+
throw error;
|
|
1704
|
+
}
|
|
1114
1705
|
}
|
|
1115
1706
|
async withIsolatedContext(handler) {
|
|
1707
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
|
|
1116
1708
|
const previousActivePage = this.activePage;
|
|
1117
1709
|
let trackedPage;
|
|
1118
1710
|
return await this.client.withIsolatedContext(async (page) => {
|
|
@@ -1129,24 +1721,35 @@ export class BrowserClient {
|
|
|
1129
1721
|
});
|
|
1130
1722
|
}
|
|
1131
1723
|
activatePage(page) {
|
|
1132
|
-
let
|
|
1724
|
+
let closePromise;
|
|
1133
1725
|
const originalClose = page.close.bind(page);
|
|
1134
1726
|
const trackedPage = new Proxy(page, {
|
|
1135
1727
|
get: (target, property, receiver) => {
|
|
1136
1728
|
if (property === "close") {
|
|
1137
1729
|
return async () => {
|
|
1138
|
-
|
|
1730
|
+
// Repeat calls resolve on the first outcome instead of replaying a
|
|
1731
|
+
// cached rejection, so a failed release cannot make close() — or
|
|
1732
|
+
// BrowserClient.close(), which closes every tracked page — throw
|
|
1733
|
+
// forever.
|
|
1734
|
+
if (closePromise) {
|
|
1735
|
+
await closePromise.catch(() => undefined);
|
|
1139
1736
|
return;
|
|
1140
|
-
closed = true;
|
|
1141
|
-
try {
|
|
1142
|
-
await originalClose();
|
|
1143
1737
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1738
|
+
closePromise = (async () => {
|
|
1739
|
+
// Bookkeeping runs even when the release fails: the page is gone
|
|
1740
|
+
// either way, so leaving it in activePages would leak it and let
|
|
1741
|
+
// solveChallenge reuse a disposed target.
|
|
1742
|
+
try {
|
|
1743
|
+
await originalClose();
|
|
1148
1744
|
}
|
|
1149
|
-
|
|
1745
|
+
finally {
|
|
1746
|
+
this.activePages.delete(trackedPage);
|
|
1747
|
+
if (this.activePage === trackedPage) {
|
|
1748
|
+
this.activePage = undefined;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
})();
|
|
1752
|
+
await closePromise;
|
|
1150
1753
|
};
|
|
1151
1754
|
}
|
|
1152
1755
|
const value = Reflect.get(target, property, receiver);
|
|
@@ -1158,7 +1761,9 @@ export class BrowserClient {
|
|
|
1158
1761
|
return trackedPage;
|
|
1159
1762
|
}
|
|
1160
1763
|
async solveChallenge(request) {
|
|
1764
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordEngine(this._engine));
|
|
1161
1765
|
if (request.type !== "recaptcha") {
|
|
1766
|
+
observeBrowserTelemetry(this.telemetry, () => this.telemetry?.recordError("BROWSER_RUNTIME_UNSUPPORTED"));
|
|
1162
1767
|
throw new ProviderError(`Unsupported browser challenge: ${request.type}`, {
|
|
1163
1768
|
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1164
1769
|
});
|