@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50
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 +537 -33
- package/CHANGELOG.md +201 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- 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 +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -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 +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- 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 +179 -16
- package/dist/config/loader.js +435 -127
- 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 +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- 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 +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- 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 +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +14 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- 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 +5 -0
- package/dist/runtime/trace.js +43 -10
- 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/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- 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.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -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 +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -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 +9 -4
- package/dist/stealth/profiles.js +160 -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 +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- 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 +690 -163
- 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 +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- 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 +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -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 +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -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 +202 -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 +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/src/runtime/browser.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import type { Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
|
|
2
|
+
import type { CDPSession, Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
|
|
3
3
|
|
|
4
4
|
import { ProviderError } from "../errors.js";
|
|
5
5
|
import type {
|
|
6
6
|
BrowserChallengeRequest,
|
|
7
7
|
BrowserChallengeResult,
|
|
8
8
|
BrowserClient as BrowserClientContract,
|
|
9
|
+
BrowserCookie,
|
|
9
10
|
BrowserEngine,
|
|
10
11
|
BrowserFrame,
|
|
11
12
|
BrowserLocator,
|
|
@@ -42,19 +43,35 @@ type PoolReleaseRequest = {
|
|
|
42
43
|
pageId: string;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
type
|
|
46
|
+
type WebSocketCommandId = number;
|
|
46
47
|
|
|
47
|
-
type
|
|
48
|
+
type WebSocketCommandError = {
|
|
48
49
|
code?: number;
|
|
49
50
|
message?: string;
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
type
|
|
53
|
-
error?:
|
|
54
|
-
id?:
|
|
53
|
+
type WebSocketCommandMessage = {
|
|
54
|
+
error?: WebSocketCommandError;
|
|
55
|
+
id?: WebSocketCommandId;
|
|
56
|
+
jsonrpc?: "2.0";
|
|
55
57
|
method?: string;
|
|
56
58
|
params?: Record<string, unknown>;
|
|
57
59
|
result?: Record<string, unknown>;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type CdpCommandFrame = {
|
|
64
|
+
id: WebSocketCommandId;
|
|
65
|
+
method: string;
|
|
66
|
+
params: Record<string, unknown>;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type JsonRpcCommandFrame = {
|
|
71
|
+
id: WebSocketCommandId;
|
|
72
|
+
jsonrpc: "2.0";
|
|
73
|
+
method: string;
|
|
74
|
+
params: Record<string, unknown>;
|
|
58
75
|
};
|
|
59
76
|
|
|
60
77
|
type CdpFrameTreeNode = {
|
|
@@ -70,13 +87,28 @@ type CdpFrameTreeNode = {
|
|
|
70
87
|
type CdpFetchFulfillParams = {
|
|
71
88
|
readonly requestId: string;
|
|
72
89
|
readonly responseCode: number;
|
|
73
|
-
readonly responseHeaders?:
|
|
90
|
+
readonly responseHeaders?: {
|
|
74
91
|
readonly name: string;
|
|
75
92
|
readonly value: string;
|
|
76
93
|
}[];
|
|
77
94
|
readonly body?: string;
|
|
78
95
|
};
|
|
79
96
|
|
|
97
|
+
type CdpPausedDocumentResponse = {
|
|
98
|
+
readonly hasBody: boolean;
|
|
99
|
+
readonly requestId: string;
|
|
100
|
+
readonly responseCode: number;
|
|
101
|
+
readonly responseHeaders: readonly {
|
|
102
|
+
readonly name: string;
|
|
103
|
+
readonly value: string;
|
|
104
|
+
}[];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type CdpResponseBody = {
|
|
108
|
+
readonly base64Encoded?: boolean;
|
|
109
|
+
readonly body?: string;
|
|
110
|
+
};
|
|
111
|
+
|
|
80
112
|
type BrowserPageContract = BrowserPage;
|
|
81
113
|
|
|
82
114
|
function toResourceBody(body: BrowserResourceBody | undefined): Buffer | string | undefined {
|
|
@@ -92,7 +124,7 @@ function toResourceBody(body: BrowserResourceBody | undefined): Buffer | string
|
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
function isResourceMethod(method: string): method is BrowserResourceMethod {
|
|
95
|
-
return method === "GET" || method === "HEAD";
|
|
127
|
+
return method === "GET" || method === "HEAD" || method === "POST";
|
|
96
128
|
}
|
|
97
129
|
|
|
98
130
|
async function toResourceRequest(request: Request): Promise<BrowserResourceRequest | null> {
|
|
@@ -147,6 +179,85 @@ function getCdpPausedRequestId(params: unknown): string | null {
|
|
|
147
179
|
return params.requestId;
|
|
148
180
|
}
|
|
149
181
|
|
|
182
|
+
function isCdpResponseStage(params: unknown): boolean {
|
|
183
|
+
return (
|
|
184
|
+
isRecord(params) &&
|
|
185
|
+
(typeof params.responseStatusCode === "number" ||
|
|
186
|
+
typeof params.responseErrorReason === "string")
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function toCdpPausedDocumentResponse(params: unknown): CdpPausedDocumentResponse | null {
|
|
191
|
+
if (
|
|
192
|
+
!isRecord(params) ||
|
|
193
|
+
typeof params.requestId !== "string" ||
|
|
194
|
+
typeof params.responseStatusCode !== "number" ||
|
|
195
|
+
params.resourceType !== "Document" ||
|
|
196
|
+
!isRecord(params.request)
|
|
197
|
+
) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const responseCode = params.responseStatusCode;
|
|
202
|
+
const responseHeaders = Array.isArray(params.responseHeaders)
|
|
203
|
+
? params.responseHeaders.flatMap((header) => {
|
|
204
|
+
if (
|
|
205
|
+
!isRecord(header) ||
|
|
206
|
+
typeof header.name !== "string" ||
|
|
207
|
+
typeof header.value !== "string"
|
|
208
|
+
) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
return [{ name: header.name, value: header.value }];
|
|
212
|
+
})
|
|
213
|
+
: [];
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
hasBody:
|
|
217
|
+
String(params.request.method ?? "").toUpperCase() !== "HEAD" &&
|
|
218
|
+
responseCode >= 200 &&
|
|
219
|
+
(responseCode < 300 || responseCode >= 400) &&
|
|
220
|
+
responseCode !== 204 &&
|
|
221
|
+
responseCode !== 205 &&
|
|
222
|
+
responseCode !== 304,
|
|
223
|
+
requestId: params.requestId,
|
|
224
|
+
responseCode,
|
|
225
|
+
responseHeaders,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function toCdpDocumentFulfillParams(
|
|
230
|
+
response: CdpPausedDocumentResponse,
|
|
231
|
+
body: CdpResponseBody,
|
|
232
|
+
contentSecurityPolicy: string,
|
|
233
|
+
): CdpFetchFulfillParams {
|
|
234
|
+
const responseBody = typeof body.body === "string" ? body.body : "";
|
|
235
|
+
return {
|
|
236
|
+
body: body.base64Encoded ? responseBody : Buffer.from(responseBody).toString("base64"),
|
|
237
|
+
requestId: response.requestId,
|
|
238
|
+
responseCode: response.responseCode,
|
|
239
|
+
responseHeaders: [
|
|
240
|
+
...response.responseHeaders,
|
|
241
|
+
{ name: "Content-Security-Policy", value: contentSecurityPolicy },
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function resourcePolicyFetchPatterns(policy: BrowserResourcePolicy) {
|
|
247
|
+
return [
|
|
248
|
+
{ requestStage: "Request" as const, urlPattern: "*" },
|
|
249
|
+
...(policy.documentContentSecurityPolicy
|
|
250
|
+
? [
|
|
251
|
+
{
|
|
252
|
+
requestStage: "Response" as const,
|
|
253
|
+
resourceType: "Document" as const,
|
|
254
|
+
urlPattern: "*",
|
|
255
|
+
},
|
|
256
|
+
]
|
|
257
|
+
: []),
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
|
|
150
261
|
function toCdpResourceHeaders(value: unknown): Record<string, string> {
|
|
151
262
|
if (!isRecord(value)) {
|
|
152
263
|
return {};
|
|
@@ -208,11 +319,171 @@ async function fulfillResourceRoute(
|
|
|
208
319
|
});
|
|
209
320
|
}
|
|
210
321
|
|
|
322
|
+
async function decideResourceRequest(
|
|
323
|
+
policy: BrowserResourcePolicy,
|
|
324
|
+
request: BrowserResourceRequest,
|
|
325
|
+
): Promise<BrowserResourceDecision> {
|
|
326
|
+
for (const resourceRoute of policy.routes) {
|
|
327
|
+
if (matchesResourceRoute(resourceRoute.match, request)) {
|
|
328
|
+
return await resourceRoute.handle(request);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { action: "block" };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function abortResourceRoute(route: Route): Promise<void> {
|
|
336
|
+
await route.abort("blockedbyclient");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function handleCdpResourceRequest(
|
|
340
|
+
session: CDPSession,
|
|
341
|
+
policy: BrowserResourcePolicy,
|
|
342
|
+
allowedMethods: ReadonlySet<BrowserResourceMethod>,
|
|
343
|
+
params: unknown,
|
|
344
|
+
): Promise<void> {
|
|
345
|
+
const paused = toCdpResourceRequest(params);
|
|
346
|
+
const requestId = paused?.requestId ?? getCdpPausedRequestId(params);
|
|
347
|
+
if (!requestId) return;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
if (!paused || !allowedMethods.has(paused.request.method)) {
|
|
351
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const decision = await decideResourceRequest(policy, paused.request);
|
|
356
|
+
switch (decision.action) {
|
|
357
|
+
case "continue":
|
|
358
|
+
await session.send("Fetch.continueRequest", { requestId });
|
|
359
|
+
return;
|
|
360
|
+
case "fulfill":
|
|
361
|
+
await session.send("Fetch.fulfillRequest", toCdpFulfillParams(requestId, decision));
|
|
362
|
+
return;
|
|
363
|
+
case "block":
|
|
364
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
await session
|
|
369
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
370
|
+
.catch(() => undefined);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function handleCdpDocumentResponse(
|
|
375
|
+
session: CDPSession,
|
|
376
|
+
contentSecurityPolicy: string,
|
|
377
|
+
params: unknown,
|
|
378
|
+
): Promise<void> {
|
|
379
|
+
const requestId = getCdpPausedRequestId(params);
|
|
380
|
+
if (!requestId) return;
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
384
|
+
if (!response) throw new Error("CDP document response metadata is unavailable");
|
|
385
|
+
if (!response.hasBody) {
|
|
386
|
+
await session.send("Fetch.continueResponse", {
|
|
387
|
+
requestId,
|
|
388
|
+
responseCode: response.responseCode,
|
|
389
|
+
responseHeaders: [...response.responseHeaders],
|
|
390
|
+
});
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const body = await session.send("Fetch.getResponseBody", {
|
|
395
|
+
requestId: response.requestId,
|
|
396
|
+
});
|
|
397
|
+
await session.send(
|
|
398
|
+
"Fetch.fulfillRequest",
|
|
399
|
+
toCdpDocumentFulfillParams(response, body, contentSecurityPolicy),
|
|
400
|
+
);
|
|
401
|
+
} catch {
|
|
402
|
+
await session
|
|
403
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
404
|
+
.catch(() => undefined);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function getCdpAuthRequiredRequestId(params: unknown): string | null {
|
|
409
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return params.requestId;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function isCdpProxyAuthChallenge(params: unknown): boolean {
|
|
417
|
+
return (
|
|
418
|
+
isRecord(params) &&
|
|
419
|
+
isRecord(params.authChallenge) &&
|
|
420
|
+
params.authChallenge.source === "Proxy"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function handleCdpAuthRequired(
|
|
425
|
+
session: CDPSession,
|
|
426
|
+
proxy: PlaywrightProxy & { username: string; password: string },
|
|
427
|
+
authAttempts: Set<string>,
|
|
428
|
+
params: unknown,
|
|
429
|
+
): Promise<void> {
|
|
430
|
+
const requestId = getCdpAuthRequiredRequestId(params);
|
|
431
|
+
if (!requestId) return;
|
|
432
|
+
|
|
433
|
+
// Chromium can emit another challenge after rejected credentials. Cancel a
|
|
434
|
+
// retry so incorrect credentials fail promptly instead of looping forever.
|
|
435
|
+
const response =
|
|
436
|
+
isCdpProxyAuthChallenge(params) && !authAttempts.has(requestId)
|
|
437
|
+
? (() => {
|
|
438
|
+
authAttempts.add(requestId);
|
|
439
|
+
return {
|
|
440
|
+
authChallengeResponse: {
|
|
441
|
+
password: proxy.password,
|
|
442
|
+
response: "ProvideCredentials" as const,
|
|
443
|
+
username: proxy.username,
|
|
444
|
+
},
|
|
445
|
+
requestId,
|
|
446
|
+
};
|
|
447
|
+
})()
|
|
448
|
+
: {
|
|
449
|
+
authChallengeResponse: { response: "CancelAuth" as const },
|
|
450
|
+
requestId,
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await session.send("Fetch.continueWithAuth", response).catch(() => undefined);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function createResourcePolicyHandler(
|
|
457
|
+
policy: BrowserResourcePolicy,
|
|
458
|
+
allowedMethods: ReadonlySet<BrowserResourceMethod>,
|
|
459
|
+
): (route: Route) => Promise<void> {
|
|
460
|
+
return async (route: Route): Promise<void> => {
|
|
461
|
+
const request = await toResourceRequest(route.request());
|
|
462
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
463
|
+
await abortResourceRoute(route);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const decision = await decideResourceRequest(policy, request);
|
|
468
|
+
switch (decision.action) {
|
|
469
|
+
case "continue":
|
|
470
|
+
await route.continue();
|
|
471
|
+
return;
|
|
472
|
+
case "fulfill":
|
|
473
|
+
await fulfillResourceRoute(route, decision);
|
|
474
|
+
return;
|
|
475
|
+
case "block":
|
|
476
|
+
await abortResourceRoute(route);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
211
481
|
export type BrowserClientOptions = BrowserOptions & {
|
|
212
482
|
allowedHosts?: string[];
|
|
213
483
|
cdpUrl?: string;
|
|
214
484
|
executablePath?: string;
|
|
215
485
|
extraArgs?: string[];
|
|
486
|
+
serviceWorkers?: "allow" | "block";
|
|
216
487
|
};
|
|
217
488
|
|
|
218
489
|
type SupportedBrowserClient = {
|
|
@@ -271,12 +542,70 @@ function formatExpression<T>(fn: string | (() => T)): string {
|
|
|
271
542
|
return `(${fn.toString()})()`;
|
|
272
543
|
}
|
|
273
544
|
|
|
274
|
-
function toLaunchOptions(
|
|
545
|
+
function toLaunchOptions(
|
|
546
|
+
options: BrowserClientOptions,
|
|
547
|
+
proxy: LaunchOptions["proxy"],
|
|
548
|
+
): LaunchOptions {
|
|
275
549
|
return {
|
|
276
|
-
|
|
550
|
+
// `extraArgs` is optional, but playwright-extra's stealth evasions mutate
|
|
551
|
+
// `options.args` unguarded (navigator.webdriver does
|
|
552
|
+
// `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
|
|
553
|
+
// therefore crashes every stealth launch that omits extraArgs with
|
|
554
|
+
// "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
|
|
555
|
+
// Always hand the launcher a concrete array.
|
|
556
|
+
args: options.extraArgs ?? [],
|
|
277
557
|
executablePath: options.executablePath,
|
|
278
558
|
headless: options.headless ?? true,
|
|
279
|
-
proxy
|
|
559
|
+
proxy,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
type PlaywrightProxy = NonNullable<LaunchOptions["proxy"]>;
|
|
564
|
+
|
|
565
|
+
function hasProxyCredentials(
|
|
566
|
+
proxy: LaunchOptions["proxy"] | undefined,
|
|
567
|
+
): proxy is PlaywrightProxy & { username: string; password: string } {
|
|
568
|
+
return proxy?.username !== undefined && proxy.password !== undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function toPlaywrightProxy(proxy: string | undefined): LaunchOptions["proxy"] {
|
|
572
|
+
if (!proxy) return undefined;
|
|
573
|
+
|
|
574
|
+
const schemeEnd = proxy.indexOf("://");
|
|
575
|
+
const authorityStart = schemeEnd >= 0 ? schemeEnd + 3 : 0;
|
|
576
|
+
const remainder = proxy.slice(authorityStart);
|
|
577
|
+
const authorityEnd = remainder.search(/[/?#]/);
|
|
578
|
+
const authority = remainder.slice(0, authorityEnd >= 0 ? authorityEnd : undefined);
|
|
579
|
+
if (!authority.includes("@")) {
|
|
580
|
+
return { server: proxy };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
let parsed: URL;
|
|
584
|
+
try {
|
|
585
|
+
parsed = new URL(proxy);
|
|
586
|
+
} catch {
|
|
587
|
+
throw new ProviderError("Browser proxy URL is invalid", {
|
|
588
|
+
code: "BROWSER_PROXY_INVALID",
|
|
589
|
+
fix: "Use a valid proxy URL.",
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
let username: string;
|
|
594
|
+
let password: string;
|
|
595
|
+
try {
|
|
596
|
+
username = decodeURIComponent(parsed.username);
|
|
597
|
+
password = decodeURIComponent(parsed.password);
|
|
598
|
+
} catch {
|
|
599
|
+
throw new ProviderError("Browser proxy credentials use invalid percent-encoding", {
|
|
600
|
+
code: "BROWSER_PROXY_INVALID",
|
|
601
|
+
fix: "Percent-encode the proxy username and password as URL userinfo.",
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
server: `${parsed.protocol}//${parsed.host}`,
|
|
607
|
+
username,
|
|
608
|
+
password,
|
|
280
609
|
};
|
|
281
610
|
}
|
|
282
611
|
|
|
@@ -470,10 +799,16 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
470
799
|
readonly id = "main";
|
|
471
800
|
readonly pageId?: string;
|
|
472
801
|
|
|
473
|
-
constructor(
|
|
802
|
+
constructor(
|
|
803
|
+
private readonly page: Page,
|
|
804
|
+
private readonly proxy: LaunchOptions["proxy"] = undefined,
|
|
805
|
+
) {}
|
|
474
806
|
|
|
475
|
-
async goto(
|
|
476
|
-
|
|
807
|
+
async goto(
|
|
808
|
+
url: string,
|
|
809
|
+
options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
|
|
810
|
+
): Promise<void> {
|
|
811
|
+
await this.page.goto(url, options);
|
|
477
812
|
}
|
|
478
813
|
|
|
479
814
|
async evaluate<T>(fn: string | (() => T)): Promise<T> {
|
|
@@ -484,6 +819,10 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
484
819
|
return await this.page.evaluate(fn);
|
|
485
820
|
}
|
|
486
821
|
|
|
822
|
+
async userAgent(): Promise<string> {
|
|
823
|
+
return await this.evaluate<string>("navigator.userAgent");
|
|
824
|
+
}
|
|
825
|
+
|
|
487
826
|
async waitForSelector(selector: string, options?: { timeout?: number }): Promise<void> {
|
|
488
827
|
await this.page.waitForSelector(selector, options);
|
|
489
828
|
}
|
|
@@ -529,45 +868,108 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
529
868
|
await this.page.close();
|
|
530
869
|
}
|
|
531
870
|
|
|
871
|
+
async cookies(): Promise<readonly BrowserCookie[]> {
|
|
872
|
+
return (await this.page.context().cookies()).map(toBrowserCookie);
|
|
873
|
+
}
|
|
874
|
+
|
|
532
875
|
async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
|
|
533
876
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
877
|
+
const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
|
|
878
|
+
const context = this.page.context();
|
|
534
879
|
const handler = async (route: Route): Promise<void> => {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
880
|
+
try {
|
|
881
|
+
// Context routing sees a popup's first request before the late `page`
|
|
882
|
+
// event. Fail every secondary page closed so no redirect can escape
|
|
883
|
+
// the per-page CDP interception while that session is being attached.
|
|
884
|
+
if (route.request().frame().page() !== this.page) {
|
|
885
|
+
await abortResourceRoute(route);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
} catch {
|
|
889
|
+
await abortResourceRoute(route);
|
|
538
890
|
return;
|
|
539
891
|
}
|
|
540
892
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
893
|
+
await policyHandler(route);
|
|
894
|
+
};
|
|
895
|
+
const sessions = new Set<CDPSession>();
|
|
896
|
+
const pendingAttachments = new Set<Promise<void>>();
|
|
897
|
+
const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
|
|
898
|
+
let active = true;
|
|
899
|
+
const attachPage = async (page: Page): Promise<void> => {
|
|
900
|
+
const session = await context.newCDPSession(page);
|
|
901
|
+
if (!active) {
|
|
902
|
+
await session.detach();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
545
905
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return;
|
|
551
|
-
case "block":
|
|
552
|
-
await route.abort("blockedbyclient");
|
|
553
|
-
return;
|
|
906
|
+
const onRequestPaused = (params: unknown) => {
|
|
907
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
908
|
+
void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
|
|
909
|
+
return;
|
|
554
910
|
}
|
|
911
|
+
void handleCdpResourceRequest(session, policy, allowedMethods, params);
|
|
912
|
+
};
|
|
913
|
+
const authAttempts = new Set<string>();
|
|
914
|
+
const onAuthRequired = (params: unknown) => {
|
|
915
|
+
if (!proxy) return;
|
|
916
|
+
void handleCdpAuthRequired(session, proxy, authAttempts, params);
|
|
917
|
+
};
|
|
918
|
+
session.on("Fetch.requestPaused", onRequestPaused);
|
|
919
|
+
if (proxy) {
|
|
920
|
+
session.on("Fetch.authRequired", onAuthRequired);
|
|
555
921
|
}
|
|
556
|
-
|
|
557
|
-
|
|
922
|
+
try {
|
|
923
|
+
await session.send("Fetch.enable", {
|
|
924
|
+
...(proxy ? { handleAuthRequests: true } : {}),
|
|
925
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
926
|
+
});
|
|
927
|
+
sessions.add(session);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
session.off("Fetch.requestPaused", onRequestPaused);
|
|
930
|
+
if (proxy) {
|
|
931
|
+
session.off("Fetch.authRequired", onAuthRequired);
|
|
932
|
+
}
|
|
933
|
+
await session.detach().catch(() => undefined);
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
const onPage = (page: Page) => {
|
|
938
|
+
const attachment = attachPage(page)
|
|
939
|
+
.catch(async () => {
|
|
940
|
+
await page.close().catch(() => undefined);
|
|
941
|
+
})
|
|
942
|
+
.finally(() => pendingAttachments.delete(attachment));
|
|
943
|
+
pendingAttachments.add(attachment);
|
|
558
944
|
};
|
|
559
945
|
|
|
560
|
-
await
|
|
946
|
+
await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
561
947
|
try {
|
|
562
|
-
|
|
948
|
+
context.on("page", onPage);
|
|
949
|
+
try {
|
|
950
|
+
await attachPage(this.page);
|
|
951
|
+
return await run();
|
|
952
|
+
} finally {
|
|
953
|
+
context.off("page", onPage);
|
|
954
|
+
}
|
|
563
955
|
} finally {
|
|
564
|
-
|
|
956
|
+
active = false;
|
|
957
|
+
await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
958
|
+
await Promise.allSettled(pendingAttachments);
|
|
959
|
+
await Promise.all(
|
|
960
|
+
[...sessions].map(async (session) => {
|
|
961
|
+
await session.send("Fetch.disable").catch(() => undefined);
|
|
962
|
+
await session.detach().catch(() => undefined);
|
|
963
|
+
}),
|
|
964
|
+
);
|
|
565
965
|
}
|
|
566
966
|
}
|
|
567
967
|
}
|
|
568
968
|
|
|
569
969
|
class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
570
970
|
private browser: import("playwright").Browser | null = null;
|
|
971
|
+
private parsedProxy: LaunchOptions["proxy"] | undefined;
|
|
972
|
+
private proxyParsed = false;
|
|
571
973
|
readonly engine = "playwright-stealth" satisfies BrowserEngine;
|
|
572
974
|
|
|
573
975
|
constructor(private readonly options: BrowserClientOptions = {}) {}
|
|
@@ -578,7 +980,11 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
|
578
980
|
}
|
|
579
981
|
|
|
580
982
|
const chromium = await loadChromiumLauncher(this.options);
|
|
581
|
-
|
|
983
|
+
if (!this.proxyParsed) {
|
|
984
|
+
this.parsedProxy = toPlaywrightProxy(this.options.proxy);
|
|
985
|
+
this.proxyParsed = true;
|
|
986
|
+
}
|
|
987
|
+
this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
|
|
582
988
|
return this.browser;
|
|
583
989
|
}
|
|
584
990
|
|
|
@@ -586,7 +992,7 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
|
586
992
|
const browser = await this.ensureBrowser();
|
|
587
993
|
const page = await browser.newPage();
|
|
588
994
|
|
|
589
|
-
return new PlaywrightBrowserPage(page);
|
|
995
|
+
return new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
590
996
|
}
|
|
591
997
|
|
|
592
998
|
async rawPage(): Promise<BrowserPageContract> {
|
|
@@ -598,9 +1004,9 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
|
598
1004
|
|
|
599
1005
|
async withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T> {
|
|
600
1006
|
const browser = await this.ensureBrowser();
|
|
601
|
-
const context = await browser.newContext();
|
|
1007
|
+
const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
|
|
602
1008
|
const page = await context.newPage();
|
|
603
|
-
const browserPage = new PlaywrightBrowserPage(page);
|
|
1009
|
+
const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
604
1010
|
|
|
605
1011
|
try {
|
|
606
1012
|
return await handler(browserPage);
|
|
@@ -641,12 +1047,12 @@ function normalizeWebSocketEndpoint(endpoint: string): string {
|
|
|
641
1047
|
throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
|
|
642
1048
|
}
|
|
643
1049
|
|
|
644
|
-
class
|
|
1050
|
+
abstract class WebSocketCommandClient {
|
|
645
1051
|
private nextId = 1;
|
|
646
1052
|
private readonly endpoint: string;
|
|
647
1053
|
private readonly listeners = new Map<string, Set<(params: unknown) => void>>();
|
|
648
1054
|
private readonly pending = new Map<
|
|
649
|
-
|
|
1055
|
+
WebSocketCommandId,
|
|
650
1056
|
{
|
|
651
1057
|
reject: (reason?: unknown) => void;
|
|
652
1058
|
resolve: (value: Record<string, unknown>) => void;
|
|
@@ -682,14 +1088,7 @@ class JsonRpcWebSocketClient {
|
|
|
682
1088
|
return await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
683
1089
|
this.pending.set(id, { resolve, reject });
|
|
684
1090
|
|
|
685
|
-
socket.send(
|
|
686
|
-
JSON.stringify({
|
|
687
|
-
id,
|
|
688
|
-
jsonrpc: "2.0",
|
|
689
|
-
method,
|
|
690
|
-
params,
|
|
691
|
-
}),
|
|
692
|
-
);
|
|
1091
|
+
socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
|
|
693
1092
|
});
|
|
694
1093
|
}
|
|
695
1094
|
|
|
@@ -705,6 +1104,12 @@ class JsonRpcWebSocketClient {
|
|
|
705
1104
|
this.socketPromise = undefined;
|
|
706
1105
|
}
|
|
707
1106
|
|
|
1107
|
+
protected abstract createCommandFrame(
|
|
1108
|
+
id: WebSocketCommandId,
|
|
1109
|
+
method: string,
|
|
1110
|
+
params: Record<string, unknown>,
|
|
1111
|
+
): CdpCommandFrame | JsonRpcCommandFrame;
|
|
1112
|
+
|
|
708
1113
|
private async getSocket(): Promise<WebSocket> {
|
|
709
1114
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
710
1115
|
return this.socket;
|
|
@@ -727,7 +1132,7 @@ class JsonRpcWebSocketClient {
|
|
|
727
1132
|
typeof event.data === "string"
|
|
728
1133
|
? event.data
|
|
729
1134
|
: Buffer.from(event.data as ArrayBufferLike).toString("utf8");
|
|
730
|
-
const payload = JSON.parse(rawData) as
|
|
1135
|
+
const payload = JSON.parse(rawData) as WebSocketCommandMessage;
|
|
731
1136
|
|
|
732
1137
|
if (typeof payload.id === "number") {
|
|
733
1138
|
const pending = this.pending.get(payload.id);
|
|
@@ -738,7 +1143,11 @@ class JsonRpcWebSocketClient {
|
|
|
738
1143
|
this.pending.delete(payload.id);
|
|
739
1144
|
|
|
740
1145
|
if (payload.error) {
|
|
741
|
-
|
|
1146
|
+
const error = new Error(payload.error.message ?? "JSON-RPC command failed");
|
|
1147
|
+
if (typeof payload.error.code === "number") {
|
|
1148
|
+
Object.assign(error, { code: payload.error.code });
|
|
1149
|
+
}
|
|
1150
|
+
pending.reject(error);
|
|
742
1151
|
return;
|
|
743
1152
|
}
|
|
744
1153
|
|
|
@@ -774,6 +1183,38 @@ class JsonRpcWebSocketClient {
|
|
|
774
1183
|
}
|
|
775
1184
|
}
|
|
776
1185
|
|
|
1186
|
+
class CdpWebSocketClient extends WebSocketCommandClient {
|
|
1187
|
+
constructor(
|
|
1188
|
+
endpoint: string,
|
|
1189
|
+
private readonly sessionId?: string,
|
|
1190
|
+
) {
|
|
1191
|
+
super(endpoint);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
protected createCommandFrame(
|
|
1195
|
+
id: WebSocketCommandId,
|
|
1196
|
+
method: string,
|
|
1197
|
+
params: Record<string, unknown>,
|
|
1198
|
+
): CdpCommandFrame {
|
|
1199
|
+
return {
|
|
1200
|
+
id,
|
|
1201
|
+
method,
|
|
1202
|
+
params,
|
|
1203
|
+
...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
class JsonRpcWebSocketClient extends WebSocketCommandClient {
|
|
1209
|
+
protected createCommandFrame(
|
|
1210
|
+
id: WebSocketCommandId,
|
|
1211
|
+
method: string,
|
|
1212
|
+
params: Record<string, unknown>,
|
|
1213
|
+
): JsonRpcCommandFrame {
|
|
1214
|
+
return { id, jsonrpc: "2.0", method, params };
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
777
1218
|
function flattenCdpFrameTree(
|
|
778
1219
|
node: CdpFrameTreeNode | undefined,
|
|
779
1220
|
out: CdpFrameTreeNode["frame"][] = [],
|
|
@@ -794,6 +1235,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
794
1235
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
795
1236
|
}
|
|
796
1237
|
|
|
1238
|
+
function isBrowserCookieSameSite(value: unknown): value is BrowserCookie["sameSite"] {
|
|
1239
|
+
return value === "Strict" || value === "Lax" || value === "None";
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function toBrowserCookie(cookie: {
|
|
1243
|
+
readonly name: string;
|
|
1244
|
+
readonly value: string;
|
|
1245
|
+
readonly domain: string;
|
|
1246
|
+
readonly path: string;
|
|
1247
|
+
readonly expires?: number;
|
|
1248
|
+
readonly httpOnly: boolean;
|
|
1249
|
+
readonly secure: boolean;
|
|
1250
|
+
readonly sameSite?: unknown;
|
|
1251
|
+
}): BrowserCookie {
|
|
1252
|
+
return {
|
|
1253
|
+
name: cookie.name,
|
|
1254
|
+
value: cookie.value,
|
|
1255
|
+
domain: cookie.domain,
|
|
1256
|
+
path: cookie.path,
|
|
1257
|
+
...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
|
|
1258
|
+
httpOnly: cookie.httpOnly,
|
|
1259
|
+
secure: cookie.secure,
|
|
1260
|
+
...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function parseCdpCookies(value: unknown): readonly BrowserCookie[] {
|
|
1265
|
+
if (!Array.isArray(value)) {
|
|
1266
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie list");
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return value.map((cookie) => {
|
|
1270
|
+
if (
|
|
1271
|
+
!isRecord(cookie) ||
|
|
1272
|
+
typeof cookie.name !== "string" ||
|
|
1273
|
+
typeof cookie.value !== "string" ||
|
|
1274
|
+
typeof cookie.domain !== "string" ||
|
|
1275
|
+
typeof cookie.path !== "string" ||
|
|
1276
|
+
typeof cookie.expires !== "number" ||
|
|
1277
|
+
typeof cookie.httpOnly !== "boolean" ||
|
|
1278
|
+
typeof cookie.secure !== "boolean"
|
|
1279
|
+
) {
|
|
1280
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie");
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
return toBrowserCookie({
|
|
1284
|
+
name: cookie.name,
|
|
1285
|
+
value: cookie.value,
|
|
1286
|
+
domain: cookie.domain,
|
|
1287
|
+
path: cookie.path,
|
|
1288
|
+
expires: cookie.expires,
|
|
1289
|
+
httpOnly: cookie.httpOnly,
|
|
1290
|
+
secure: cookie.secure,
|
|
1291
|
+
sameSite: cookie.sameSite,
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
797
1296
|
function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
|
|
798
1297
|
if (
|
|
799
1298
|
!isRecord(value) ||
|
|
@@ -861,6 +1360,20 @@ function getCdpExecutionContext(params: unknown): {
|
|
|
861
1360
|
};
|
|
862
1361
|
}
|
|
863
1362
|
|
|
1363
|
+
function getCdpDestroyedExecutionContextId(params: unknown): number | undefined {
|
|
1364
|
+
if (!isRecord(params)) {
|
|
1365
|
+
return undefined;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return typeof params.executionContextId === "number"
|
|
1369
|
+
? params.executionContextId
|
|
1370
|
+
: undefined;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function isMissingExecutionContextError(error: unknown): boolean {
|
|
1374
|
+
return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
864
1377
|
class CdpBrowserLocator implements BrowserLocator {
|
|
865
1378
|
constructor(
|
|
866
1379
|
private readonly frame: {
|
|
@@ -974,7 +1487,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
974
1487
|
constructor(
|
|
975
1488
|
readonly pageId: string,
|
|
976
1489
|
private readonly browserContextId: string | undefined,
|
|
977
|
-
private readonly pageClient:
|
|
1490
|
+
private readonly pageClient: CdpWebSocketClient,
|
|
978
1491
|
private readonly release: (request: PoolReleaseRequest) => Promise<void>,
|
|
979
1492
|
) {}
|
|
980
1493
|
|
|
@@ -982,17 +1495,33 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
982
1495
|
return this.pageId;
|
|
983
1496
|
}
|
|
984
1497
|
|
|
985
|
-
async goto(
|
|
1498
|
+
async goto(
|
|
1499
|
+
url: string,
|
|
1500
|
+
options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
|
|
1501
|
+
): Promise<void> {
|
|
986
1502
|
await this.initialize();
|
|
987
1503
|
const startedAt = Date.now();
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
1504
|
+
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
1505
|
+
const waitUntil = options?.waitUntil ?? "load";
|
|
1506
|
+
let expectedEventSeen = false;
|
|
1507
|
+
const eventName =
|
|
1508
|
+
waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
|
|
1509
|
+
const unsubscribe = this.pageClient.on(eventName, () => {
|
|
1510
|
+
expectedEventSeen = true;
|
|
991
1511
|
});
|
|
992
1512
|
|
|
993
1513
|
try {
|
|
994
|
-
await this.pageClient.send("Page.navigate", { url })
|
|
995
|
-
|
|
1514
|
+
const navigation = (await this.pageClient.send("Page.navigate", { url })) as {
|
|
1515
|
+
readonly errorText?: unknown;
|
|
1516
|
+
};
|
|
1517
|
+
if (typeof navigation.errorText === "string" && navigation.errorText.length > 0) {
|
|
1518
|
+
throw new Error(`Page.navigate failed: ${navigation.errorText} at ${url}`);
|
|
1519
|
+
}
|
|
1520
|
+
await this.waitForDocumentReady(
|
|
1521
|
+
startedAt + timeout,
|
|
1522
|
+
() => expectedEventSeen,
|
|
1523
|
+
waitUntil,
|
|
1524
|
+
);
|
|
996
1525
|
} finally {
|
|
997
1526
|
unsubscribe();
|
|
998
1527
|
}
|
|
@@ -1003,10 +1532,27 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1003
1532
|
return await this.evaluateWithContext<T>(fn);
|
|
1004
1533
|
}
|
|
1005
1534
|
|
|
1535
|
+
async userAgent(): Promise<string> {
|
|
1536
|
+
return await this.evaluate<string>("navigator.userAgent");
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1006
1539
|
async evaluateInFrame<T>(frameId: string, fn: string | (() => T)): Promise<T> {
|
|
1007
1540
|
await this.initialize();
|
|
1008
1541
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
1009
|
-
|
|
1542
|
+
try {
|
|
1543
|
+
return await this.evaluateWithContext<T>(fn, contextId);
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
if (!isMissingExecutionContextError(error)) {
|
|
1546
|
+
throw error;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (this.frameExecutionContexts.get(frameId) === contextId) {
|
|
1550
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const refreshedContextId = await this.getFrameExecutionContextId(frameId);
|
|
1554
|
+
return await this.evaluateWithContext<T>(fn, refreshedContextId);
|
|
1555
|
+
}
|
|
1010
1556
|
}
|
|
1011
1557
|
|
|
1012
1558
|
async waitForSelectorInFrame(
|
|
@@ -1149,6 +1695,12 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1149
1695
|
return Buffer.from(String(result.data ?? ""), "base64");
|
|
1150
1696
|
}
|
|
1151
1697
|
|
|
1698
|
+
async cookies(): Promise<readonly BrowserCookie[]> {
|
|
1699
|
+
await this.initialize();
|
|
1700
|
+
const result = await this.pageClient.send("Network.getCookies");
|
|
1701
|
+
return parseCdpCookies(result.cookies);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1152
1704
|
async close(): Promise<void> {
|
|
1153
1705
|
if (this.closed) {
|
|
1154
1706
|
return;
|
|
@@ -1176,7 +1728,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1176
1728
|
|
|
1177
1729
|
try {
|
|
1178
1730
|
await this.pageClient.send("Fetch.enable", {
|
|
1179
|
-
patterns:
|
|
1731
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
1180
1732
|
});
|
|
1181
1733
|
} catch (error) {
|
|
1182
1734
|
unsubscribe();
|
|
@@ -1209,6 +1761,28 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1209
1761
|
}
|
|
1210
1762
|
|
|
1211
1763
|
try {
|
|
1764
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
1765
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
1766
|
+
if (!response) throw new Error("CDP document response metadata is unavailable");
|
|
1767
|
+
if (!response.hasBody) {
|
|
1768
|
+
await this.pageClient.send("Fetch.continueResponse", {
|
|
1769
|
+
requestId,
|
|
1770
|
+
responseCode: response.responseCode,
|
|
1771
|
+
responseHeaders: [...response.responseHeaders],
|
|
1772
|
+
});
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
const body = await this.pageClient.send("Fetch.getResponseBody", {
|
|
1777
|
+
requestId: response.requestId,
|
|
1778
|
+
});
|
|
1779
|
+
await this.pageClient.send(
|
|
1780
|
+
"Fetch.fulfillRequest",
|
|
1781
|
+
toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy),
|
|
1782
|
+
);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1212
1786
|
const parsed = toCdpResourceRequest(params);
|
|
1213
1787
|
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
1214
1788
|
await this.failCdpResourceRequest(requestId);
|
|
@@ -1222,6 +1796,11 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1222
1796
|
|
|
1223
1797
|
const decision = await resourceRoute.handle(parsed.request);
|
|
1224
1798
|
switch (decision.action) {
|
|
1799
|
+
case "continue":
|
|
1800
|
+
await this.pageClient.send("Fetch.continueRequest", {
|
|
1801
|
+
requestId: parsed.requestId,
|
|
1802
|
+
});
|
|
1803
|
+
return;
|
|
1225
1804
|
case "fulfill":
|
|
1226
1805
|
await this.pageClient.send(
|
|
1227
1806
|
"Fetch.fulfillRequest",
|
|
@@ -1264,6 +1843,21 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1264
1843
|
this.frameExecutionContexts.set(context.frameId, context.id);
|
|
1265
1844
|
}
|
|
1266
1845
|
});
|
|
1846
|
+
this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
|
|
1847
|
+
const destroyedContextId = getCdpDestroyedExecutionContextId(params);
|
|
1848
|
+
if (destroyedContextId === undefined) {
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
for (const [frameId, contextId] of this.frameExecutionContexts) {
|
|
1853
|
+
if (contextId === destroyedContextId) {
|
|
1854
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
this.pageClient.on("Runtime.executionContextsCleared", () => {
|
|
1859
|
+
this.frameExecutionContexts.clear();
|
|
1860
|
+
});
|
|
1267
1861
|
await this.pageClient.send("Page.enable");
|
|
1268
1862
|
await this.pageClient.send("Runtime.enable");
|
|
1269
1863
|
this.initialized = true;
|
|
@@ -1291,12 +1885,16 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1291
1885
|
|
|
1292
1886
|
private async waitForDocumentReady(
|
|
1293
1887
|
deadline: number,
|
|
1294
|
-
|
|
1888
|
+
isExpectedEventSeen: () => boolean,
|
|
1889
|
+
waitUntil: "load" | "domcontentloaded",
|
|
1295
1890
|
): Promise<void> {
|
|
1296
1891
|
while (Date.now() < deadline) {
|
|
1297
1892
|
const readyState = await this.evaluate<string>("document.readyState");
|
|
1298
|
-
|
|
1299
|
-
|
|
1893
|
+
const documentReady =
|
|
1894
|
+
readyState === "complete" ||
|
|
1895
|
+
(waitUntil === "domcontentloaded" && readyState === "interactive");
|
|
1896
|
+
if (documentReady) {
|
|
1897
|
+
if (isExpectedEventSeen() || readyState === "complete") {
|
|
1300
1898
|
return;
|
|
1301
1899
|
}
|
|
1302
1900
|
}
|
|
@@ -1335,7 +1933,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
|
|
|
1335
1933
|
...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
|
|
1336
1934
|
}),
|
|
1337
1935
|
);
|
|
1338
|
-
const pageClient = new
|
|
1936
|
+
const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
|
|
1339
1937
|
const page = new CdpPoolBrowserPage(
|
|
1340
1938
|
acquireResult.pageId,
|
|
1341
1939
|
acquireResult.browserContextId,
|