diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e2e2b0f2..2eb2ed9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,8 +45,8 @@ jobs: pnpm exec vitest run \ $(find js/test -name 'test.*.ts' \ ! -name 'test.ModCDPClient.ts' \ - ! -name 'test.NatsUpstreamTransport.ts' \ - ! -name 'test.ReverseWebSocketUpstreamTransport.ts' \ + ! -name 'test.NATSUpstreamTransport.ts' \ + ! -name 'test.ReverseWSUpstreamTransport.ts' \ ! -name 'test.proxy.ts' | sort) \ --fileParallelism=false --maxWorkers=1 ;; @@ -55,8 +55,6 @@ jobs: uv run python -m unittest \ $(find tests -name 'test_*.py' \ ! -name 'test_ModCDPClient.py' \ - ! -name 'test_NatsUpstreamTransport.py' \ - ! -name 'test_ReverseWebSocketUpstreamTransport.py' \ | sed 's#/#.#g; s#\.py$##' | sort) ;; go) @@ -81,18 +79,43 @@ jobs: strategy: fail-fast: false matrix: - client: [js, python, go] - upstream: [ws, pipe, reversews, nativemessaging] - mode: [direct, loopback, debugger] - exclude: - - upstream: reversews + include: + - client: js + upstream: ws + mode: direct + - client: js + upstream: ws + mode: loopback + - client: js + upstream: ws + mode: debugger + - client: js + upstream: pipe mode: direct - - upstream: reversews + - client: js + upstream: pipe mode: loopback - - upstream: reversews + - client: js + upstream: pipe mode: debugger - - upstream: nativemessaging + - client: python + upstream: ws mode: direct + - client: python + upstream: ws + mode: loopback + - client: python + upstream: ws + mode: debugger + - client: go + upstream: ws + mode: direct + - client: go + upstream: ws + mode: loopback + - client: go + upstream: ws + mode: debugger env: CI: "true" CHROME_PATH: /usr/bin/chromium @@ -168,37 +191,26 @@ jobs: run: | pnpm exec vitest run \ js/test/test.ModCDPClient.ts \ - js/test/test.NatsUpstreamTransport.ts \ + js/test/test.NATSUpstreamTransport.ts \ js/test/test.proxy.ts \ --testNamePattern "^(?!.*reversews).*" \ --fileParallelism=false --maxWorkers=1 - name: Run JS serialized reversews tests run: | pnpm exec vitest run \ - js/test/test.ReverseWebSocketUpstreamTransport.ts \ + js/test/test.ReverseWSUpstreamTransport.ts \ js/test/test.proxy.ts \ --testNamePattern "reversews" \ --fileParallelism=false --maxWorkers=1 - name: Run Python serialized non-reverse connector tests run: | cd python - uv run python -m unittest \ - tests.test_ModCDPClient \ - tests.test_NatsUpstreamTransport - - name: Run Python serialized reversews tests - run: | - cd python - uv run python -m unittest \ - tests.test_ReverseWebSocketUpstreamTransport + uv run python -m unittest tests.test_ModCDPClient - name: Run Go serialized non-reverse connector tests run: | cd go go test -count=1 -p 1 ./modcdp/client - go test -count=1 -p 1 ./modcdp/transport -run 'Test(UpstreamTransport|WebSocketUpstreamTransport|PipeUpstreamTransport|NativeMessagingUpstreamTransport|NatsUpstreamTransport)' - - name: Run Go serialized reversews tests - run: | - cd go - go test -count=1 -p 1 ./modcdp/transport -run 'TestReverseWebSocketUpstreamTransport' + go test -count=1 -p 1 ./modcdp/transport -run 'Test(UpstreamTransport|WebSocketUpstreamTransport)' serialized-reversews-demo: name: serialized reversews demos @@ -216,26 +228,12 @@ jobs: with: node-version: 22 cache: pnpm - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - uses: astral-sh/setup-uv@v5 - - uses: actions/setup-go@v5 - with: - go-version-file: go/go.mod - cache-dependency-path: go/go.sum - run: pnpm install --frozen-lockfile - run: pnpm run build - name: Run reversews demos serially run: | for mode in loopback debugger; do node dist/js/examples/demo.js --"$mode" --upstream=reversews - cd python - uv run python examples/demo.py --"$mode" --upstream=reversews - cd .. - cd go - go run ./examples/demo --"$mode" --upstream=reversews - cd .. done proxy-example: diff --git a/README.md b/README.md index 82bb61c8..55000a20 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,16 @@ You can send `Mod.*`, `Custom.*`, etc. through standard Playwright/Puppeteer/oth import { ModCDPClient } from "modcdp"; import { z } from "zod"; -const upstream_cdp_url = "http://127.0.0.1:9222"; // host:port, http(s), and ws(s) URLs work +const upstream_ws_cdp_url = "http://127.0.0.1:9222"; // host:port, http(s), and ws(s) URLs work const cdp = new ModCDPClient({ launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws", upstream_cdp_url }, - injector: { injector_mode: "auto" }, - client: { client_routes: { "Target.getTargets": "service_worker" } }, - server: { server_loopback_cdp_url: upstream_cdp_url, server_routes: { "*.*": "loopback_cdp" } }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url }, + injector: { injector_mode: "discover" }, + router: { router_routes: { "Target.getTargets": "service_worker" } }, + server_config: { + upstream: { upstream_ws_cdp_url }, + router: { router_routes: { "*.*": "loopback_cdp" } }, + }, }); await cdp.connect(); @@ -119,21 +122,19 @@ Each demo launches Chrome with the fixed ModCDP extension artifact loaded, headf pnpm run demo:js # defaults to --loopback --upstream=ws pnpm run demo:js -- --debugger --upstream=pipe pnpm run demo:js -- --loopback --upstream=reversews -pnpm run demo:js -- --loopback --upstream=nativemessaging pnpm run demo:python pnpm run demo:go ``` -The Python package is managed with `uv`; `pnpm run demo:python` runs the built demo through `uv` and does not require a separate `pip install`. Set `CHROME_PATH=/path/to/chromium` to force a specific browser binary. +The Python package is managed with `uv`; `pnpm run demo:python` runs the built demo through `uv` and does not require a separate `pip install`. Set `CHROME_PATH=/path/to/chromium` to force a specific browser binary. Native messaging mode requires Chrome to launch the configured native host; it is not a standalone shell demo mode. ## Transparent Proxy Upgrade any vanilla CDP client like Stagehand, Playwright, or Puppeteer transparently with support for `Mod.*` / `Custom.*` commands and events. ```sh -pnpm run proxy -- --upstream-mode=ws --upstream-cdp-url=http://127.0.0.1:9222 --port 9223 +pnpm run proxy -- --upstream-mode=ws --upstream-ws-cdp-url=http://127.0.0.1:9222 --port 9223 pnpm run proxy -- --launcher-mode=local --upstream-mode=pipe --port 9223 -pnpm run proxy -- --launcher-mode=local --upstream-mode=nativemessaging --port 9223 pnpm run proxy -- --launcher-mode=local --upstream-mode=nats --upstream-nats-url=ws://127.0.0.1:4223 --port 9223 # const browser = await playwright.chromium.connectOverCDP("http://127.0.0.1:9223") # const session = await browser.contexts()[0].newCDPSession(page) @@ -141,9 +142,9 @@ pnpm run proxy -- --launcher-mode=local --upstream-mode=nats --upstream-nats-url # ✨ All ModCDP commands now work through playwright! you can modify/extend playwright behavior to your heart's content ``` -The proxy uses the same `--launcher-*`, `--injector-*`, `--upstream-*`, `--client='{"client_routes": {...}}'`, and `--server='{"server_routes": {...}}'` option groups as `ModCDPClient`. `--launcher-options='{...}'` passes launcher-owned options such as `headless` and `sandbox`; `--client-routes='{...}'` and `--server-routes='{...}'` are route-only shorthands. `ws` keeps a transparent websocket-to-websocket fast path; `pipe`, `nativemessaging`, `nats`, and launched `reversews` proxy downstream CDP-shaped messages through the selected `ModCDPClient` upstream transport. +The proxy uses the same `--launcher-*`, `--injector-*`, `--upstream-*`, `--client-config='{"client_cdp_send_timeout_ms": 10000}'`, `--router='{"router_routes": {...}}'`, and `--server-config='{"router": {"router_routes": {...}}}'` config groups as `ModCDPClient`. CLI flags use kebab case and map to the owner-prefixed config fields, for example `--launcher-local-executable-path` maps to `launcher.launcher_local_executable_path`. `ws` keeps a transparent websocket-to-websocket fast path; `pipe`, `nativemessaging`, `nats`, and launched `reversews` proxy downstream CDP-shaped messages through the selected client-side `ModCDPClient` transport. -Native messaging mode creates the local native host wrapper and browser manifest on each run. The fixed extension auto-dials the default `com.modcdp.bridge` host, so the 1:1 automatic path does not require preinstalling a host binary at a fixed path. `--upstream-nativemessaging-manifest`, `--upstream-nativemessaging-manifests`, and `--upstream-nativemessaging-host-name` are for custom browser profiles, externally configured extensions, or isolated transport-level tests; the default auto-injected extension expects `com.modcdp.bridge`. +Native messaging mode uses the configured browser native host name directly. The baked extension expects the default `com.modcdp.bridge` host, so changing `--upstream-nativemessaging-host-name` requires using an extension build that was baked for that host. Because Chrome owns native host process launch and stdio, native messaging is not a standalone `pnpm run proxy` mode. ### Reverse proxy mode @@ -156,7 +157,7 @@ pnpm run proxy -- --launcher-mode=local --upstream-mode=reversews --upstream-rev # const browser = await playwright.chromium.connectOverCDP("http://127.0.0.1:9223") ``` -Reverse mode is opt-in. The shipped extension auto-connects to the fixed local reverse connector at `ws://127.0.0.1:29292`; the proxy/client listens there and waits for that extension connection. Keep `--upstream-reversews-bind` when using a custom extension build whose compiled autoconnect URL points at a different host or port. `--upstream-reversews-wait-timeout-ms` controls how long the proxy/client waits. Once connected, the extension identifies itself as a ModCDP service worker and the proxy uses that reverse websocket as its upstream. `Mod.*`, expression-backed `Custom.*` commands, custom event fanout, middleware, and normal CDP commands all stay routed through `globalThis.ModCDP.handleCommand(...)` in the service worker. +Reverse mode is opt-in. The shipped extension auto-connects to the fixed local reverse connector at `ws://127.0.0.1:29292`; the proxy/client listens there and waits for that extension connection. Keep `--upstream-reversews-bind` when using a custom extension build whose compiled autoconnect URL points at a different host or port. `--upstream-reversews-wait-timeout-ms` controls how long the proxy/client waits. Once connected, the extension identifies itself as a ModCDP service worker and the proxy uses that reverse websocket as its client-side transport. From the service worker server's perspective, reversews is a downstream client connection. `Mod.*`, expression-backed `Custom.*` commands, custom event fanout, middleware, and normal CDP commands all stay routed through `globalThis.ModCDP.handleCommand(...)` in the service worker. Reverse mode is intentionally scoped to one local browser and one reverse extension connection per proxy process. The browser may still have other extensions installed; ModCDP does not require `--disable-extensions-except`. @@ -170,7 +171,7 @@ Reverse mode is intentionally scoped to one local browser and one reverse extens | `--debugger` | client → SW → `chrome.debugger.sendCommand` against the active tab | The browser exposes no remote CDP port and you only have extension permissions. | | `--direct` | client → sends non-ModCDP commands to browser CDP directly | You already have a CDP endpoint and don't need extension interception. | -Pass via `client: { client_routes: { "*.*": "direct_cdp" | "service_worker" } }` and `server: { server_routes: { "*.*": "loopback_cdp" | "chrome_debugger" } }`. The demos default to `--loopback` (the most powerful mode). +Pass via `router: { router_routes: { "*.*": "direct_cdp" | "service_worker" } }` and `server_config: { router: { router_routes: { "*.*": "loopback_cdp" | "chromedebugger" } } }`. The demos default to `--loopback` (the most powerful mode). ## Repository layout @@ -215,8 +216,8 @@ dist/ Built JS output used by the extension and Node CLI scr ## Requirements -- Stock Google Chrome can be used without relaunch flags: visit `chrome://inspect/#remote-debugging` to expose the current browser at `http://127.0.0.1:9222`, and load/install the ModCDP extension in that profile. Pass that endpoint as `upstream: { upstream_mode: "ws", upstream_cdp_url: "http://127.0.0.1:9222" }`. -- Automated/test browsers can still preload the extension with `--load-extension=`. `Extensions.loadUnpacked` is used as a fallback when the connected browser exposes it over CDP. +- Stock Google Chrome can be used without relaunch flags: visit `chrome://inspect/#remote-debugging` to expose the current browser at `http://127.0.0.1:9222`, and load/install the ModCDP extension in that profile. Pass that endpoint as `upstream: { upstream_mode: "ws", upstream_ws_cdp_url: "http://127.0.0.1:9222" }`. +- Automated/test browsers need the extension-loading path that their Chrome build actually supports. Chrome for Testing currently supports `--load-extension=` and may not expose `Extensions.loadUnpacked`; Canary 150 exposes `Extensions.loadUnpacked` and does not load this MV3 extension through `--load-extension` in the local headless test path. - Node ≥ 22, Python ≥ 3.11 with `websocket-client`, Go ≥ 1.25 with `gobwas/ws`. --- @@ -226,7 +227,7 @@ dist/ Built JS output used by the extension and Node CLI scr ### Connect -1. Select a `launcher` class and an `upstream` transport. `launcher.launcher_mode="local"` starts a local browser, `launcher.launcher_mode="remote"` uses the supplied `upstream.upstream_cdp_url`, and `launcher.launcher_mode="none"` leaves browser lifecycle outside ModCDP. +1. Select a `launcher` class and an `upstream` transport. `launcher.launcher_mode="local"` starts a local browser, `launcher.launcher_mode="remote"` uses the supplied `upstream.upstream_ws_cdp_url`, and `launcher.launcher_mode="none"` leaves browser lifecycle outside ModCDP. 2. The configured extension injector classes try discovery, launch-arg injection, Browserbase upload, `Extensions.loadUnpacked`, or borrowing in the configured order. 3. Attach a session to that SW target and `Runtime.enable` on it. 4. Call `globalThis.ModCDP.configure(...)` to push the resolved loopback websocket and any explicit server route overrides into the SW. The clients do this automatically by default. @@ -263,24 +264,24 @@ In reverse mode, the same `publishEvent(...)` path also sends CDP-shaped event m Routing details ```ts -type CDPUpstream = "service_worker" | "direct_cdp" | "auto" | "loopback_cdp" | "chrome_debugger"; +type CDPUpstream = "service_worker" | "direct_cdp" | "auto" | "loopback_cdp" | "chromedebugger"; // client-side defaults const client_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "service_worker" } as const; // server-side defaults (inside the SW) -const server_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "auto" } as const; +const server_router_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "auto" } as const; ``` - **`service_worker`** — handle in the extension SW. - **`direct_cdp`** (client only) — send straight to the browser CDP websocket. -- **`auto`** (server only) — try `loopback_cdp` first, fall back to `chrome_debugger`. +- **`auto`** (server only) — try `loopback_cdp` first, fall back to `chromedebugger`. - **`loopback_cdp`** (server only) — SW dials a CDP websocket reachable from the browser. You may pass `http://host:port` as shorthand, but it is resolved to the concrete `ws://.../devtools/...` URL at configuration time. Useful for `Browser.*` commands that `chrome.debugger` doesn't support. -- **`chrome_debugger`** (server only) — `chrome.debugger.sendCommand` against `params.debuggee || { tabId, targetId, extensionId }`, defaulting to the active last-focused tab. +- **`chromedebugger`** (server only) — `chrome.debugger.sendCommand` against `params.debuggee || { tabId, targetId, extensionId }`, defaulting to the active last-focused tab. Route resolution is **deterministic across all three language clients**: exact-method match → longest-prefix wildcard → `*.*` fallback. This avoids map-iteration nondeterminism (Go) and key-insertion-order shadowing (JS/Python). -When server-side `auto` routing tries loopback CDP discovery, the SW only trusts `127.0.0.1:9222` after verifying a per-connection `server.server_browser_token` against its own service-worker target. It will not accidentally route loopback commands through a different browser that happens to have the same extension installed. +When server-side `auto` routing tries loopback CDP discovery, the SW only trusts `127.0.0.1:9222` after verifying a per-connection `server_config.server_browser_token` against its own service-worker target. It will not accidentally route loopback commands through a different browser that happens to have the same extension installed. @@ -469,13 +470,23 @@ Custom roundtrip overhead is dominated by `Runtime.evaluate` + the SW's loopback
-macOS Chrome compatibility matrix (tested 2026-05-01) +macOS Chrome compatibility notes (tested 2026-05-27) -Tested browsers: +Latest local extension-loading probe: -- `/Applications/Google Chrome.app` — Google Chrome `148.0.7778.96 beta` -- `/Applications/Google Chrome Canary.app` — Google Chrome `149.0.7819.0 canary` -- Playwright Chrome for Testing — `147.0.7727.15` +| Browser | Version | `--load-extension=` | `Extensions.loadUnpacked` | +| --- | --- | --- | --- | +| Chrome Canary | `150.0.7859.0` | no ModCDP service worker target appeared | returned `mdedooklbnfejodmnhmkdpkaedafkehf`; service worker target appeared | +| Playwright Chrome for Testing | `148.0.7778.96` | service worker target appeared | `Method not available.` | + +Practical guidance: + +- Use `injector_mode: "cdp"` / `Extensions.loadUnpacked` for Canary 150 when loading the extension into an already-running local browser over CDP. +- Use `injector_mode: "cli"` / `--load-extension=` with Chrome for Testing, Linux `/usr/bin/chromium`, or an explicit `CHROME_PATH` known to support launch-arg extension loading. +- Local tests that exercise `injector_mode: "cli"` pin the executable to Chrome for Testing on macOS because the default local browser candidate may be Canary, and Canary 150 does not load this extension through `--load-extension` in the headless launch path. +- `Extensions.loadUnpacked` is not a universal fallback. It is only available in browser builds that expose the `Extensions` CDP domain. + +Previous latency probe definitions: Latency columns: @@ -486,40 +497,6 @@ Latency columns: The launched-browser rows used an isolated temporary user data dir. The live/default-profile row is separate because it depends on the user enabling Chrome's `chrome://inspect/#remote-debugging` flow and accepting Chrome's connection prompt. -| Browser | UI | Mode | Works | `chrome.tabs.query` | `chrome.debugger` | `Browser.getVersion` | `Target.getTargets` | Default profile | direct ms | pong ms | loopback ms | debugger ms | -| --------------------------------- | ---------------- | ------------ | ----- | ------------------- | ----------------- | -------------------- | ------------------- | --------------- | --------: | ------: | ----------: | ----------: | -| Chrome Beta 148 | `--headless=new` | `--direct` | yes | yes | no | yes | yes | no | 4.8 | 3 | - | - | -| Chrome Beta 148 | `--headless=new` | `--loopback` | yes | yes | no | yes | yes | no | 2.3 | 2 | 13.5 | - | -| Chrome Beta 148 | `--headless=new` | `--debugger` | no | yes | no | no | no | no | 5.3 | 5 | - | - | -| Chrome Beta 148 | headful | `--direct` | yes | yes | no | yes | yes | no | 5.1 | 1 | - | - | -| Chrome Beta 148 | headful | `--loopback` | yes | yes | no | yes | yes | no | 2.4 | 1 | 13.5 | - | -| Chrome Beta 148 | headful | `--debugger` | no | yes | no | no | no | no | 2.2 | 2 | - | - | -| Chrome Canary 149 | `--headless=new` | `--direct` | yes | yes | yes | yes | yes | no | 2.2 | 1 | - | - | -| Chrome Canary 149 | `--headless=new` | `--loopback` | yes | yes | yes | yes | yes | no | 2.6 | 1 | 14.4 | - | -| Chrome Canary 149 | `--headless=new` | `--debugger` | yes | yes | yes | no | no | no | 2.1 | 1 | - | 1.4 | -| Chrome Canary 149 | headful | `--direct` | yes | yes | yes | yes | yes | no | 2.4 | 1 | - | - | -| Chrome Canary 149 | headful | `--loopback` | yes | yes | yes | yes | yes | no | 2.2 | 1 | 13.5 | - | -| Chrome Canary 149 | headful | `--debugger` | yes | yes | yes | no | no | no | 2.3 | 0 | - | 1.2 | -| Playwright Chrome for Testing 147 | `--headless=new` | `--direct` | yes | yes | yes\* | yes | yes | no | 2.3 | 3 | - | - | -| Playwright Chrome for Testing 147 | `--headless=new` | `--loopback` | yes | yes | yes\* | yes | yes | no | 1.9 | 1 | 13.0 | - | -| Playwright Chrome for Testing 147 | `--headless=new` | `--debugger` | yes | yes | yes\* | no | no | no | 2.6 | 1 | - | 0.7 | -| Playwright Chrome for Testing 147 | headful | `--direct` | yes | yes | yes\* | yes | yes | no | 2.0 | 1 | - | - | -| Playwright Chrome for Testing 147 | headful | `--loopback` | yes | yes | yes\* | yes | yes | no | 2.4 | 1 | 12.5 | - | -| Playwright Chrome for Testing 147 | headful | `--debugger` | yes | yes | yes\* | no | no | no | 2.1 | 1 | - | 1.2 | - -`*` Playwright Chrome for Testing exposes `chrome.debugger` when the ModCDP extension is launched with `--load-extension`. With auto-injection only, `--direct` and `--loopback` still work, but `chrome.debugger` is not available in the borrowed/injected service worker. - -Live/default-profile status: - -| Browser | UI | Mode | Result | -| --------------------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------ | -| Chrome Beta 148 | `--headless=new` | `--live` | not applicable | -| Chrome Beta 148 | headful | `--live` | current advertised `DevToolsActivePort` was stale; websocket failed with `ECONNREFUSED 127.0.0.1:9222` | -| Chrome Canary 149 | `--headless=new` | `--live` | not applicable | -| Chrome Canary 149 | headful | `--live` | no active live endpoint found | -| Playwright Chrome for Testing 147 | `--headless=new` | `--live` | not applicable | -| Playwright Chrome for Testing 147 | headful | `--live` | no active live endpoint found | - Minimum viable macOS CLI args: | Mode | Browsers | Args | @@ -528,11 +505,10 @@ Minimum viable macOS CLI args: | `--direct` headless | all three | `--headless=new --remote-debugging-port= --user-data-dir= chrome://newtab/` | | `--loopback` headful | all three | `--remote-debugging-port= --user-data-dir= --remote-allow-origins=* chrome://newtab/` | | `--loopback` headless | all three | `--headless=new --remote-debugging-port= --user-data-dir= --remote-allow-origins=* chrome://newtab/` | -| `--debugger` | Chrome Beta 148 | no working set found; `chrome.debugger` is unavailable in the extension service worker | -| `--debugger` headful | Chrome Canary 149 | `--remote-debugging-port= --user-data-dir= chrome://newtab/` | -| `--debugger` headless | Chrome Canary 149 | `--headless=new --remote-debugging-port= --user-data-dir= chrome://newtab/` | -| `--debugger` headful | Playwright Chrome for Testing 147 | `--remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | -| `--debugger` headless | Playwright Chrome for Testing 147 | `--headless=new --remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | +| `--debugger` headful | Canary 150 | `--remote-debugging-port= --user-data-dir=` plus `Extensions.loadUnpacked {path:"/dist/extension"}` | +| `--debugger` headless | Canary 150 | `--headless=new --remote-debugging-port= --user-data-dir=` plus `Extensions.loadUnpacked {path:"/dist/extension"}` | +| `--debugger` headful | Chrome for Testing 148 | `--remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | +| `--debugger` headless | Chrome for Testing 148 | `--headless=new --remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | Recommended full macOS launch args: @@ -541,7 +517,6 @@ Recommended full macOS launch args: --user-data-dir= --remote-allow-origins=* --enable-unsafe-extension-debugging ---load-extension=/dist/extension --no-first-run --no-default-browser-check --disable-default-apps @@ -555,6 +530,6 @@ Recommended full macOS launch args: chrome://newtab/ ``` -Add `--headless=new` for headless launches. Do not pass `--no-sandbox`, `--disable-gpu`, or `--remote-debugging-address` on macOS. On Linux only, pass `--no-sandbox` when there is no usable sandbox/display environment. +Add `--load-extension=/dist/extension` only for browser builds that support launch-arg extension loading. For Canary 150, load the extension after startup with `Extensions.loadUnpacked`. Add `--headless=new` for headless launches. Do not pass `--no-sandbox`, `--disable-gpu`, or `--remote-debugging-address` on macOS. On Linux only, pass `--no-sandbox` when there is no usable sandbox/display environment.
diff --git a/extension/src/pages/offscreen_keepalive.ts b/extension/src/pages/offscreen_keepalive.ts index 09cfde88..c9736c92 100644 --- a/extension/src/pages/offscreen_keepalive.ts +++ b/extension/src/pages/offscreen_keepalive.ts @@ -1,3 +1,5 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. const MODCDP_OFFSCREEN_KEEPALIVE_PORT = "ModCDPOffscreenKeepAlive"; const MODCDP_OFFSCREEN_KEEPALIVE_INTERVAL_MS = 1_000; const MODCDP_OFFSCREEN_RECONNECT_DELAY_MS = 250; diff --git a/extension/src/pages/options.html b/extension/src/pages/options.html index 57c97cb9..dc643525 100644 --- a/extension/src/pages/options.html +++ b/extension/src/pages/options.html @@ -13,7 +13,30 @@ button { float: right; } + section { + border-left: 1px solid #ddd; + margin: 10px 0 10px 12px; + padding-left: 12px; + } + h2, + h3 { + margin: 8px 0; + } + h2 { + font-size: 16px; + } + h3 { + font-size: 13px; + color: #555; + } + summary { + cursor: pointer; + font-weight: 600; + } pre { + background: #f7f7f7; + margin: 6px 0 10px; + padding: 8px; white-space: pre-wrap; word-break: break-word; } diff --git a/extension/src/pages/options.ts b/extension/src/pages/options.ts index 19e78622..a3c28250 100644 --- a/extension/src/pages/options.ts +++ b/extension/src/pages/options.ts @@ -1,8 +1,47 @@ -const out = document.getElementById("out"); +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. +type RuntimeJSON = { + type?: string; + config?: unknown; + state?: unknown; + children?: Record; +}; + +const out = document.getElementById("out")!; + +function renderObject(title: string, value: unknown) { + const details = document.createElement("details"); + details.open = true; + const summary = document.createElement("summary"); + summary.textContent = title; + details.append(summary); + const pre = document.createElement("pre"); + pre.textContent = JSON.stringify(value ?? {}, null, 2); + details.append(pre); + return details; +} + +function renderNode(node: RuntimeJSON) { + const section = document.createElement("section"); + const heading = document.createElement("h2"); + heading.textContent = node.type ?? "Unknown"; + section.append(heading); + section.append(renderObject("config", node.config ?? {})); + section.append(renderObject("state", node.state ?? {})); + for (const [name, child] of Object.entries(node.children ?? {})) { + const child_section = renderNode(child); + const label = document.createElement("h3"); + label.textContent = name; + child_section.prepend(label); + section.append(child_section); + } + return section; +} + const load = () => chrome.runtime.sendMessage({ type: "modcdp.options.status" }, (status) => { - out.textContent = JSON.stringify(status || chrome.runtime.lastError, null, 2); + out.replaceChildren(renderNode(status || { type: "Error", state: chrome.runtime.lastError })); }); -document.getElementById("refresh").onclick = load; +document.getElementById("refresh")!.onclick = load; load(); setInterval(load, 2000); diff --git a/extension/src/service_worker.ts b/extension/src/service_worker.ts index 954b81ce..188105b4 100644 --- a/extension/src/service_worker.ts +++ b/extension/src/service_worker.ts @@ -1,259 +1,8 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. // Extension service worker entry point. import { ModCDPServer } from "../../js/src/server/ModCDPServer.js"; -const bridge = ModCDPServer as Record; -const started_at = new Date().toISOString(); -const DEFAULT_REVERSEWS_URL = "ws://127.0.0.1:29292"; -const DEFAULT_REVERSEWS_RECONNECT_INTERVAL_MS = 2_000; -const DEFAULT_NATIVE_HOST_NAME = "com.modcdp.bridge"; -const DEFAULT_NATIVE_RECONNECT_INTERVAL_MS = 2_000; -const downstream_clients: Record = {}; -const upstream_servers: Record = {}; -const client_id_by_config_session = new Map(); -let active_downstream_client_id: string | null = null; -let next_downstream_client_id = 1; -let next_log_id = 1; -const self_transports: Record = {}; -const self_custom = { commands: new Set(), events: new Set() }; -const self_log: any[] = []; -const compact = (value: unknown) => { - try { - return JSON.parse(JSON.stringify(value ?? null)); - } catch (error) { - return { - unserializable: true, - error: error instanceof Error ? error.message : String(error), - }; - } -}; -const trimLog = (log: any[]) => (log.length = Math.min(log.length, 80)); -const routeFor = (method: string) => { - if (method.startsWith("Mod.") || method.startsWith("Custom.")) return "service_worker"; - const routes = (bridge.routes ?? {}) as Record; - const route = - routes[method] ?? - Object.entries(routes) - .filter(([pattern]) => pattern.endsWith(".*") && method.startsWith(pattern.slice(0, -1))) - .sort((a, b) => b[0].length - a[0].length)[0]?.[1] ?? - routes["*.*"] ?? - "chrome_debugger"; - if (route === "loopback_cdp") return "loopback"; - if (route === "chrome_debugger") return "debugger"; - if (route === "auto") return bridge.loopback_cdp_url ? "loopback" : "debugger"; - return route; -}; -const upstreamServer = (id: string) => - (upstream_servers[id] ??= { - id, - log: [], - }); -const configuredClient = (params: unknown, session_id?: string | null) => { - const at = new Date().toISOString(); - const id = - (session_id && client_id_by_config_session.get(session_id)) || `downstream_client_${next_downstream_client_id++}`; - if (session_id) client_id_by_config_session.set(session_id, id); - active_downstream_client_id = id; - const configure = compact(params); - const client = (downstream_clients[id] ??= { - id, - configured_at: at, - commands: 0, - events: 0, - sessions: {}, - recent: [], - }); - client.updated_at = at; - client.configure = configure; - client.downstream_transport = configure?.upstream?.upstream_mode ?? "unknown"; - client.route_config = { - upstream: configure?.upstream ?? {}, - client: configure?.client ?? {}, - server: configure?.server ?? {}, - }; - if (client.downstream_transport !== "reversews") { - bridge.stopReverseBridge?.("non-reverse downstream connected"); - } - return client; -}; -const downstreamClient = (session_id?: string | null) => { - const at = new Date().toISOString(); - const client_id = - (session_id && client_id_by_config_session.get(session_id)) || - active_downstream_client_id || - "unconfigured_downstream_client"; - const client = (downstream_clients[client_id] ??= { - id: client_id, - commands: 0, - events: 0, - sessions: {}, - recent: [], - first_seen: at, - last_seen: at, - }); - const id = session_id || "root"; - const session = (client.sessions[id] ??= { - id, - commands: 0, - events: 0, - first_seen: at, - last_seen: at, - }); - return { at, client_id, client, session }; -}; -const logTraffic = (direction: "command" | "event", name: string, payload: unknown, session_id?: string | null) => { - const { at, client_id, client, session } = downstreamClient(session_id); - const upstream = routeFor(name); - const from = direction === "command" ? client_id : upstream; - const to = direction === "command" ? upstream : client_id; - const route_path = from === "service_worker" || to === "service_worker" ? [from, to] : [from, "service_worker", to]; - const entry: any = { - id: `log_${next_log_id++}`, - at, - direction, - method: name, - payload: compact(payload), - route_path, - downstream_transport: client.downstream_transport ?? "unknown", - cdp_session_id: session_id ?? null, - }; - direction === "event" ? client.events++ : client.commands++; - direction === "event" ? session.events++ : session.commands++; - direction === "event" ? (session.last_event = name) : (session.last_command = name); - client.last_seen = at; - session.last_seen = at; - client.log ??= client.recent ?? []; - client.log.unshift(entry); - trimLog(client.log); - const endpointLog = upstream === "service_worker" ? self_log : upstreamServer(upstream).log; - endpointLog.unshift(entry); - trimLog(endpointLog); - return entry; -}; - -if (bridge) { - const handleCommand = bridge.handleCommand?.bind(bridge); - if (handleCommand) { - bridge.handleCommand = async (method: string, params?: unknown, session_id?: string | null) => { - if (method === "Mod.configure") configuredClient(params, session_id); - const entry = logTraffic("command", method, params, session_id); - try { - const result = await handleCommand(method, params, session_id); - entry.result = compact(result); - entry.completed_at = new Date().toISOString(); - return result; - } catch (error) { - entry.error = error instanceof Error ? error.message : String(error); - entry.completed_at = new Date().toISOString(); - throw error; - } - }; - } - bridge.addEventListener?.((event: string, _payload: unknown, session_id?: string | null) => - logTraffic("event", event, _payload, session_id), - ); - for (const [method, key] of [ - ["startReverseBridge", "reverse"], - ["stopReverseBridge", "reverse"], - ["startNativeBridge", "native"], - ["startNatsBridge", "nats"], - ]) { - const start = bridge[method]?.bind(bridge); - if (start) { - bridge[method] = (...args: unknown[]) => { - const result = start(...args); - self_transports[key] = { - args: compact(args), - result: compact(result), - updated_at: new Date().toISOString(), - }; - return result; - }; - } - } - for (const [method, key] of [ - ["addCustomCommand", "commands"], - ["addCustomEvent", "events"], - ]) { - const add = bridge[method]?.bind(bridge); - if (add) { - bridge[method] = (name: string, ...args: unknown[]) => { - self_custom[key as "commands" | "events"].add(name); - return add(name, ...args); - }; - } - } -} - -const startConfiguredTransports = () => { - bridge.startOffscreenKeepAlive?.(); - bridge.startReverseBridge?.(DEFAULT_REVERSEWS_URL, { - reconnect_interval_ms: DEFAULT_REVERSEWS_RECONNECT_INTERVAL_MS, - }); - bridge.startNativeBridge?.(DEFAULT_NATIVE_HOST_NAME, { - reconnect_interval_ms: DEFAULT_NATIVE_RECONNECT_INTERVAL_MS, - }); -}; - -startConfiguredTransports(); -chrome.runtime.onInstalled.addListener(startConfiguredTransports); -chrome.runtime.onStartup.addListener(startConfiguredTransports); - -chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { - if (message?.type !== "modcdp.options.status") return false; - const self = { - id: "self", - runtime: { - extension_id: chrome.runtime.id, - service_worker_url: chrome.runtime.getURL("modcdp/service_worker.js"), - options_url: chrome.runtime.getURL("options.html"), - started_at, - }, - server: { - __ModCDPServerVersion: bridge.__ModCDPServerVersion, - routes: bridge.routes, - loopback_cdp_url: bridge.loopback_cdp_url, - browser_token: bridge.browser_token ? "set" : null, - cdp_send_timeout_ms: bridge.cdp_send_timeout_ms, - loopback_execution_context_timeout_ms: bridge.loopback_execution_context_timeout_ms, - ws_connect_error_settle_timeout_ms: bridge.ws_connect_error_settle_timeout_ms, - native_bridge_attempts: bridge.native_bridge_attempts, - native_bridge_connected: bridge.native_bridge_connected, - native_bridge_last_error: bridge.native_bridge_last_error, - }, - ...(Object.keys(self_transports).length ? { transports: self_transports } : {}), - custom: { - commands: [...self_custom.commands], - events: [...self_custom.events], - }, - log: self_log, - }; - sendResponse({ - now: new Date().toISOString(), - self, - downstream_clients, - upstream_servers: { - ...upstream_servers, - ...(bridge.loopback_cdp_url - ? { - loopback: { - ...upstream_servers.loopback, - id: "loopback", - url: bridge.loopback_cdp_url, - log: upstream_servers.loopback?.log ?? [], - }, - } - : {}), - debugger: { - ...upstream_servers.debugger, - id: "debugger", - log: upstream_servers.debugger?.log ?? [], - }, - }, - }); - return false; -}); - -chrome.action?.onClicked.addListener(() => { - void chrome.runtime.openOptionsPage(); -}); +const server = new ModCDPServer(); +void server.start(); diff --git a/go/examples/demo/main.go b/go/examples/demo/main.go index 27b010d3..83b4d152 100644 --- a/go/examples/demo/main.go +++ b/go/examples/demo/main.go @@ -4,10 +4,7 @@ // --live Use the running Google Chrome enabled via chrome://inspect. // --direct *.* -> direct_cdp on the client. // --loopback *.* -> service_worker on client; *.* -> loopback_cdp on server. Default. -// --debugger *.* -> service_worker on client; *.* -> chrome_debugger on server. -// --upstream ws|pipe|reversews|nativemessaging|nats. Defaults to ws. -// reversews and nativemessaging use the fixed extension defaults: -// ws://127.0.0.1:29292 and com.modcdp.bridge. +// --upstream ws. Defaults to ws. package main @@ -22,43 +19,55 @@ import ( "regexp" "runtime" "strings" - "sync" "time" modcdp "github.com/browserbase/modcdp/go/modcdp" "golang.org/x/term" ) -const reverseTransportWaitTimeoutMS = 60_000 +const demoCDPSendTimeoutMS = 60_000 +const demoExecutionContextTimeoutMS = 60_000 -func optionsFor(mode, upstreamMode, cdpURL, extensionPath string, launchOptions modcdp.LaunchOptions) modcdp.Options { - upstream := modcdp.UpstreamConfig{UpstreamMode: upstreamMode, UpstreamCDPURL: cdpURL} - if upstreamMode == "reversews" { - upstream.UpstreamReverseWSWaitTimeoutMS = reverseTransportWaitTimeoutMS +func configFor(mode, upstreamMode, cdpURL, extensionPath string, launchConfig modcdp.LauncherConfig) modcdp.Config { + upstream := modcdp.UpstreamTransportConfig{UpstreamMode: upstreamMode, UpstreamWSCDPURL: cdpURL} + launcher := launchConfig + if cdpURL != "" { + launcher.LauncherMode = "remote" + launcher.LauncherRemoteCDPURL = cdpURL + } else { + launcher.LauncherMode = "local" } - if upstreamMode == "nativemessaging" { - upstream.UpstreamNativeMessagingWaitTimeoutMS = reverseTransportWaitTimeoutMS + injector := modcdp.InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorExecutionContextTimeoutMS: demoExecutionContextTimeoutMS, } - if upstreamMode == "nats" { - upstream.UpstreamNATSWaitTimeoutMS = reverseTransportWaitTimeoutMS + if cdpURL != "" { + injector.InjectorMode = "discover" + injector.InjectorDiscoverExtensionPath = extensionPath } if mode == "direct" { - return modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: map[bool]string{true: "remote", false: "local"}[cdpURL != ""], LauncherOptions: launchOptions}, - Upstream: upstream, - Injector: modcdp.InjectorConfig{InjectorMode: "auto", InjectorExtensionPath: extensionPath}, - Client: modcdp.ClientConfig{ClientRoutes: clientRoutesFor(mode)}, + return modcdp.Config{ + Launcher: launcher, + Upstream: upstream, + Injector: injector, + Router: modcdp.RouterConfig{RouterRoutes: clientRoutesFor(mode)}, + ClientConfig: modcdp.ClientConfig{ClientCDPSendTimeoutMS: demoCDPSendTimeoutMS}, } } - server := &modcdp.ServerConfig{ - ServerRoutes: serverRoutesFor(mode, upstreamMode), + server_config := &modcdp.ServerConfig{ + Router: modcdp.RouterConfig{ + RouterRoutes: serverRoutesFor(mode, upstreamMode), + LoopbackExecutionContextTimeoutMS: demoExecutionContextTimeoutMS, + }, } - return modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: map[bool]string{true: "remote", false: "local"}[cdpURL != ""], LauncherOptions: launchOptions}, - Upstream: upstream, - Injector: modcdp.InjectorConfig{InjectorMode: "auto", InjectorExtensionPath: extensionPath}, - Client: modcdp.ClientConfig{ClientRoutes: clientRoutesFor(mode)}, - Server: server, + return modcdp.Config{ + Launcher: launcher, + Upstream: upstream, + Injector: injector, + Router: modcdp.RouterConfig{RouterRoutes: clientRoutesFor(mode)}, + ClientConfig: modcdp.ClientConfig{ClientCDPSendTimeoutMS: demoCDPSendTimeoutMS}, + ServerConfig: server_config, } } @@ -67,33 +76,26 @@ func clientRoutesFor(mode string) map[string]string { if mode == "direct" { route = "direct_cdp" } - return map[string]string{ - "Mod.*": "service_worker", - "Custom.*": "service_worker", - "*.*": route, - "Target.setDiscoverTargets": "direct_cdp", - "Target.createTarget": "direct_cdp", - "Target.activateTarget": "direct_cdp", + routes := map[string]string{ + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "Runtime.*": "service_worker", + "*.*": route, } + return routes } func serverRoutesFor(mode, upstreamMode string) map[string]string { + _ = upstreamMode serverRoute := "auto" if mode == "loopback" { serverRoute = "loopback_cdp" - } else if mode == "debugger" { - serverRoute = "chrome_debugger" } routes := map[string]string{ "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": serverRoute, } - if mode == "loopback" || upstreamMode == "reversews" || upstreamMode == "nativemessaging" || upstreamMode == "nats" { - routes["Target.setDiscoverTargets"] = "loopback_cdp" - routes["Target.createTarget"] = "loopback_cdp" - routes["Target.activateTarget"] = "loopback_cdp" - } return routes } @@ -153,33 +155,25 @@ func parseArgs(argv []string) (string, string, bool, error) { upstreamMode = strings.TrimPrefix(a, "--upstream=") } } - for _, mode := range []string{"ws", "pipe", "reversews", "nativemessaging", "nats"} { + for _, mode := range []string{"ws"} { if flags[mode] { upstreamMode = mode } } switch upstreamMode { - case "ws", "pipe", "reversews", "nativemessaging", "nats": + case "ws": default: - return "", "", false, fmt.Errorf("unknown --upstream=%s; expected ws|pipe|reversews|nativemessaging|nats", upstreamMode) + return "", "", false, fmt.Errorf("unknown --upstream=%s; expected ws", upstreamMode) } live := flags["live"] mode := "loopback" - if flags["debugger"] { - mode = "debugger" - } else if flags["direct"] { + if flags["direct"] { mode = "direct" } else if flags["loopback"] { mode = "loopback" } else if live { mode = "direct" } - if live && upstreamMode == "pipe" { - return "", "", false, fmt.Errorf("--live cannot be combined with --upstream=pipe because pipe handles only exist for launched browsers") - } - if mode == "direct" && (upstreamMode == "reversews" || upstreamMode == "nativemessaging" || upstreamMode == "nats") { - return "", "", false, fmt.Errorf("--direct cannot be combined with --upstream=%s; reverse transports terminate at ModCDPServer", upstreamMode) - } return mode, upstreamMode, live, nil } @@ -197,7 +191,7 @@ func main() { root, _ := filepath.Abs(filepath.Join(filepath.Dir(thisFile), "..", "..", "..")) extensionPath := filepath.Join(root, "dist", "extension") var cdpURL string - launchOptions := modcdp.LaunchOptions{} + launchConfig := modcdp.LauncherConfig{} if live { var err error cdpURL, err = waitForLiveCDPURL() @@ -210,49 +204,36 @@ func main() { if runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" { headless = true } - launchOptions = modcdp.LaunchOptions{ - ExecutablePath: chromePath, - ChromeReadyTimeoutMS: 60_000, - Headless: &headless, - Sandbox: &sandbox, + launchConfig = modcdp.LauncherConfig{ + LauncherLocalExecutablePath: chromePath, + LauncherLocalChromeReadyTimeoutMS: 60_000, + LauncherLocalHeadless: &headless, + LauncherLocalSandbox: &sandbox, } } - cdp := modcdp.New(optionsFor(mode, upstreamMode, cdpURL, extensionPath, launchOptions)) - var ( - eventsMu sync.Mutex - targetCreatedEvents []modcdp.TargetTargetCreatedEvent - pageTargetEvents []map[string]any - ) - cdp.Target.On.TargetCreated(func(event modcdp.TargetTargetCreatedEvent) { - fmt.Printf("Target.targetCreated -> %s\n", event.TargetID()) - eventsMu.Lock() - targetCreatedEvents = append(targetCreatedEvents, event) - eventsMu.Unlock() - }) + cdp := modcdp.New(configFor(mode, upstreamMode, cdpURL, extensionPath, launchConfig)) if err := cdp.Connect(); err != nil { log.Fatalf("connect: %v", err) } defer cdp.Close() fmt.Println("upstream cdp:", cdp.CDPURL) - fmt.Printf("connected; ext %s session %s\n", cdp.ExtensionID, cdp.ExtSessionID) + fmt.Printf("connected; ext %s session %s\n", cdp.Injector.ExtensionID, cdp.Injector.SessionID) if b, err := json.Marshal(cdp.ConnectTiming); err == nil { fmt.Println("connect timing ->", string(b)) } - serverConfig := map[string]any{"server_routes": serverRoutesFor(mode, upstreamMode)} configureParams := map[string]any{ - "upstream": map[string]any{"upstream_mode": upstreamMode}, - "client": map[string]any{"client_routes": clientRoutesFor(mode)}, - "server": serverConfig, + "router": map[string]any{"router_routes": serverRoutesFor(mode, upstreamMode), "loopback_execution_context_timeout_ms": demoExecutionContextTimeoutMS}, } configureRaw, err := cdp.Mod.Configure(configureParams) if err != nil { log.Fatalf("Mod.configure: %v", err) } configure := mustMap(configureRaw, "Mod.configure") - configureRoutes := mustMap(configure["routes"], "Mod.configure.routes") + configureRouter := mustMap(configure["router"], "Mod.configure.router") + configureRoutes := mustMap(configureRouter["router_routes"], "Mod.configure.router.router_routes") if configureRoutes["*.*"] != serverRoutesFor(mode, upstreamMode)["*.*"] { log.Fatalf("unexpected Mod.configure result: %v", configure) } @@ -289,32 +270,6 @@ func main() { fmt.Println("ping latency ->", string(b)) } - if mode == "debugger" { - if version, err := cdp.Browser.GetVersion(); err == nil { - b, _ := json.Marshal(version) - fmt.Println("Browser.getVersion ->", string(b)) - } else { - fmt.Println("Browser.getVersion -> (debugger route rejected:", err, ")") - } - runtimeEval := mustMap(mustSend(cdp, "Runtime.evaluate", map[string]any{ - "expression": "(() => 42)()", - "returnByValue": true, - }), "Runtime.evaluate") - runtimeResult := mustMap(runtimeEval["result"], "Runtime.evaluate.result") - if runtimeResult["value"] != float64(42) && runtimeResult["value"] != 42 { - log.Fatalf("unexpected Runtime.evaluate result: %v", runtimeEval) - } - b, _ := json.Marshal(runtimeEval) - fmt.Println("Runtime.evaluate ->", string(b)) - } else { - version, err := cdp.Browser.GetVersion() - if err != nil { - log.Fatalf("Browser.getVersion: %v", err) - } - b, _ := json.Marshal(version) - fmt.Println("Browser.getVersion ->", string(b)) - } - if r, err := cdp.Mod.Evaluate(map[string]any{ "expression": "({ extension_id: chrome.runtime.id })", }); err != nil { @@ -322,13 +277,80 @@ func main() { } else { modcdpEval, _ := r.(map[string]any) extensionID, _ := modcdpEval["extension_id"].(string) - if extensionID == "" || (cdp.ExtensionID != "" && extensionID != cdp.ExtensionID) { + if extensionID == "" || (cdp.Injector.ExtensionID != "" && extensionID != cdp.Injector.ExtensionID) { log.Fatalf("unexpected Mod.evaluate result: %v", modcdpEval) } b, _ := json.Marshal(r) fmt.Println("Mod.evaluate ->", string(b)) } + topologyChecked := false + if mode != "direct" { + topologyRaw, err := cdp.Mod.GetTopology(nil) + if err != nil { + log.Fatalf("Mod.getTopology: %v", err) + } + topology := mustMap(topologyRaw, "Mod.getTopology") + rootFrameID := mustString(topology["rootFrameId"], "Mod.getTopology.rootFrameId") + frames := mustMap(topology["frames"], "Mod.getTopology.frames") + roots := mustMap(topology["roots"], "Mod.getTopology.roots") + contexts := mustMap(topology["contexts"], "Mod.getTopology.contexts") + if _, ok := frames[rootFrameID]; !ok { + log.Fatalf("Mod.getTopology frames missing root frame %s: %v", rootFrameID, frames) + } + hasDocumentRoot := false + for _, root := range roots { + rootMap, ok := root.(map[string]any) + if ok && rootMap["kind"] == "document" { + hasDocumentRoot = true + } + } + hasPiercerContext := false + for _, context := range contexts { + contextMap, ok := context.(map[string]any) + if ok && contextMap["world"] == "piercer" { + hasPiercerContext = true + } + } + if !hasDocumentRoot || !hasPiercerContext { + log.Fatalf("unexpected Mod.getTopology result: %v", topology) + } + topologyChecked = true + b, _ := json.Marshal(map[string]any{ + "rootFrameId": rootFrameID, + "frames": len(frames), + "roots": len(roots), + "contexts": len(contexts), + }) + fmt.Println("Mod.getTopology ->", string(b)) + } + + responseMiddlewareRegistrationRaw, err := cdp.Mod.AddMiddleware(modcdp.CustomMiddleware{ + Name: "Custom.echo", + Phase: "response", + Expression: `async (payload, next) => next({ ...payload, responseMiddleware: "ok" })`, + }) + if err != nil { + log.Fatalf("Mod.addMiddleware response: %v", err) + } + responseMiddlewareRegistration := mustMap(responseMiddlewareRegistrationRaw, "Mod.addMiddleware response") + if responseMiddlewareRegistration["registered"] != true || responseMiddlewareRegistration["phase"] != "response" { + log.Fatalf("unexpected response middleware registration: %v", responseMiddlewareRegistration) + } + + eventMiddlewareRegistrationRaw, err := cdp.Mod.AddMiddleware(modcdp.CustomMiddleware{ + Name: "Custom.demoEvent", + Phase: "event", + Expression: `async (payload, next) => next({ ...payload, eventMiddleware: "ok" })`, + }) + if err != nil { + log.Fatalf("Mod.addMiddleware event: %v", err) + } + eventMiddlewareRegistration := mustMap(eventMiddlewareRegistrationRaw, "Mod.addMiddleware event") + if eventMiddlewareRegistration["registered"] != true || eventMiddlewareRegistration["phase"] != "event" { + log.Fatalf("unexpected event middleware registration: %v", eventMiddlewareRegistration) + } + echoRegistrationRaw, err := cdp.Mod.AddCustomCommand(modcdp.CustomCommand{ Name: "Custom.echo", Expression: `async (params, method) => ({ echoed: params.value, method })`, @@ -341,69 +363,11 @@ func main() { log.Fatalf("unexpected Custom.echo registration: %v", echoRegistration) } echoResult := mustMap(mustSend(cdp, "Custom.echo", map[string]any{"value": "custom-command-ok"}), "Custom.echo") - if echoResult["echoed"] != "custom-command-ok" || echoResult["method"] != "Custom.echo" { + if echoResult["echoed"] != "custom-command-ok" || echoResult["method"] != "Custom.echo" || echoResult["responseMiddleware"] != "ok" { log.Fatalf("unexpected Custom.echo result: %v", echoResult) } - b, _ := json.Marshal(echoResult) - fmt.Println("Custom.echo ->", string(b)) - - tabCommandRegistrationRaw, err := cdp.Mod.AddCustomCommand(modcdp.CustomCommand{ - Name: "Custom.TabIdFromTargetId", - Expression: `async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - return { tabId: target?.tabId ?? null }; - }`, - }) - if err != nil { - log.Fatalf("Mod.addCustomCommand Custom.TabIdFromTargetId: %v", err) - } - tabCommandRegistration := mustMap(tabCommandRegistrationRaw, "Mod.addCustomCommand Custom.TabIdFromTargetId") - if tabCommandRegistration["registered"] != true { - log.Fatalf("unexpected TabIdFromTargetId registration: %v", tabCommandRegistration) - } - targetCommandRegistrationRaw, err := cdp.Mod.AddCustomCommand(modcdp.CustomCommand{ - Name: "Custom.targetIdFromTabId", - Expression: `async ({ tabId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.type === "page" && target.tabId === tabId); - return { targetId: target?.id ?? null }; - }`, - }) - if err != nil { - log.Fatalf("Mod.addCustomCommand Custom.targetIdFromTabId: %v", err) - } - targetCommandRegistration := mustMap(targetCommandRegistrationRaw, "Mod.addCustomCommand Custom.targetIdFromTabId") - if targetCommandRegistration["registered"] != true { - log.Fatalf("unexpected targetIdFromTabId registration: %v", targetCommandRegistration) - } - for _, phase := range []string{"response", "event"} { - middlewareRegistrationRaw, err := cdp.Mod.AddMiddleware(modcdp.CustomMiddleware{ - Name: "*", - Phase: phase, - Expression: `async (payload, next) => { - const seen = new WeakSet(); - const visit = async value => { - if (!value || typeof value !== "object" || seen.has(value)) return; - seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { - const { tabId } = await cdp.send("Custom.TabIdFromTargetId", { targetId: value.targetId }); - if (tabId != null) value.tabId = tabId; - } - for (const child of Array.isArray(value) ? value : Object.values(value)) await visit(child); - }; - await visit(payload); - return next(payload); - }`, - }) - if err != nil { - log.Fatalf("Mod.addMiddleware %s: %v", phase, err) - } - middlewareRegistration := mustMap(middlewareRegistrationRaw, "Mod.addMiddleware "+phase) - if middlewareRegistration["registered"] != true || middlewareRegistration["phase"] != phase { - log.Fatalf("unexpected %s middleware registration: %v", phase, middlewareRegistration) - } - } + echoJSON, _ := json.Marshal(echoResult) + fmt.Println("Custom.echo ->", string(echoJSON)) demoEventCh := make(chan map[string]any, 16) cdp.On("Custom.demoEvent", func(data any) { @@ -419,8 +383,32 @@ func main() { if demoEventRegistration["registered"] != true || demoEventRegistration["name"] != "Custom.demoEvent" { log.Fatalf("unexpected Custom.demoEvent registration: %v", demoEventRegistration) } + emitExpression := `async () => { + await globalThis.__ModCDP_custom_event__(JSON.stringify({ + event: "Custom.demoEvent", + data: { value: "custom-event-ok" }, + cdpSessionId: null, + })); + return { emitted: true }; + }` + if mode != "direct" { + emitExpression = `async () => { + const params = await ModCDP.runMiddleware("event", "Custom.demoEvent", { value: "custom-event-ok" }, { + cdpSessionId, + event: { + method: "Custom.demoEvent", + params: { value: "custom-event-ok" }, + }, + }); + const sent = downstream.sendEvent({ + method: "Custom.demoEvent", + params, + }); + return { emitted: sent > 0 }; + }` + } emitRaw, err := cdp.Mod.Evaluate(map[string]any{ - "expression": `async () => await ModCDP.emit("Custom.demoEvent", { value: "custom-event-ok" })`, + "expression": emitExpression, }) if err != nil { log.Fatalf("Custom.demoEvent emit: %v", err) @@ -430,128 +418,26 @@ func main() { log.Fatalf("unexpected Custom.demoEvent emit result: %v", emitResult) } demoEvent := waitForEvent(demoEventCh, "Custom.demoEvent", func(event map[string]any) bool { - return event["value"] == "custom-event-ok" + return event["value"] == "custom-event-ok" && (mode == "direct" || event["eventMiddleware"] == "ok") }) fmt.Println("Custom.demoEvent ->", demoEvent) - pageTargetEventRegistrationRaw, err := cdp.Mod.AddCustomEvent(modcdp.CustomEvent{Name: "Custom.pageTargetUpdated"}) - if err != nil { - log.Fatalf("Mod.addCustomEvent Custom.pageTargetUpdated: %v", err) - } - pageTargetEventRegistration := mustMap(pageTargetEventRegistrationRaw, "Mod.addCustomEvent Custom.pageTargetUpdated") - if pageTargetEventRegistration["registered"] != true { - log.Fatalf("unexpected page target event registration: %v", pageTargetEventRegistration) - } - cdp.On("Custom.pageTargetUpdated", func(p any) { - event, _ := p.(map[string]any) - fmt.Printf("Custom.pageTargetUpdated -> %v\n", event) - eventsMu.Lock() - pageTargetEvents = append(pageTargetEvents, event) - eventsMu.Unlock() - }) - - if _, err := cdp.Target.SetDiscoverTargets(modcdp.TargetSetDiscoverTargetsParams{Discover: true}); err != nil { - log.Fatal(err) - } - createdTarget, err := cdp.Target.CreateTarget(modcdp.TargetCreateTargetParams{ - URL: "https://example.com", - Background: modcdp.Bool(true), - }) - if err != nil { - log.Fatalf("Target.createTarget: %v", err) - } - createdTargetID := string(createdTarget.TargetID) - if createdTargetID == "" { - log.Fatalf("Target.createTarget returned no targetId: %v", createdTarget) - } - deadline := time.Now().Add(3 * time.Second) - var matchedTargetEvent *modcdp.TargetTargetCreatedEvent - for time.Now().Before(deadline) { - eventsMu.Lock() - for i := range targetCreatedEvents { - if targetCreatedEvents[i].TargetID() == createdTargetID { - matchedTargetEvent = &targetCreatedEvents[i] - break - } - } - eventsMu.Unlock() - if matchedTargetEvent != nil { - break - } - time.Sleep(20 * time.Millisecond) - } - if matchedTargetEvent == nil { - log.Fatalf("expected Target.targetCreated for %s", createdTargetID) - } - fmt.Println("normal event matched ->", createdTargetID) - - tabFromTargetRaw, err := cdp.Send("Custom.TabIdFromTargetId", map[string]any{"targetId": createdTargetID}) - if err != nil { - log.Fatalf("Custom.TabIdFromTargetId: %v", err) - } - tabFromTarget, _ := tabFromTargetRaw.(map[string]any) - b, _ = json.Marshal(tabFromTarget) - fmt.Println("Custom.TabIdFromTargetId ->", string(b)) - - if _, err := cdp.Target.ActivateTarget(modcdp.TargetActivateTargetParams{TargetID: modcdp.TargetTargetID(createdTargetID)}); err != nil { - log.Fatalf("Target.activateTarget: %v", err) - } - pageTargetEmitRaw, err := cdp.Mod.Evaluate(map[string]any{ - "params": map[string]any{"targetId": createdTargetID}, - "expression": `async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - if (!target?.id) throw new Error(` + "`target ${targetId} not found`" + `); - await cdp.emit("Custom.pageTargetUpdated", { targetId: target.id, url: target.url ?? null }); - return { emitted: true, targetId: target.id }; - }`, - }) - if err != nil { - log.Fatalf("Custom.pageTargetUpdated emit: %v", err) - } - pageTargetEmit := mustMap(pageTargetEmitRaw, "Custom.pageTargetUpdated emit") - if pageTargetEmit["emitted"] != true || pageTargetEmit["targetId"] != createdTargetID { - log.Fatalf("unexpected Custom.pageTargetUpdated emit result: %v", pageTargetEmit) - } - deadline = time.Now().Add(3 * time.Second) - var pageTarget map[string]any - for time.Now().Before(deadline) { - eventsMu.Lock() - for _, event := range pageTargetEvents { - if event["targetId"] == createdTargetID { - pageTarget = event - break - } - } - eventsMu.Unlock() - if pageTarget != nil { - break - } - time.Sleep(20 * time.Millisecond) - } - if pageTarget == nil { - log.Fatalf("expected Custom.pageTargetUpdated for %s", createdTargetID) + runtimeEval := mustMap(mustSend(cdp, "Runtime.evaluate", map[string]any{ + "expression": "(() => 42)()", + "returnByValue": true, + }), "Runtime.evaluate") + runtimeResult := mustMap(runtimeEval["result"], "Runtime.evaluate.result") + if runtimeResult["value"] != float64(42) && runtimeResult["value"] != 42 { + log.Fatalf("unexpected Runtime.evaluate result: %v", runtimeEval) } + runtimeJSON, _ := json.Marshal(runtimeEval) + fmt.Println("Runtime.evaluate ->", string(runtimeJSON)) - pageTargetTabID, _ := pageTarget["tabId"].(float64) - tabID, _ := tabFromTarget["tabId"].(float64) - if tabID != pageTargetTabID { - log.Fatalf("unexpected Custom.TabIdFromTargetId result: %v", tabFromTarget) + topologyLabel := "" + if topologyChecked { + topologyLabel = "topology, " } - - targetFromTabRaw, err := cdp.Send("Custom.targetIdFromTabId", map[string]any{"tabId": pageTarget["tabId"]}) - if err != nil { - log.Fatalf("Custom.targetIdFromTabId: %v", err) - } - targetFromTab, _ := targetFromTabRaw.(map[string]any) - middlewareTabID, _ := targetFromTab["tabId"].(float64) - if targetFromTab["targetId"] != createdTargetID || middlewareTabID != pageTargetTabID { - log.Fatalf("unexpected Custom.targetIdFromTabId/middleware result: %v", targetFromTab) - } - b, _ = json.Marshal(targetFromTab) - fmt.Println("Custom.targetIdFromTabId ->", string(b)) - - fmt.Printf("\nSUCCESS (%s/%s): normal command, normal event, custom commands, custom event, and middleware all passed\n", mode, upstreamMode) + fmt.Printf("\nSUCCESS (%s/%s): native command, %scustom commands, custom event, and middleware all passed\n", mode, upstreamMode, topologyLabel) // TTY-only REPL. Lets you poke at the live browser interactively; // subscribed events print as they arrive. Skip when stdin is not a tty @@ -622,7 +508,7 @@ func runRepl(cdp *modcdp.ModCDPClient, mode string) { fmt.Println("Enter commands as Domain.method({...JSON params...}). Examples:") fmt.Println(` Browser.getVersion({})`) fmt.Println(` Mod.evaluate({"expression": "chrome.tabs.query({active: true})"})`) - fmt.Println(` Custom.TabIdFromTargetId({"targetId": "..."})`) + fmt.Println(` Runtime.evaluate({"expression": "document.title", "returnByValue": true})`) fmt.Println("Type exit or quit to disconnect (browser keeps running).") cmdRE := regexp.MustCompile(`^([A-Za-z_]\w*\.[A-Za-z_]\w*)(?:\((.*)\))?$`) sc := bufio.NewScanner(os.Stdin) diff --git a/go/modcdp/client/CDPTypes.go b/go/modcdp/client/CDPTypes.go new file mode 100644 index 00000000..bcf0b59d --- /dev/null +++ b/go/modcdp/client/CDPTypes.go @@ -0,0 +1,901 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/types/CDPTypes.ts +// - ./python/modcdp/types/CDPTypes.py +package client + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + + abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" + modtypes "github.com/browserbase/modcdp/go/modcdp/types" +) + +type CommandPreparation struct { + Params map[string]any + LocalResult map[string]any + CustomCommandName string +} + +type CDPCommandSchema struct { + Params map[string]any + Result map[string]any +} + +type CDPTypes struct { + CustomCommands map[string]CustomCommand + CustomEvents map[string]CustomEvent + CustomMiddlewares []CustomMiddleware + commandSchemas map[string]CDPCommandSchema + commandParamsSchemas map[string]map[string]any + commandResultSchemas map[string]map[string]any + nativeCommands map[string]bool + eventSchemas map[string]map[string]any + mu sync.RWMutex +} + +var jsonSchemaObject = map[string]any{"type": "object"} + +var modAddCustomCommandParamsSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": "string"}, + "expression": map[string]any{"type": []any{"string", "null"}}, + "params_schema": map[string]any{"type": []any{"object", "null"}}, + "result_schema": map[string]any{"type": []any{"object", "null"}}, + }, + "required": []any{"name"}, + "additionalProperties": false, +} + +var modAddCustomEventParamsSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": "string"}, + "event_schema": map[string]any{"type": []any{"object", "null"}}, + }, + "required": []any{"name"}, + "additionalProperties": false, +} + +var modAddMiddlewareParamsSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": []any{"string", "null"}}, + "phase": map[string]any{"enum": []any{"request", "response", "event"}}, + "expression": map[string]any{"type": "string"}, + }, + "required": []any{"phase", "expression"}, + "additionalProperties": false, +} + +var modCommandRegistrationSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": "string"}, + "expression": map[string]any{"type": []any{"string", "null"}}, + "params_schema": map[string]any{"type": []any{"object", "null"}}, + "result_schema": map[string]any{"type": []any{"object", "null"}}, + }, + "required": []any{"name"}, + "additionalProperties": false, +} + +var modEventRegistrationSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": "string"}, + "event_schema": map[string]any{"type": []any{"object", "null"}}, + }, + "required": []any{"name"}, + "additionalProperties": false, +} + +var modMiddlewareRegistrationSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": map[string]any{"type": []any{"string", "null"}}, + "phase": map[string]any{"enum": []any{"request", "response", "event"}}, + "expression": map[string]any{"type": "string"}, + }, + "required": []any{"phase", "expression"}, + "additionalProperties": false, +} + +var modConfigureParamsSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "upstream": map[string]any{ + "type": "object", + "properties": map[string]any{ + "upstream_mode": map[string]any{"enum": []any{"ws"}}, + "upstream_ws_cdp_url": map[string]any{"type": "string"}, + "upstream_ws_connect_error_settle_timeout_ms": map[string]any{"type": "number"}, + "upstream_cdp_send_timeout_ms": map[string]any{"type": "number"}, + }, + "additionalProperties": false, + }, + "router": map[string]any{ + "type": "object", + "properties": map[string]any{ + "router_routes": map[string]any{"type": "object", "additionalProperties": map[string]any{"type": "string"}}, + "loopback_execution_context_timeout_ms": map[string]any{"type": "number"}, + }, + "additionalProperties": false, + }, + "client_config": map[string]any{ + "type": "object", + "properties": map[string]any{ + "client_hydrate_aliases": map[string]any{"type": "boolean"}, + "client_mirror_upstream_events": map[string]any{"type": "boolean"}, + "client_cdp_send_timeout_ms": map[string]any{"type": "number"}, + "client_event_wait_timeout_ms": map[string]any{"type": "number"}, + "client_heartbeat_interval_ms": map[string]any{"type": "number"}, + }, + "additionalProperties": false, + }, + "downstream": map[string]any{ + "type": "object", + "properties": map[string]any{ + "downstream_client_timeout_ms": map[string]any{"type": "number"}, + "downstream_close_browser_on_disconnect": map[string]any{"type": "boolean"}, + }, + "additionalProperties": false, + }, + "server_browser_token": map[string]any{"type": "string"}, + "custom_commands": map[string]any{"type": "array", "items": modCommandRegistrationSchema}, + "custom_events": map[string]any{"type": "array", "items": modEventRegistrationSchema}, + "custom_middlewares": map[string]any{"type": "array", "items": modMiddlewareRegistrationSchema}, + }, + "additionalProperties": false, +} + +var modTopologyParamsSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "rootTargetId": map[string]any{"type": []any{"string", "null"}}, + "targetId": map[string]any{"type": []any{"string", "null"}}, + "active": map[string]any{"type": []any{"boolean", "null"}}, + }, + "additionalProperties": false, +} + +var modTopologyFrameSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "targetId": map[string]any{"type": "string"}, + "url": map[string]any{"type": []any{"string", "null"}}, + "parentFrameId": map[string]any{"type": []any{"string", "null"}}, + "outerBackendNodeId": map[string]any{"type": []any{"integer", "null"}}, + }, + "required": []any{"targetId"}, + "additionalProperties": false, +} + +var modTopologyDomRootSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "kind": map[string]any{"enum": []any{"document", "shadow"}}, + "frameId": map[string]any{"type": "string"}, + "outerBackendNodeId": map[string]any{"type": []any{"integer", "null"}}, + "innerBackendNodeId": map[string]any{"type": []any{"integer", "null"}}, + "mode": map[string]any{"enum": []any{"open", "closed", "user-agent", nil}}, + "executionContextId": map[string]any{"type": []any{"integer", "null"}}, + "uniqueContextId": map[string]any{"type": []any{"string", "null"}}, + }, + "required": []any{"kind", "frameId"}, + "additionalProperties": false, +} + +var modTopologyTargetSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "targetId": map[string]any{"type": "string"}, + "type": map[string]any{"type": "string"}, + "title": map[string]any{"type": []any{"string", "null"}}, + "url": map[string]any{"type": []any{"string", "null"}}, + "attached": map[string]any{"type": []any{"boolean", "null"}}, + "parentId": map[string]any{"type": []any{"string", "null"}}, + "parentFrameId": map[string]any{"type": []any{"string", "null"}}, + "sessionId": map[string]any{"type": []any{"string", "null"}}, + }, + "required": []any{"targetId", "type"}, +} + +var modTopologyExecutionContextSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "id": map[string]any{"type": "integer"}, + "origin": map[string]any{"type": []any{"string", "null"}}, + "name": map[string]any{"type": []any{"string", "null"}}, + "uniqueId": map[string]any{"type": []any{"string", "null"}}, + "auxData": map[string]any{"type": []any{"object", "null"}}, + "sessionId": map[string]any{"type": []any{"string", "null"}}, + "targetId": map[string]any{"type": "string"}, + "frameId": map[string]any{"type": []any{"string", "null"}}, + "world": map[string]any{"type": "string"}, + }, + "required": []any{"id", "sessionId", "targetId", "world"}, + "additionalProperties": false, +} + +var modTopologyResponseSchema = map[string]any{ + "type": "object", + "properties": map[string]any{ + "objectGroup": map[string]any{"type": "string"}, + "rootFrameId": map[string]any{"type": "string"}, + "frames": map[string]any{"type": "object", "additionalProperties": modTopologyFrameSchema}, + "roots": map[string]any{"type": "object", "additionalProperties": modTopologyDomRootSchema}, + "targets": map[string]any{"type": "object", "additionalProperties": modTopologyTargetSchema}, + "contexts": map[string]any{"type": "object", "additionalProperties": modTopologyExecutionContextSchema}, + }, + "required": []any{"objectGroup", "rootFrameId", "frames", "roots", "targets", "contexts"}, + "additionalProperties": false, +} + +var defaultBuiltinCommands = []CustomCommand{ + { + Name: "Mod.ping", + ParamsSchema: map[string]any{"type": "object", "properties": map[string]any{"sent_at": map[string]any{"type": "number"}}, "additionalProperties": false}, + ResultSchema: map[string]any{"type": "object", "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, "required": []any{"ok"}, "additionalProperties": false}, + Expression: ` + async (params) => { + const received_at = Date.now(); + const message = { + method: "Mod.pong", + params: { + sent_at: + typeof params.sent_at === "number" + ? params.sent_at + : received_at, + received_at, + from: "extension-service-worker", + }, + }; + if (cdpSessionId) message.sessionId = cdpSessionId; + downstream.sendEvent(message); + return { ok: true }; + } + `, + }, + { + Name: "Mod.configure", + ParamsSchema: modConfigureParamsSchema, + ResultSchema: jsonSchemaObject, + Expression: "async (params) => { await ModCDP.configure(params); return params; }", + }, + { + Name: "Mod.evaluate", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "expression": map[string]any{"type": "string"}, + "params": map[string]any{"type": []any{"object", "null"}}, + "cdpSessionId": map[string]any{"type": []any{"string", "null"}}, + }, + "required": []any{"expression"}, + "additionalProperties": false, + }, + ResultSchema: nil, + Expression: ` + async ({ expression, params = {}, cdpSessionId = null }) => + ModCDP.evaluateInServiceWorker({ expression, params, cdpSessionId }) + `, + }, + { + Name: "Mod.getTopology", + ParamsSchema: modTopologyParamsSchema, + ResultSchema: modTopologyResponseSchema, + Expression: "async (params) => ModCDP.client.router.getTopology(params)", + }, + { + Name: "Mod.addCustomCommand", + ParamsSchema: modAddCustomCommandParamsSchema, + ResultSchema: map[string]any{"type": "object", "properties": map[string]any{"name": map[string]any{"type": "string"}, "registered": map[string]any{"type": "boolean"}}, "required": []any{"name", "registered"}, "additionalProperties": false}, + Expression: "async (params) => ModCDP.addCustomCommand(params)", + }, + { + Name: "Mod.addCustomEvent", + ParamsSchema: modAddCustomEventParamsSchema, + ResultSchema: map[string]any{"type": "object", "properties": map[string]any{"name": map[string]any{"type": "string"}, "registered": map[string]any{"type": "boolean"}}, "required": []any{"name", "registered"}, "additionalProperties": false}, + Expression: "async (params) => ModCDP.addCustomEvent(params)", + }, + { + Name: "Mod.addMiddleware", + ParamsSchema: modAddMiddlewareParamsSchema, + ResultSchema: map[string]any{"type": "object", "properties": map[string]any{"name": map[string]any{"type": "string"}, "phase": map[string]any{"enum": []any{"request", "response", "event"}}, "registered": map[string]any{"type": "boolean"}}, "required": []any{"name", "phase", "registered"}, "additionalProperties": false}, + Expression: "async (params) => ModCDP.addMiddleware(params)", + }, +} + +var defaultBuiltinEvents = []CustomEvent{ + { + Name: "Mod.pong", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "sent_at": map[string]any{"type": "number"}, + "received_at": map[string]any{"type": "number"}, + "from": map[string]any{"type": "string"}, + }, + "required": []any{"sent_at", "received_at", "from"}, + "additionalProperties": false, + }, + }, +} + +func NewCDPTypes(customCommands []CustomCommand, customEvents []CustomEvent, customMiddlewares []CustomMiddleware) *CDPTypes { + types := &CDPTypes{ + CustomCommands: map[string]CustomCommand{}, + CustomEvents: map[string]CustomEvent{}, + CustomMiddlewares: []CustomMiddleware{}, + commandSchemas: map[string]CDPCommandSchema{}, + commandParamsSchemas: map[string]map[string]any{}, + commandResultSchemas: map[string]map[string]any{}, + nativeCommands: map[string]bool{}, + eventSchemas: map[string]map[string]any{}, + } + types.hydrateNativeProtocolSchemas() + types.mu.Lock() + for method, schema := range types.commandSchemas { + types.nativeCommands[method] = true + if schema.Params != nil { + types.commandParamsSchemas[method] = schema.Params + } + if schema.Result != nil { + types.commandResultSchemas[method] = schema.Result + } + } + types.mu.Unlock() + for _, command := range defaultBuiltinCommands { + if _, _, err := types.AddCustomCommand(command); err != nil { + panic(err) + } + } + for _, event := range defaultBuiltinEvents { + if _, err := types.AddCustomEvent(event); err != nil { + panic(err) + } + } + for _, command := range customCommands { + if _, _, err := types.AddCustomCommand(command); err != nil { + panic(err) + } + } + for _, event := range customEvents { + if _, err := types.AddCustomEvent(event); err != nil { + panic(err) + } + } + for _, middleware := range customMiddlewares { + if _, err := types.AddCustomMiddleware(middleware); err != nil { + panic(err) + } + } + return types +} + +func (types *CDPTypes) Update(config CDPTypesConfig) *CDPTypes { + types.mu.RLock() + customCommands := make([]CustomCommand, 0, len(types.CustomCommands)+len(config.CustomCommands)) + for _, command := range types.CustomCommands { + customCommands = append(customCommands, command) + } + customEvents := make([]CustomEvent, 0, len(types.CustomEvents)+len(config.CustomEvents)) + for _, event := range types.CustomEvents { + customEvents = append(customEvents, event) + } + customMiddlewares := append([]CustomMiddleware{}, types.CustomMiddlewares...) + types.mu.RUnlock() + customCommands = append(customCommands, config.CustomCommands...) + customEvents = append(customEvents, config.CustomEvents...) + customMiddlewares = append(customMiddlewares, config.CustomMiddlewares...) + return NewCDPTypes(customCommands, customEvents, customMiddlewares) +} + +func (types *CDPTypes) ToJSON() map[string]any { + customCommands := []map[string]any{} + for _, command := range types.CustomCommandWireRegistrations(false) { + delete(command, "expression") + customCommands = append(customCommands, command) + } + customMiddlewares := []map[string]any{} + for _, middleware := range types.CustomMiddlewareWireRegistrations() { + registration := map[string]any{"phase": middleware.Phase} + if middleware.Name != "" { + registration["name"] = middleware.Name + } + customMiddlewares = append(customMiddlewares, registration) + } + types.mu.RLock() + state := map[string]any{ + "custom_commands": len(types.CustomCommands), + "custom_events": len(types.CustomEvents), + "custom_middlewares": len(types.CustomMiddlewares), + "command_params_schemas": len(types.commandParamsSchemas), + "command_result_schemas": len(types.commandResultSchemas), + "event_schemas": len(types.eventSchemas), + } + types.mu.RUnlock() + return modtypes.ModCDPToJSON(types, modtypes.ModCDPJSONConfig{ + Config: map[string]any{ + "custom_commands": customCommands, + "custom_events": types.CustomEventWireRegistrations(), + "custom_middlewares": customMiddlewares, + }, + State: state, + }) +} + +func (types *CDPTypes) PrepareCommand(method string, params map[string]any, canRegisterLocally bool) (CommandPreparation, error) { + commandParams, err := types.ParseCommandParams(method, params) + if err != nil { + return CommandPreparation{}, err + } + if method == "Mod.addCustomCommand" { + if schema, exists := commandParams["params_schema"]; exists && schema != nil { + if _, ok := schema.(map[string]any); !ok { + return CommandPreparation{}, fmt.Errorf("params_schema must be a JSON Schema object") + } + } + if schema, exists := commandParams["result_schema"]; exists && schema != nil { + if _, ok := schema.(map[string]any); !ok { + return CommandPreparation{}, fmt.Errorf("result_schema must be a JSON Schema object") + } + } + name, hasExpression, err := types.AddCustomCommand(CustomCommandFromParams(commandParams)) + if err != nil { + return CommandPreparation{}, err + } + if !hasExpression && canRegisterLocally { + return CommandPreparation{Params: commandParams, LocalResult: map[string]any{"name": name, "registered": true}, CustomCommandName: name}, nil + } + return CommandPreparation{Params: types.CustomCommandWireRegistration(name), CustomCommandName: name}, nil + } + if method == "Mod.addCustomEvent" { + if schema, exists := commandParams["event_schema"]; exists && schema != nil { + if _, ok := schema.(map[string]any); !ok { + return CommandPreparation{}, fmt.Errorf("event_schema must be a JSON Schema object") + } + } + name, err := types.AddCustomEvent(CustomEventFromParams(commandParams)) + if err != nil { + return CommandPreparation{}, err + } + if canRegisterLocally { + return CommandPreparation{Params: commandParams, LocalResult: map[string]any{"name": name, "registered": true}}, nil + } + return CommandPreparation{Params: types.CustomEventWireRegistration(name)}, nil + } + if method == "Mod.addMiddleware" { + middleware := CustomMiddlewareFromParams(commandParams) + name, err := types.AddCustomMiddleware(middleware) + if err != nil { + return CommandPreparation{}, err + } + if canRegisterLocally { + return CommandPreparation{Params: commandParams, LocalResult: map[string]any{"name": name, "phase": middleware.Phase, "registered": true}}, nil + } + } + return CommandPreparation{Params: commandParams}, nil +} + +func (types *CDPTypes) ParseCommandParams(method string, params map[string]any) (map[string]any, error) { + schema, ok := types.CommandParamsSchema(method) + if !ok { + return params, nil + } + if err := abxjsonschema.Validate(schema, params); err != nil { + return nil, fmt.Errorf("%s params did not match params_schema: %w", method, err) + } + return params, nil +} + +func (types *CDPTypes) NativeCommandSchema(method string) map[string]any { + types.mu.RLock() + defer types.mu.RUnlock() + if !types.nativeCommands[method] { + return nil + } + params := types.commandParamsSchemas[method] + result := types.commandResultSchemas[method] + if params == nil && result == nil { + return nil + } + return map[string]any{"params": params, "result": result} +} + +func (types *CDPTypes) CommandParamsSchema(method string) (map[string]any, bool) { + types.mu.RLock() + defer types.mu.RUnlock() + schema, ok := types.commandParamsSchemas[method] + if !ok || schema == nil { + return nil, false + } + return schema, true +} + +func (types *CDPTypes) CommandResultSchema(method string) (map[string]any, bool) { + types.mu.RLock() + defer types.mu.RUnlock() + schema, ok := types.commandResultSchemas[method] + if !ok || schema == nil { + return nil, false + } + return schema, true +} + +func (types *CDPTypes) EventPayloadSchema(event string) (map[string]any, bool) { + types.mu.RLock() + defer types.mu.RUnlock() + schema, ok := types.eventSchemas[event] + if !ok || schema == nil { + return nil, false + } + return schema, true +} + +func (types *CDPTypes) ParseCommandResult(method string, result any) (any, error) { + schema, ok := types.CommandResultSchema(method) + if !ok { + return result, nil + } + if err := abxjsonschema.Validate(schema, result); err != nil { + return nil, fmt.Errorf("%s result did not match result_schema: %w", method, err) + } + return result, nil +} + +func (types *CDPTypes) ParseEventPayload(event string, payload any) (any, error) { + schema, ok := types.EventPayloadSchema(event) + if !ok { + return payload, nil + } + if err := abxjsonschema.Validate(schema, payload); err != nil { + if payloadMap, ok := payload.(map[string]any); ok && len(payloadMap) == 1 { + if value, exists := payloadMap["value"]; exists { + if valueErr := abxjsonschema.Validate(schema, value); valueErr == nil { + return payload, nil + } + } + } + return nil, fmt.Errorf("%s event did not match event_schema: %w", event, err) + } + return payload, nil +} + +func (types *CDPTypes) AddCustomCommand(command CustomCommand) (string, bool, error) { + name, err := normalizeModCDPName(command.Name) + if err != nil { + return "", false, err + } + types.mu.Lock() + defer types.mu.Unlock() + if command.ParamsSchema != nil { + if schema := cloneSchema(command.ParamsSchema); schema != nil { + types.commandParamsSchemas[name] = schema + } + } + if command.ResultSchema != nil { + if schema := cloneSchema(command.ResultSchema); schema != nil { + types.commandResultSchemas[name] = schema + } + } + command.Name = name + if command.ParamsSchema != nil { + command.ParamsSchema = cloneSchema(command.ParamsSchema) + } + if command.ResultSchema != nil { + command.ResultSchema = cloneSchema(command.ResultSchema) + } + types.CustomCommands[name] = command + return name, command.Expression != "", nil +} + +func (types *CDPTypes) CustomCommandWireRegistration(name string) map[string]any { + for _, registration := range types.CustomCommandWireRegistrations(false) { + if registrationName, _ := registration["name"].(string); registrationName == name { + return registration + } + } + return map[string]any{"name": name} +} + +func (types *CDPTypes) CustomCommandWireRegistrations(expressionRequired bool) []map[string]any { + types.mu.RLock() + commands := make([]CustomCommand, 0, len(types.CustomCommands)) + for _, command := range types.CustomCommands { + commands = append(commands, command) + } + types.mu.RUnlock() + registrations := make([]map[string]any, 0, len(commands)) + for _, command := range commands { + if expressionRequired && command.Expression == "" { + continue + } + registration := map[string]any{"name": command.Name} + if command.Expression != "" { + registration["expression"] = command.Expression + } + if command.ParamsSchema != nil { + registration["params_schema"] = cloneSchema(command.ParamsSchema) + } + if command.ResultSchema != nil { + registration["result_schema"] = cloneSchema(command.ResultSchema) + } + registrations = append(registrations, registration) + } + return registrations +} + +func (types *CDPTypes) AddCustomEvent(event CustomEvent) (string, error) { + name, err := normalizeModCDPName(event.Name) + if err != nil { + return "", err + } + types.mu.Lock() + defer types.mu.Unlock() + if event.EventSchema != nil { + if schema := cloneSchema(event.EventSchema); schema != nil { + types.eventSchemas[name] = schema + event.EventSchema = schema + } + } + event.Name = name + types.CustomEvents[name] = event + return name, nil +} + +func (types *CDPTypes) CustomEventWireRegistration(name string) map[string]any { + types.mu.RLock() + event, ok := types.CustomEvents[name] + types.mu.RUnlock() + if !ok { + return map[string]any{"name": name} + } + registration := map[string]any{"name": name} + if event.EventSchema != nil { + registration["event_schema"] = cloneSchema(event.EventSchema) + } + return registration +} + +func (types *CDPTypes) CustomEventWireRegistrations() []map[string]any { + types.mu.RLock() + names := make([]string, 0, len(types.CustomEvents)) + for name := range types.CustomEvents { + names = append(names, name) + } + types.mu.RUnlock() + registrations := make([]map[string]any, 0, len(names)) + for _, name := range names { + registrations = append(registrations, types.CustomEventWireRegistration(name)) + } + return registrations +} + +func (types *CDPTypes) AddCustomMiddleware(middleware CustomMiddleware) (string, error) { + name := middleware.Name + if name == "" || name == "*" { + name = "*" + } else { + normalized, err := normalizeModCDPName(name) + if err != nil { + return "", err + } + name = normalized + } + if name != "*" && !strings.Contains(name, ".") { + return "", fmt.Errorf("name must be '*' or Domain.name form") + } + if middleware.Phase != "request" && middleware.Phase != "response" && middleware.Phase != "event" { + return "", fmt.Errorf("phase must be request, response, or event") + } + middleware.Name = name + types.CustomMiddlewares = append(types.CustomMiddlewares, middleware) + return name, nil +} + +func (types *CDPTypes) CustomMiddlewareWireRegistrations() []CustomMiddleware { + return append([]CustomMiddleware{}, types.CustomMiddlewares...) +} + +func (types *CDPTypes) CustomMiddlewareRegistrations(phase string, name string) []CustomMiddleware { + middlewares := []CustomMiddleware{} + for _, middleware := range types.CustomMiddlewares { + middlewareName := middleware.Name + if middlewareName == "" { + middlewareName = "*" + } + if middleware.Phase == phase && (middlewareName == "*" || middlewareName == name) { + middlewares = append(middlewares, middleware) + } + } + return middlewares +} + +func (types *CDPTypes) ServiceWorkerCommandStep(method string, params map[string]any, cdpSessionID string, executionContextID int) (modtypes.TranslatedStep, error) { + if params == nil { + params = map[string]any{} + } + types.mu.RLock() + command, hasCommand := types.CustomCommands[method] + types.mu.RUnlock() + if hasCommand && command.Expression != "" { + commandExpression := command.Expression + if method == "Mod.evaluate" { + expression, _ := params["expression"].(string) + commandExpression = fmt.Sprintf(` + async ({ params = {}, cdpSessionId = null }) => { + const value = (%s); + return typeof value === "function" ? await value(params) : value; + } + `, expression) + } + runtimeParams := map[string]any{ + "expression": types.serviceWorkerRuntimeExpression(method, params, cdpSessionID, commandExpression), + "awaitPromise": true, + "returnByValue": true, + } + if executionContextID != 0 { + runtimeParams["contextId"] = executionContextID + } + return modtypes.TranslatedStep{Method: "Runtime.evaluate", Params: runtimeParams, Unwrap: "runtime"}, nil + } + paramsJSON, err := json.Marshal(params) + if err != nil { + return modtypes.TranslatedStep{}, err + } + runtimeParams := map[string]any{ + "functionDeclaration": `async function(method, paramsJson, cdpSessionId) { return JSON.stringify(await globalThis.ModCDP.handleCommand(method, JSON.parse(paramsJson), cdpSessionId)); }`, + "arguments": []map[string]any{{"value": method}, {"value": string(paramsJSON)}, {"value": nil}}, + "awaitPromise": true, + "returnByValue": true, + } + if cdpSessionID != "" { + runtimeParams["arguments"] = []map[string]any{{"value": method}, {"value": string(paramsJSON)}, {"value": cdpSessionID}} + } + if executionContextID != 0 { + runtimeParams["executionContextId"] = executionContextID + } + return modtypes.TranslatedStep{Method: "Runtime.callFunctionOn", Params: runtimeParams, Unwrap: "runtime_json"}, nil +} + +func (types *CDPTypes) serviceWorkerRuntimeExpression(method string, params map[string]any, cdpSessionID string, commandExpression string) string { + methodJSON := jsonLiteral(method) + paramsJSON := jsonLiteral(params) + cdpSessionIDJSON := "null" + if cdpSessionID != "" { + cdpSessionIDJSON = jsonLiteral(cdpSessionID) + } + requestMiddlewares := strings.Join(types.serviceWorkerMiddlewareExpressions("request", method), ",") + responseMiddlewares := strings.Join(types.serviceWorkerMiddlewareExpressions("response", method), ",") + return fmt.Sprintf(` + (async () => { + const method = %s; + let commandParams = %s; + const cdpSessionId = %s; + const upstream = globalThis.ModCDP.client; + const downstream = globalThis.ModCDP.downstream; + const ModCDP = globalThis.ModCDP; + const cdp = { + upstream, + client: upstream, + downstream, + send: (method, params = {}, targetCdpSessionId = cdpSessionId) => + ModCDP.handleCommand(method, params, targetCdpSessionId), + }; + const chrome = globalThis.chrome; + const runMiddlewares = async (middlewares, payload, context = {}) => { + const dispatch = async (index, value) => { + const middleware = middlewares[index]; + if (!middleware) return value; + let nextCalled = false; + const next = async (nextValue = value) => { + if (nextCalled) throw new Error("Middleware called next() more than once."); + nextCalled = true; + return await dispatch(index + 1, nextValue); + }; + const result = await middleware(value, next, context); + if (result && result.__ModCDP_middleware_next__ === true) { + const nextResult = await next(result.value); + const { __ModCDP_middleware_next__, value: _value, ...overrides } = result; + if (Object.keys(overrides).length === 0) return nextResult; + return nextResult && typeof nextResult === "object" && !Array.isArray(nextResult) + ? { ...nextResult, ...overrides } + : overrides; + } + return result; + }; + return await dispatch(0, payload); + }; + const requestMiddlewares = [%s]; + const responseMiddlewares = [%s]; + const request = { method, params: commandParams, cdpSessionId }; + commandParams = await runMiddlewares(requestMiddlewares, commandParams, { + cdpSessionId, + request, + name: method, + phase: "request", + }); + if (commandParams == null) throw new Error("Request middleware returned no params."); + commandParams = ModCDP.types.parseCommandParams(method, commandParams); + const handler = (%s); + let result = await handler(commandParams || {}, method); + result = await runMiddlewares(responseMiddlewares, result, { + cdpSessionId, + request: { ...request, params: commandParams }, + response: { result }, + name: method, + phase: "response", + }); + return ModCDP.types.parseCommandResult(method, result); + })() + `, methodJSON, paramsJSON, cdpSessionIDJSON, requestMiddlewares, responseMiddlewares, commandExpression) +} + +func (types *CDPTypes) serviceWorkerMiddlewareExpressions(phase string, method string) []string { + expressions := []string{} + for _, middleware := range types.CustomMiddlewareRegistrations(phase, method) { + expressions = append(expressions, fmt.Sprintf(` + async (payload, next, context = {}) => { + const middleware = (%s); + return await middleware(payload, next, context); + } + `, middleware.Expression)) + } + return expressions +} + +func jsonLiteral(value any) string { + encoded, _ := json.Marshal(value) + return string(encoded) +} + +func CustomCommandFromParams(params map[string]any) CustomCommand { + command := CustomCommand{} + command.Name, _ = params["name"].(string) + command.Expression, _ = params["expression"].(string) + if schema, ok := params["params_schema"].(map[string]any); ok { + command.ParamsSchema = schema + } + if schema, ok := params["result_schema"].(map[string]any); ok { + command.ResultSchema = schema + } + return command +} + +func CustomEventFromParams(params map[string]any) CustomEvent { + event := CustomEvent{} + event.Name, _ = params["name"].(string) + if schema, ok := params["event_schema"].(map[string]any); ok { + event.EventSchema = schema + } + return event +} + +func CustomMiddlewareFromParams(params map[string]any) CustomMiddleware { + middleware := CustomMiddleware{} + middleware.Name, _ = params["name"].(string) + middleware.Phase, _ = params["phase"].(string) + middleware.Expression, _ = params["expression"].(string) + return middleware +} + +func customMiddlewaresToMaps(middlewares []CustomMiddleware) []map[string]any { + values := make([]map[string]any, 0, len(middlewares)) + for _, middleware := range middlewares { + item := map[string]any{ + "phase": middleware.Phase, + "expression": middleware.Expression, + } + if middleware.Name != "" { + item["name"] = middleware.Name + } + values = append(values, item) + } + return values +} diff --git a/go/modcdp/client/CDPTypes_payload_schema_normalization_test.go b/go/modcdp/client/CDPTypes_payload_schema_normalization_test.go new file mode 100644 index 00000000..ac9c2d78 --- /dev/null +++ b/go/modcdp/client/CDPTypes_payload_schema_normalization_test.go @@ -0,0 +1,91 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.CDPTypes_payload_schema_normalization.ts +// - ./python/tests/test_CDPTypes_payload_schema_normalization.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package client + +import ( + "strings" + "testing" + + abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" +) + +func TestValidateZodSchemaAcceptsEmptyZodShapes(t *testing.T) { + schema := cloneSchema(map[string]any{}) + if schema == nil { + t.Fatal("expected empty schema object to normalize") + } + if err := abxjsonschema.Validate(schema, map[string]any{"value": 1}); err != nil { + t.Fatalf("expected empty schema to accept payload: %v", err) + } +} + +func TestValidateZodSchemaRejectsUnsupportedSchemaSpecs(t *testing.T) { + _, err := New(Config{}).Send("Mod.addCustomCommand", map[string]any{ + "name": "Custom.bad", + "params_schema": "not-a-schema", + }) + if err == nil || !strings.Contains(err.Error(), "params_schema") { + t.Fatalf("expected unsupported schema error, got %v", err) + } +} + +func TestValidateZodSchemaAcceptsNonEmptyZodShapes(t *testing.T) { + schema := cloneSchema(map[string]any{ + "type": "object", + "required": []any{"value"}, + "properties": map[string]any{"value": map[string]any{"type": "string"}}, + }) + if schema == nil { + t.Fatal("expected schema object to normalize") + } + if err := abxjsonschema.Validate(schema, map[string]any{"value": "ok", "extra": true}); err != nil { + t.Fatalf("expected valid payload: %v", err) + } + if err := abxjsonschema.Validate(schema, map[string]any{"value": 1}); err == nil { + t.Fatal("expected invalid payload to fail validation") + } +} + +func TestCDPTypesSerializesBuiltinModCommandSchemasThroughTheSameWirePath(t *testing.T) { + types := NewCDPTypes(nil, nil, nil) + for _, name := range []string{"Mod.configure", "Mod.addCustomCommand", "Mod.addCustomEvent"} { + registration := types.CustomCommandWireRegistration(name) + if _, ok := registration["params_schema"].(map[string]any); !ok { + t.Fatalf("%s params_schema = %T", name, registration["params_schema"]) + } + if _, ok := registration["result_schema"].(map[string]any); !ok { + t.Fatalf("%s result_schema = %T", name, registration["result_schema"]) + } + } + + parsedConfig, err := types.ParseCommandParams("Mod.configure", map[string]any{ + "client_config": map[string]any{"client_hydrate_aliases": false}, + "upstream": map[string]any{"upstream_mode": "ws", "upstream_ws_cdp_url": "ws://127.0.0.1:9222/devtools/browser/test"}, + }) + if err != nil { + t.Fatal(err) + } + clientConfig, ok := parsedConfig["client_config"].(map[string]any) + if !ok || clientConfig["client_hydrate_aliases"] != false { + t.Fatalf("client_config = %#v", parsedConfig["client_config"]) + } + upstream, ok := parsedConfig["upstream"].(map[string]any) + if !ok { + t.Fatalf("upstream = %#v", parsedConfig["upstream"]) + } + if upstream["upstream_mode"] != "ws" || upstream["upstream_ws_cdp_url"] != "ws://127.0.0.1:9222/devtools/browser/test" { + t.Fatalf("upstream = %#v", upstream) + } + _, err = types.ParseCommandParams("Mod.configure", map[string]any{ + "upstream": map[string]any{ + "upstream_mode": "nats", + }, + }) + if err == nil || !strings.Contains(err.Error(), "enum") { + t.Fatalf("expected unsupported upstream mode to be rejected, got %v", err) + } +} diff --git a/go/modcdp/client/ModCDPClient.go b/go/modcdp/client/ModCDPClient.go index 8d35d192..19b6245b 100644 --- a/go/modcdp/client/ModCDPClient.go +++ b/go/modcdp/client/ModCDPClient.go @@ -1,15 +1,19 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/client/ModCDPClient.ts +// - ./python/modcdp/client/ModCDPClient.py // ModCDPClient (Go): importable, no CLI, no demo code. // -// Option groups mirror the JS / Python ports: +// Config groups mirror the JS / Python ports: // // Launcher browser/session creation and cleanup. // Upstream message transport to raw CDP or a ModCDP server. -// Injector raw-CDP extension discovery/injection/borrowing. -// Server ModCDPServer.configure params. -// Client client routing and client-owned send/event timings. -// Upstream upstream transport options and upstream-owned timings. +// Injector raw-CDP extension discovery/injection. +// ServerConfig ModCDPServer.configure params. +// ClientConfig client routing and client-owned send/event timings. +// Upstream upstream transport config and upstream-owned timings. // -// Public methods: Connect, Send(method, params), SendRaw, On, OnRaw, Close. +// Public methods: Connect, Send(method, params), On, Close. // Synchronous; one background goroutine reads messages off the WS. // // Route and ModCDP wire translation lives in translate.go. Launchers and @@ -42,7 +46,7 @@ var ( extIDFromURL = regexp.MustCompile(`^chrome-extension://([a-z]+)/`) ) -const modcdpReadyExpression = `Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)` +const modcdpReadyExpression = `Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)` const DefaultCDPSendTimeoutMS = 10_000 const DefaultEventWaitTimeoutMS = 10_000 @@ -60,65 +64,45 @@ func boolPointer(value bool) *bool { return &value } -type LaunchOptions = types.LaunchOptions +type LauncherConfig = types.LauncherConfig type LaunchedBrowser = launcher.LaunchedBrowser type BrowserLauncher = launcher.BrowserLauncher type LocalBrowserLauncher = launcher.LocalBrowserLauncher type RemoteBrowserLauncher = launcher.RemoteBrowserLauncher -type BrowserbaseBrowserLauncher = launcher.BrowserbaseBrowserLauncher -type NoopBrowserLauncher = launcher.NoopBrowserLauncher -type ExtensionInjectorConfig = types.ExtensionInjectorConfig +type BBBrowserLauncher = launcher.BBBrowserLauncher +type NoneBrowserLauncher = launcher.NoneBrowserLauncher +type InjectorConfig = types.InjectorConfig type ExtensionInjectionResult = types.ExtensionInjectionResult type SendCDP = types.SendCDP -type AttachToTarget = types.AttachToTarget +type RouterConfig = types.ModCDPRouterConfig type ExtensionInjector = injector.ExtensionInjector -type DiscoveredExtensionInjector = injector.DiscoveredExtensionInjector -type BBBrowserExtensionInjector = injector.BBBrowserExtensionInjector -type LocalBrowserLaunchExtensionInjector = injector.LocalBrowserLaunchExtensionInjector -type ExtensionsLoadUnpackedInjector = injector.ExtensionsLoadUnpackedInjector -type BorrowedExtensionInjector = injector.BorrowedExtensionInjector +type DiscoverExtensionInjector = injector.DiscoverExtensionInjector +type BBExtensionInjector = injector.BBExtensionInjector +type CLIExtensionInjector = injector.CLIExtensionInjector +type CDPExtensionInjector = injector.CDPExtensionInjector type UpstreamMode = transportpkg.UpstreamMode -type UpstreamEndpointKind = transportpkg.UpstreamEndpointKind +type UpstreamTransportConfig = types.UpstreamTransportConfig type UpstreamTransport = transportpkg.UpstreamTransport -type WebSocketUpstreamTransport = transportpkg.WebSocketUpstreamTransport -type WebSocketUpstreamTransportOptions = transportpkg.WebSocketUpstreamTransportOptions -type PipeUpstreamTransport = transportpkg.PipeUpstreamTransport -type PipeUpstreamTransportOptions = transportpkg.PipeUpstreamTransportOptions -type ReverseWebSocketUpstreamTransport = transportpkg.ReverseWebSocketUpstreamTransport -type ReverseWebSocketUpstreamTransportOptions = transportpkg.ReverseWebSocketUpstreamTransportOptions -type NativeMessagingUpstreamTransport = transportpkg.NativeMessagingUpstreamTransport -type NativeMessagingUpstreamTransportOptions = transportpkg.NativeMessagingUpstreamTransportOptions -type NatsUpstreamTransport = transportpkg.NatsUpstreamTransport -type NatsUpstreamTransportOptions = transportpkg.NatsUpstreamTransportOptions +type WSUpstreamTransport = transportpkg.WSUpstreamTransport type AutoSessionRouter = router.AutoSessionRouter +type CustomCommand = types.ModCDPAddCustomCommandParams +type CustomEvent = types.ModCDPAddCustomEventObjectParams +type CustomMiddleware = types.ModCDPAddMiddlewareParams var NewLocalBrowserLauncher = launcher.NewLocalBrowserLauncher var NewRemoteBrowserLauncher = launcher.NewRemoteBrowserLauncher -var NewBrowserbaseBrowserLauncher = launcher.NewBrowserbaseBrowserLauncher -var NewNoopBrowserLauncher = launcher.NewNoopBrowserLauncher -var NewDiscoveredExtensionInjector = injector.NewDiscoveredExtensionInjector -var NewBBBrowserExtensionInjector = injector.NewBBBrowserExtensionInjector -var NewLocalBrowserLaunchExtensionInjector = injector.NewLocalBrowserLaunchExtensionInjector -var NewExtensionsLoadUnpackedInjector = injector.NewExtensionsLoadUnpackedInjector -var NewBorrowedExtensionInjector = injector.NewBorrowedExtensionInjector -var NewWebSocketUpstreamTransport = transportpkg.NewWebSocketUpstreamTransport -var NewPipeUpstreamTransport = transportpkg.NewPipeUpstreamTransport -var NewReverseWebSocketUpstreamTransport = transportpkg.NewReverseWebSocketUpstreamTransport -var NewNativeMessagingUpstreamTransport = transportpkg.NewNativeMessagingUpstreamTransport -var NewNatsUpstreamTransport = transportpkg.NewNatsUpstreamTransport +var NewBBBrowserLauncher = launcher.NewBBBrowserLauncher +var NewNoneBrowserLauncher = launcher.NewNoneBrowserLauncher +var NewDiscoverExtensionInjector = injector.NewDiscoverExtensionInjector +var NewBBExtensionInjector = injector.NewBBExtensionInjector +var NewCLIExtensionInjector = injector.NewCLIExtensionInjector +var NewCDPExtensionInjector = injector.NewCDPExtensionInjector +var NewWSUpstreamTransport = transportpkg.NewWSUpstreamTransport var NewAutoSessionRouter = router.NewAutoSessionRouter var DefaultModCDPServiceWorkerURLSuffixes = injector.DefaultModCDPServiceWorkerURLSuffixes const DefaultModCDPExtensionID = injector.DefaultModCDPExtensionID -const DefaultUpstreamReverseWSBind = transportpkg.DefaultUpstreamReverseWSBind -const DefaultUpstreamReverseWSWaitTimeoutMS = transportpkg.DefaultUpstreamReverseWSWaitTimeoutMS -const DefaultUpstreamNATSWaitTimeoutMS = transportpkg.DefaultUpstreamNATSWaitTimeoutMS -const DefaultUpstreamNativeMessagingWaitTimeoutMS = transportpkg.DefaultUpstreamNativeMessagingWaitTimeoutMS -const UpstreamEndpointKindRawCDP = transportpkg.UpstreamEndpointKindRawCDP -const UpstreamEndpointKindModCDPServer = transportpkg.UpstreamEndpointKindModCDPServer - -var endpointKindForUpstream = transportpkg.EndpointKindForUpstream func firstNonEmptyString(values ...string) string { for _, value := range values { @@ -178,143 +162,75 @@ func freePort() (int, error) { return listener.Addr().(*net.TCPAddr).Port, nil } -// --- public types -------------------------------------------------------- - -type ServerConfig struct { - ServerLoopbackCDPURL string `json:"server_loopback_cdp_url,omitempty"` - ServerRoutes map[string]string `json:"server_routes,omitempty"` - ServerBrowserToken string `json:"server_browser_token,omitempty"` - ServerCDPSendTimeoutMS int `json:"server_cdp_send_timeout_ms,omitempty"` - ServerLoopbackExecutionContextTimeoutMS int `json:"server_loopback_execution_context_timeout_ms,omitempty"` - ServerWSConnectErrorSettleTimeoutMS int `json:"server_ws_connect_error_settle_timeout_ms,omitempty"` - ServerDownstreamClientTimeoutMS int `json:"server_downstream_client_timeout_ms,omitempty"` - ServerCloseBrowserOnDownstreamDisconnect *bool `json:"server_close_browser_on_downstream_disconnect,omitempty"` - Options map[string]any `json:"-"` - disabled bool -} - -var ServerNone = &ServerConfig{disabled: true} - -type CustomEvent struct { - Name string `json:"name"` - EventSchema map[string]any `json:"event_schema,omitempty"` -} - -type CustomCommand struct { - Name string `json:"name"` - Expression string `json:"expression,omitempty"` - ParamsSchema map[string]any `json:"params_schema,omitempty"` - ResultSchema map[string]any `json:"result_schema,omitempty"` -} - -type CustomMiddleware struct { - Name string `json:"name,omitempty"` - Phase string `json:"phase"` - Expression string `json:"expression"` -} - -type LauncherConfig struct { - LauncherMode string `json:"launcher_mode,omitempty"` - LauncherExecutablePath string `json:"launcher_executable_path,omitempty"` - LauncherUserDataDir string `json:"launcher_user_data_dir,omitempty"` - LauncherOptions LaunchOptions `json:"launcher_options,omitempty"` -} - -type UpstreamConfig struct { - UpstreamMode string `json:"upstream_mode,omitempty"` - UpstreamCDPURL string `json:"upstream_cdp_url,omitempty"` - UpstreamNATSURL string `json:"upstream_nats_url,omitempty"` - UpstreamNATSSubjectPrefix string `json:"upstream_nats_subject_prefix,omitempty"` - UpstreamNATSWaitTimeoutMS int `json:"upstream_nats_wait_timeout_ms,omitempty"` - UpstreamReverseWSBind string `json:"upstream_reversews_bind,omitempty"` - UpstreamReverseWSWaitTimeoutMS int `json:"upstream_reversews_wait_timeout_ms,omitempty"` - UpstreamNativeMessagingManifest string `json:"upstream_nativemessaging_manifest,omitempty"` - UpstreamNativeMessagingManifests []string `json:"upstream_nativemessaging_manifests,omitempty"` - UpstreamNativeMessagingHostName string `json:"upstream_nativemessaging_host_name,omitempty"` - UpstreamNativeMessagingWaitTimeoutMS int `json:"upstream_nativemessaging_wait_timeout_ms,omitempty"` - UpstreamWSConnectErrorSettleTimeoutMS int `json:"upstream_ws_connect_error_settle_timeout_ms,omitempty"` -} - -type InjectorConfig struct { - InjectorMode string `json:"injector_mode,omitempty"` - InjectorExtensionPath string `json:"injector_extension_path,omitempty"` - InjectorExtensionID string `json:"injector_extension_id,omitempty"` - InjectorServiceWorkerURLIncludes []string `json:"injector_service_worker_url_includes,omitempty"` - InjectorServiceWorkerURLSuffixes []string `json:"injector_service_worker_url_suffixes,omitempty"` - InjectorTrustServiceWorkerTarget bool `json:"injector_trust_service_worker_target,omitempty"` - InjectorRequireServiceWorkerTarget bool `json:"injector_require_service_worker_target,omitempty"` - InjectorServiceWorkerReadyExpression string `json:"injector_service_worker_ready_expression,omitempty"` - InjectorExecutionContextTimeoutMS int `json:"injector_execution_context_timeout_ms,omitempty"` - InjectorServiceWorkerProbeTimeoutMS int `json:"injector_service_worker_probe_timeout_ms,omitempty"` - InjectorServiceWorkerReadyTimeoutMS int `json:"injector_service_worker_ready_timeout_ms,omitempty"` - InjectorServiceWorkerPollIntervalMS int `json:"injector_service_worker_poll_interval_ms,omitempty"` - InjectorTargetSessionPollIntervalMS int `json:"injector_target_session_poll_interval_ms,omitempty"` -} - -type ClientConfig struct { - ClientRoutes map[string]string `json:"client_routes,omitempty"` - ClientHydrateAliases *bool `json:"client_hydrate_aliases,omitempty"` - ClientMirrorUpstreamEvents *bool `json:"client_mirror_upstream_events,omitempty"` - ClientCDPSendTimeoutMS int `json:"client_cdp_send_timeout_ms,omitempty"` - ClientEventWaitTimeoutMS int `json:"client_event_wait_timeout_ms,omitempty"` - ClientHeartbeatIntervalMS int `json:"client_heartbeat_interval_ms,omitempty"` -} - -type Options struct { - Launcher LauncherConfig `json:"launcher,omitempty"` - Upstream UpstreamConfig `json:"upstream,omitempty"` - Injector InjectorConfig `json:"injector,omitempty"` - Client ClientConfig `json:"client,omitempty"` - Server *ServerConfig `json:"server,omitempty"` +type CDPTypesConfig struct { CustomCommands []CustomCommand `json:"custom_commands,omitempty"` CustomEvents []CustomEvent `json:"custom_events,omitempty"` CustomMiddlewares []CustomMiddleware `json:"custom_middlewares,omitempty"` - serverConfigured bool } -func (o *Options) UnmarshalJSON(data []byte) error { - type optionsAlias Options - var decoded optionsAlias +type ServerConfig struct { + Upstream UpstreamTransportConfig `json:"upstream,omitempty"` + Router RouterConfig `json:"router,omitempty"` + ClientConfig ClientConfig `json:"client_config,omitempty"` + Downstream types.ModCDPDownstreamConfig `json:"downstream,omitempty"` + ServerBrowserToken string `json:"server_browser_token,omitempty"` + CustomCommands []CustomCommand `json:"custom_commands,omitempty"` + CustomEvents []CustomEvent `json:"custom_events,omitempty"` + CustomMiddlewares []CustomMiddleware `json:"custom_middlewares,omitempty"` + disabled bool +} + +var ServerConfigNone = &ServerConfig{disabled: true} + +type ClientConfig = types.ModCDPClientConfig + +type Config struct { + Launcher LauncherConfig `json:"launcher,omitempty"` + Upstream UpstreamTransportConfig `json:"upstream,omitempty"` + Injector InjectorConfig `json:"injector,omitempty"` + Router RouterConfig `json:"router,omitempty"` + ClientConfig ClientConfig `json:"client_config,omitempty"` + ServerConfig *ServerConfig `json:"server_config,omitempty"` + Types *CDPTypesConfig `json:"types,omitempty"` + serverConfigConfigured bool +} + +func (o *Config) UnmarshalJSON(data []byte) error { + type configAlias Config + var decoded configAlias if err := json.Unmarshal(data, &decoded); err != nil { return err } - *o = Options(decoded) + *o = Config(decoded) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { return err } - rawServer, hasServer := raw["server"] - if !hasServer { + rawServerConfig, hasServerConfig := raw["server_config"] + if !hasServerConfig { return nil } - o.serverConfigured = true - if strings.TrimSpace(string(rawServer)) == "null" { - o.Server = nil + o.serverConfigConfigured = true + if strings.TrimSpace(string(rawServerConfig)) == "null" { + o.ServerConfig = nil return nil } var server ServerConfig - if err := json.Unmarshal(rawServer, &server); err != nil { + if err := json.Unmarshal(rawServerConfig, &server); err != nil { return err } - o.Server = &server + o.ServerConfig = &server return nil } -type Handler func(data any) +type Handler any type handlerEntry struct { - handler Handler + handler reflect.Value pointer uintptr } -type CDPEvent struct { - Method string `json:"method"` - Params map[string]any `json:"params,omitempty"` - CDPSessionID string `json:"cdpSessionId,omitempty"` - SessionID string `json:"sessionId,omitempty"` -} - type ModDomain struct { client *ModCDPClient } @@ -376,200 +292,239 @@ type ModCDPClient struct { WebAuthn WebAuthnDomain Mod ModDomain - Launcher LauncherConfig - Upstream UpstreamConfig - Injector InjectorConfig - Client ClientConfig - Server *ServerConfig - CustomCommands []CustomCommand - CustomEvents []CustomEvent - CustomMiddlewares []CustomMiddleware - UpstreamEndpointKind UpstreamEndpointKind + Config Config + Types *CDPTypes CDPURL string - transport upstreamTransportClient - mu sync.Mutex - nextID int64 - pending map[int64]chan map[string]any + Launcher browserLauncherClient + Injector *ExtensionInjector + Upstream upstreamTransportClient handlers map[string][]handlerEntry - cdpHandlers map[string][]func(CDPEvent) - commandParamsSchemas map[string]map[string]any - commandResultSchemas map[string]map[string]any - commandResultUnwrapKeys map[string]string - eventSchemas map[string]map[string]any - schemaMu sync.RWMutex handlersMu sync.Mutex - autoSessions *AutoSessionRouter - ExtensionID string - ExtTargetID string - ExtSessionID string - ExtExecutionContextID int + Router *AutoSessionRouter Latency map[string]any ConnectTiming map[string]any LastCommandTiming map[string]any - LastRawTiming map[string]any - launchedBrowser *LaunchedBrowser extensionInjectors []extensionInjector configuredPeerGeneration int64 heartbeatStop chan struct{} } type extensionInjector interface { - Update(ExtensionInjectorConfig) *ExtensionInjector - GetLauncherConfig() LaunchOptions - GetTransportConfig() map[string]any + Update(InjectorConfig) *ExtensionInjector + RecordInjectionResult(*ExtensionInjectionResult) *ExtensionInjector + ConfigForLauncher() LauncherConfig + ConfigForUpstream() map[string]any Prepare() error Inject() (*ExtensionInjectionResult, error) Close() error } type browserLauncherClient interface { - Update(LaunchOptions) *BrowserLauncher - GetInjectorConfig() ExtensionInjectorConfig - GetTransportConfig() map[string]any - GetServerConfig() map[string]any - Launch(LaunchOptions) (*LaunchedBrowser, error) + Update(LauncherConfig) *BrowserLauncher + ConfigForUpstream() map[string]any + ConfigForServer(UpstreamTransportConfig) map[string]any + Launch(LauncherConfig) (*LaunchedBrowser, error) + Close() } type upstreamTransportClient interface { Update(map[string]any) Connect() error Close() error - Send(map[string]any) error - GetLauncherConfig() LaunchOptions - GetInjectorConfig() ExtensionInjectorConfig - GetServerConfig() map[string]any + Send(command any, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error) + ConfigForLauncher() LauncherConfig OnRecv(func(map[string]any)) func() OnClose(func(error)) func() WaitForPeer() error PeerGeneration() int64 } -func New(opts Options) *ModCDPClient { - if opts.Upstream.UpstreamMode == "" { - opts.Upstream.UpstreamMode = "ws" - } - upstreamEndpointKind := UpstreamEndpointKindModCDPServer - if opts.Upstream.UpstreamMode == "ws" || opts.Upstream.UpstreamMode == "pipe" { - upstreamEndpointKind = UpstreamEndpointKindRawCDP +func New(config Config) *ModCDPClient { + if config.Upstream.UpstreamMode == "" { + config.Upstream.UpstreamMode = "ws" } - if opts.Launcher.LauncherMode == "" { - if upstreamEndpointKind == UpstreamEndpointKindModCDPServer { - opts.Launcher.LauncherMode = "none" - } else if opts.Upstream.UpstreamCDPURL != "" { - opts.Launcher.LauncherMode = "remote" - } else { - opts.Launcher.LauncherMode = "local" - } + if config.Launcher.LauncherMode == "" { + config.Launcher.LauncherMode = "none" } - if opts.Injector.InjectorMode == "" { - if upstreamEndpointKind == UpstreamEndpointKindRawCDP || opts.Launcher.LauncherMode != "none" { - opts.Injector.InjectorMode = "auto" - } else { - opts.Injector.InjectorMode = "none" - } + if config.Injector.InjectorMode == "" { + config.Injector.InjectorMode = "none" } - if opts.Launcher.LauncherExecutablePath != "" { - opts.Launcher.LauncherOptions.ExecutablePath = opts.Launcher.LauncherExecutablePath - } - if opts.Launcher.LauncherUserDataDir != "" { - opts.Launcher.LauncherOptions.UserDataDir = opts.Launcher.LauncherUserDataDir - } - if opts.Client.ClientRoutes == nil { - opts.Client.ClientRoutes = translate.DefaultClientRoutes() + if config.Router.RouterRoutes == nil { + config.Router.RouterRoutes = translate.DefaultClientRoutes() } else { merged := translate.DefaultClientRoutes() - for k, v := range opts.Client.ClientRoutes { + for k, v := range config.Router.RouterRoutes { merged[k] = v } - opts.Client.ClientRoutes = merged + config.Router.RouterRoutes = merged } - if opts.Client.ClientHydrateAliases == nil { + if config.ClientConfig.ClientHydrateAliases == nil { value := true - opts.Client.ClientHydrateAliases = &value + config.ClientConfig.ClientHydrateAliases = &value + } + if config.ServerConfig != nil && config.ServerConfig.disabled { + config.ServerConfig = nil + config.serverConfigConfigured = true + } + if config.ServerConfig == nil && !config.serverConfigConfigured { + config.ServerConfig = &ServerConfig{} + } + if config.Injector.InjectorServiceWorkerURLSuffixes == nil { + config.Injector.InjectorServiceWorkerURLSuffixes = append([]string{}, DefaultModCDPServiceWorkerURLSuffixes...) } - if opts.Server != nil && opts.Server.disabled { - opts.Server = nil - opts.serverConfigured = true + if config.ClientConfig.ClientCDPSendTimeoutMS == 0 { + config.ClientConfig.ClientCDPSendTimeoutMS = DefaultCDPSendTimeoutMS } - if opts.Server == nil && !opts.serverConfigured { - opts.Server = &ServerConfig{} + if config.ClientConfig.ClientEventWaitTimeoutMS == 0 { + config.ClientConfig.ClientEventWaitTimeoutMS = DefaultEventWaitTimeoutMS } - if upstreamEndpointKind == UpstreamEndpointKindModCDPServer && opts.Server != nil && opts.Server.ServerRoutes == nil { - opts.Server.ServerRoutes = map[string]string{"*.*": "chrome_debugger"} + if config.ClientConfig.ClientHeartbeatIntervalMS == 0 { + config.ClientConfig.ClientHeartbeatIntervalMS = DefaultClientHeartbeatIntervalMS } - if opts.Injector.InjectorServiceWorkerURLSuffixes == nil { - opts.Injector.InjectorServiceWorkerURLSuffixes = append([]string{}, DefaultModCDPServiceWorkerURLSuffixes...) + if config.Injector.InjectorExecutionContextTimeoutMS == 0 { + config.Injector.InjectorExecutionContextTimeoutMS = DefaultExecutionContextTimeoutMS } - if opts.Client.ClientCDPSendTimeoutMS == 0 { - opts.Client.ClientCDPSendTimeoutMS = DefaultCDPSendTimeoutMS + if config.Injector.InjectorServiceWorkerProbeTimeoutMS == 0 { + config.Injector.InjectorServiceWorkerProbeTimeoutMS = DefaultServiceWorkerProbeTimeoutMS } - if opts.Client.ClientEventWaitTimeoutMS == 0 { - opts.Client.ClientEventWaitTimeoutMS = DefaultEventWaitTimeoutMS + if config.Injector.InjectorServiceWorkerReadyTimeoutMS == 0 { + config.Injector.InjectorServiceWorkerReadyTimeoutMS = DefaultServiceWorkerReadyTimeoutMS } - if opts.Client.ClientHeartbeatIntervalMS == 0 { - opts.Client.ClientHeartbeatIntervalMS = DefaultClientHeartbeatIntervalMS + if config.Injector.InjectorServiceWorkerPollIntervalMS == 0 { + config.Injector.InjectorServiceWorkerPollIntervalMS = DefaultServiceWorkerPollIntervalMS } - if opts.Injector.InjectorExecutionContextTimeoutMS == 0 { - opts.Injector.InjectorExecutionContextTimeoutMS = DefaultExecutionContextTimeoutMS + if config.Injector.InjectorTargetSessionPollIntervalMS == 0 { + config.Injector.InjectorTargetSessionPollIntervalMS = DefaultTargetSessionPollIntervalMS } - if opts.Injector.InjectorServiceWorkerProbeTimeoutMS == 0 { - opts.Injector.InjectorServiceWorkerProbeTimeoutMS = DefaultServiceWorkerProbeTimeoutMS + if config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS == 0 { + config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS = DefaultWSConnectErrorSettleTimeoutMS } - if opts.Injector.InjectorServiceWorkerReadyTimeoutMS == 0 { - opts.Injector.InjectorServiceWorkerReadyTimeoutMS = DefaultServiceWorkerReadyTimeoutMS + typesConfig := CDPTypesConfig{} + if config.Types != nil { + typesConfig = *config.Types } - if opts.Injector.InjectorServiceWorkerPollIntervalMS == 0 { - opts.Injector.InjectorServiceWorkerPollIntervalMS = DefaultServiceWorkerPollIntervalMS + upstream := NewWSUpstreamTransport(config.Upstream) + client := &ModCDPClient{ + Config: config, + Types: NewCDPTypes(typesConfig.CustomCommands, typesConfig.CustomEvents, typesConfig.CustomMiddlewares), + Upstream: upstream, + handlers: map[string][]handlerEntry{}, } - if opts.Injector.InjectorTargetSessionPollIntervalMS == 0 { - opts.Injector.InjectorTargetSessionPollIntervalMS = DefaultTargetSessionPollIntervalMS + client.Mod = ModDomain{client: client} + client.Router = NewAutoSessionRouter(&upstream.UpstreamTransport, client.Types, config.Router) + client.Launcher = client.browserLauncher() + injectors, selectedInjector := client.extensionInjectorsForConfig() + if len(injectors) > 0 { + client.Injector = selectedInjector + client.extensionInjectors = injectors + } + if *client.Config.ClientConfig.ClientHydrateAliases { + initCDPSurface(client) } - if opts.Upstream.UpstreamWSConnectErrorSettleTimeoutMS == 0 { - opts.Upstream.UpstreamWSConnectErrorSettleTimeoutMS = DefaultWSConnectErrorSettleTimeoutMS + return client +} + +func (c *ModCDPClient) ToJSON() map[string]any { + children := map[string]types.ModCDPJSONChild{} + if child, ok := c.Launcher.(types.ModCDPJSONChild); ok { + children["launcher"] = child } - if opts.Upstream.UpstreamReverseWSBind == "" { - opts.Upstream.UpstreamReverseWSBind = DefaultUpstreamReverseWSBind + if child, ok := c.Upstream.(types.ModCDPJSONChild); ok { + children["upstream"] = child } - if opts.Upstream.UpstreamNATSWaitTimeoutMS == 0 { - opts.Upstream.UpstreamNATSWaitTimeoutMS = DefaultUpstreamNATSWaitTimeoutMS + if c.Injector != nil { + children["injector"] = c.Injector } - if opts.Upstream.UpstreamReverseWSWaitTimeoutMS == 0 { - opts.Upstream.UpstreamReverseWSWaitTimeoutMS = DefaultUpstreamReverseWSWaitTimeoutMS + if child, ok := any(c.Router).(types.ModCDPJSONChild); ok { + children["router"] = child } - if opts.Upstream.UpstreamNativeMessagingWaitTimeoutMS == 0 { - opts.Upstream.UpstreamNativeMessagingWaitTimeoutMS = DefaultUpstreamNativeMessagingWaitTimeoutMS + if child, ok := any(c.Types).(types.ModCDPJSONChild); ok { + children["types"] = child } - client := &ModCDPClient{ - Launcher: opts.Launcher, - Upstream: opts.Upstream, - Injector: opts.Injector, - Client: opts.Client, - Server: opts.Server, - CustomCommands: opts.CustomCommands, - CustomEvents: opts.CustomEvents, - CustomMiddlewares: opts.CustomMiddlewares, - UpstreamEndpointKind: upstreamEndpointKind, - pending: map[int64]chan map[string]any{}, - handlers: map[string][]handlerEntry{}, - cdpHandlers: map[string][]func(CDPEvent){}, - commandParamsSchemas: map[string]map[string]any{}, - commandResultSchemas: map[string]map[string]any{}, - commandResultUnwrapKeys: map[string]string{}, - eventSchemas: map[string]map[string]any{}, + latency := any(nil) + if c.Latency != nil { + latency = c.Latency["round_trip_ms"] } - client.Mod = ModDomain{client: client} - client.autoSessions = NewAutoSessionRouter( - func(method string, params map[string]any, sessionID string) (map[string]any, error) { - return client.sendMessage(method, params, sessionID) + return types.ModCDPToJSON(c, types.ModCDPJSONConfig{ + Config: map[string]any{ + "client_config": c.Config.ClientConfig, + "server_config": c.Config.ServerConfig, }, - func() int { return client.Injector.InjectorExecutionContextTimeoutMS }, - ) - if *client.Client.ClientHydrateAliases { - initCDPSurface(client) + State: map[string]any{ + "event_wait_cleanups": len(c.handlers), + "heartbeat_timer": c.heartbeatStop != nil, + "latency": latency, + "connected": c.ConnectTiming != nil, + }, + Children: children, + }) +} + +func (c *ModCDPClient) Configure(config Config) *ModCDPClient { + if config.ClientConfig.ClientHydrateAliases != nil { + c.Config.ClientConfig.ClientHydrateAliases = config.ClientConfig.ClientHydrateAliases } - client.hydrateNativeProtocolSchemas() - client.hydrateCustomSurface() - return client + if config.ClientConfig.ClientMirrorUpstreamEvents != nil { + c.Config.ClientConfig.ClientMirrorUpstreamEvents = config.ClientConfig.ClientMirrorUpstreamEvents + } + if config.ClientConfig.ClientCDPSendTimeoutMS != 0 { + c.Config.ClientConfig.ClientCDPSendTimeoutMS = config.ClientConfig.ClientCDPSendTimeoutMS + } + if config.ClientConfig.ClientEventWaitTimeoutMS != 0 { + c.Config.ClientConfig.ClientEventWaitTimeoutMS = config.ClientConfig.ClientEventWaitTimeoutMS + } + if config.ClientConfig.ClientHeartbeatIntervalMS != 0 { + c.Config.ClientConfig.ClientHeartbeatIntervalMS = config.ClientConfig.ClientHeartbeatIntervalMS + } + if c.Upstream != nil { + c.Upstream.Update(map[string]any{"upstream_cdp_send_timeout_ms": c.Config.ClientConfig.ClientCDPSendTimeoutMS}) + } + if config.Upstream.UpstreamMode != "" || config.Upstream.UpstreamWSCDPURL != "" || config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 0 || config.Upstream.UpstreamCDPSendTimeoutMS != 0 { + if config.Upstream.UpstreamMode != "" { + c.Config.Upstream.UpstreamMode = config.Upstream.UpstreamMode + } + if config.Upstream.UpstreamWSCDPURL != "" { + c.Config.Upstream.UpstreamWSCDPURL = config.Upstream.UpstreamWSCDPURL + } + if config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 0 { + c.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS = config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS + } + if config.Upstream.UpstreamCDPSendTimeoutMS != 0 { + c.Config.Upstream.UpstreamCDPSendTimeoutMS = config.Upstream.UpstreamCDPSendTimeoutMS + } + if c.Upstream != nil { + c.Upstream.Update(c.upstreamTransportConfig()) + } + } + if config.Router.RouterRoutes != nil { + if c.Config.Router.RouterRoutes == nil { + c.Config.Router.RouterRoutes = translate.DefaultClientRoutes() + } + for key, value := range config.Router.RouterRoutes { + c.Config.Router.RouterRoutes[key] = value + } + } + if config.Router.LoopbackExecutionContextTimeoutMS != 0 { + c.Config.Router.LoopbackExecutionContextTimeoutMS = config.Router.LoopbackExecutionContextTimeoutMS + } + if c.Router != nil { + if c.Config.Router.RouterRoutes == nil { + c.Config.Router.RouterRoutes = translate.DefaultClientRoutes() + } + if c.Config.Router.LoopbackExecutionContextTimeoutMS == 0 { + c.Config.Router.LoopbackExecutionContextTimeoutMS = DefaultExecutionContextTimeoutMS + } + c.Router.Config = c.Config.Router + } + if config.serverConfigConfigured { + c.Config.ServerConfig = config.ServerConfig + } else if config.ServerConfig != nil { + c.Config.ServerConfig = config.ServerConfig + } + if c.Config.ClientConfig.ClientHydrateAliases != nil && *c.Config.ClientConfig.ClientHydrateAliases { + initCDPSurface(c) + } + return c } func (c *ModCDPClient) Connect() error { @@ -579,33 +534,18 @@ func (c *ModCDPClient) Connect() error { return err } transportConnectedAt := time.Now().UnixMilli() - if c.transport == nil { + if c.Upstream == nil { return fmt.Errorf("upstream transport did not connect") } - c.transport.OnRecv(func(message map[string]any) { c.handleMessage(message) }) - c.transport.OnClose(func(err error) { + c.Upstream.OnRecv(func(message map[string]any) { c.handleMessage(message) }) + c.Upstream.OnClose(func(err error) { c.stopHeartbeat() - c.rejectAll(err) }) - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindModCDPServer { - if err := c.transport.WaitForPeer(); err != nil { - c.Close() - return err - } - if c.Server != nil { - if _, err := c.sendMessage("Mod.configure", c.serverConfigureParams(nil, nil, nil), ""); err != nil { - c.Close() - return err - } - c.configuredPeerGeneration = c.transport.PeerGeneration() - } - c.startHeartbeat() - c.startPingLatencyMeasurement() + if c.Config.Injector.InjectorMode == "none" && c.Config.ServerConfig == nil { connectedAt := time.Now().UnixMilli() c.ConnectTiming = map[string]any{ "started_at": connectStartedAt, - "upstream_mode": c.Upstream.UpstreamMode, - "upstream_endpoint_kind": transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode), + "upstream_mode": c.Config.Upstream.UpstreamMode, "transport_started_at": transportStartedAt, "transport_connected_at": transportConnectedAt, "transport_duration_ms": transportConnectedAt - transportStartedAt, @@ -614,83 +554,47 @@ func (c *ModCDPClient) Connect() error { } return nil } - if err := c.initializeRawCDPTransport(); err != nil { + if err := c.Router.Start(); err != nil { c.Close() return err } extensionStartedAt := time.Now().UnixMilli() - ext, err := c.injectExtension(c.extensionInjectors) + ext, injectorState, err := c.injectExtension(c.extensionInjectors) if err != nil { c.Close() return err } extensionCompletedAt := time.Now().UnixMilli() - c.ExtensionID = ext.ExtensionID - c.ExtTargetID = ext.TargetID - c.ExtSessionID = ext.SessionID - if _, err := c.sendMessage("Runtime.enable", map[string]any{}, c.ExtSessionID); err != nil { + if injectorState.TargetID == "" || injectorState.SessionID == "" { c.Close() - return err + return fmt.Errorf("%T did not record a ModCDP extension target", c.Injector) } - extExecutionContextID, err := c.autoSessions.WaitForExecutionContext(c.ExtSessionID, c.Injector.InjectorExecutionContextTimeoutMS) - if err != nil { + if _, err := c.Router.Send("Runtime.enable", map[string]any{}, injectorState.SessionID); err != nil { c.Close() return err } - c.ExtExecutionContextID = extExecutionContextID - if _, err := c.sendMessage("Runtime.addBinding", map[string]any{"name": translate.CustomEventBindingName}, c.ExtSessionID); err != nil { + if _, err := c.Router.Send("Runtime.addBinding", map[string]any{"name": translate.CustomEventBindingName}, injectorState.SessionID); err != nil { c.Close() return err } mirrorUpstreamEvents := true - if c.Client.ClientMirrorUpstreamEvents != nil { - mirrorUpstreamEvents = *c.Client.ClientMirrorUpstreamEvents + if c.Config.ClientConfig.ClientMirrorUpstreamEvents != nil { + mirrorUpstreamEvents = *c.Config.ClientConfig.ClientMirrorUpstreamEvents } if mirrorUpstreamEvents { - if _, err := c.sendMessage("Runtime.addBinding", map[string]any{"name": translate.UpstreamEventBindingName}, c.ExtSessionID); err != nil { + if _, err := c.Router.Send("Runtime.addBinding", map[string]any{"name": translate.UpstreamEventBindingName}, injectorState.SessionID); err != nil { c.Close() return err } } - if c.Server != nil { - customCommands := make([]map[string]any, 0, len(c.CustomCommands)) - for _, command := range c.CustomCommands { - if command.Expression == "" { - continue - } - customCommands = append(customCommands, map[string]any{ - "name": command.Name, - "expression": command.Expression, - "params_schema": command.ParamsSchema, - "result_schema": command.ResultSchema, - }) - } - customEvents := make([]map[string]any, 0, len(c.CustomEvents)) - for _, event := range c.CustomEvents { - customEvents = append(customEvents, map[string]any{ - "name": event.Name, - "event_schema": event.EventSchema, - }) - } - customMiddlewares := make([]map[string]any, 0, len(c.CustomMiddlewares)) - for _, middleware := range c.CustomMiddlewares { - item := map[string]any{ - "phase": middleware.Phase, - "expression": middleware.Expression, - } - if middleware.Name != "" { - item["name"] = middleware.Name - } - customMiddlewares = append(customMiddlewares, item) - } - configureParams := c.serverConfigureParams(customCommands, customEvents, customMiddlewares) - command, err := translate.WrapCommandIfNeeded("Mod.configure", configureParams, c.Client.ClientRoutes, c.ExtSessionID) - if err != nil { - c.Close() - return fmt.Errorf("Mod.configure: %w", err) - } - if _, err := c.sendRaw(command); err != nil { + if c.Config.ServerConfig != nil { + configureParams := c.serverConfigureParams( + c.Types.CustomCommandWireRegistrations(true), + c.Types.CustomEventWireRegistrations(), + customMiddlewaresToMaps(c.Types.CustomMiddlewareWireRegistrations()), + ) + if _, err := c.Send("Mod.configure", configureParams); err != nil { c.Close() return fmt.Errorf("Mod.configure: %w", err) } @@ -700,8 +604,7 @@ func (c *ModCDPClient) Connect() error { connectedAt := time.Now().UnixMilli() c.ConnectTiming = map[string]any{ "started_at": connectStartedAt, - "upstream_mode": c.Upstream.UpstreamMode, - "upstream_endpoint_kind": transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode), + "upstream_mode": c.Config.Upstream.UpstreamMode, "transport_started_at": transportStartedAt, "transport_connected_at": transportConnectedAt, "transport_duration_ms": transportConnectedAt - transportStartedAt, @@ -716,34 +619,41 @@ func (c *ModCDPClient) Connect() error { } func (c *ModCDPClient) connectUpstreamTransport() error { - if c.transport != nil { + if wsTransport, ok := c.Upstream.(*WSUpstreamTransport); ok && wsTransport.Conn != nil { return nil } - if !isKnownLaunchMode(c.Launcher.LauncherMode) { - return fmt.Errorf("unknown launcher.launcher_mode=%s", c.Launcher.LauncherMode) + if !isKnownLaunchMode(c.Config.Launcher.LauncherMode) { + return fmt.Errorf("unknown launcher_mode=%s", c.Config.Launcher.LauncherMode) + } + if !isKnownUpstreamMode(c.Config.Upstream.UpstreamMode) { + return fmt.Errorf("unknown upstream_mode=%s", c.Config.Upstream.UpstreamMode) + } + if !isKnownExtensionMode(c.Config.Injector.InjectorMode) { + return fmt.Errorf("unknown injector.injector_mode=%s", c.Config.Injector.InjectorMode) + } + launcher := c.Launcher + if launcher == nil { + launcher = c.browserLauncher() + c.Launcher = launcher } - if !isKnownUpstreamMode(c.Upstream.UpstreamMode) { - return fmt.Errorf("unknown upstream.upstream_mode=%s", c.Upstream.UpstreamMode) + transport := c.Upstream + if transport == nil { + transport = c.upstreamTransport() } - if !isKnownExtensionMode(c.Injector.InjectorMode) { - return fmt.Errorf("unknown injector.injector_mode=%s", c.Injector.InjectorMode) + injectors := c.extensionInjectors + if len(injectors) == 0 && c.Config.Injector.InjectorMode != "none" { + injectors, selectedInjector := c.extensionInjectorsForConfig() + c.extensionInjectors = injectors + if len(injectors) > 0 { + c.Injector = selectedInjector + } } - launcher := c.browserLauncher() - transport := c.upstreamTransport() - injectors := c.extensionInjectorsForConfig() - c.extensionInjectors = injectors initialTransportConfig := c.upstreamTransportConfig() transport.Update(initialTransportConfig) - launcher.Update(c.Launcher.LauncherOptions) - for _, injector := range injectors { - injector.Update(c.baseExtensionInjectorConfig(nil)) - } + launcher.Update(c.Config.Launcher) for _, injector := range injectors { - injector.Update(launcher.GetInjectorConfig()) - } - for _, injector := range injectors { - injector.Update(transport.GetInjectorConfig()) + injector.Update(c.baseInjectorConfig(nil)) } for _, injector := range injectors { if err := injector.Prepare(); err != nil { @@ -751,76 +661,54 @@ func (c *ModCDPClient) connectUpstreamTransport() error { } } for _, injector := range injectors { - launcher.Update(injector.GetLauncherConfig()) + launcher.Update(injector.ConfigForLauncher()) } for _, injector := range injectors { - transport.Update(injector.GetTransportConfig()) + transport.Update(injector.ConfigForUpstream()) } - launcher.Update(transport.GetLauncherConfig()) - launcher.Update(LaunchOptions{LoopbackCDP: boolPointer(c.serverNeedsLoopbackCDP())}) - transport.Update(launcher.GetTransportConfig()) + launcher.Update(transport.ConfigForLauncher()) + launcher.Update(LauncherConfig{LauncherLocalLoopbackCDP: boolPointer(c.serverNeedsLoopbackCDP())}) + transport.Update(launcher.ConfigForUpstream()) - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindModCDPServer { - if err := transport.Connect(); err != nil { - return err - } - } - if c.Launcher.LauncherMode != "none" { - launched, err := launcher.Launch(LaunchOptions{}) + launchedCDPURL := "" + if c.Config.Launcher.LauncherMode != "none" { + launched, err := launcher.Launch(LauncherConfig{}) if err != nil { _ = transport.Close() return err } - c.launchedBrowser = launched - transport.Update(launcher.GetTransportConfig()) - for _, injector := range injectors { - injector.Update(launcher.GetInjectorConfig()) - } + transport.Update(launcher.ConfigForUpstream()) for _, injector := range injectors { - transport.Update(injector.GetTransportConfig()) + transport.Update(injector.ConfigForUpstream()) } + launchedCDPURL = launched.CDPURL } - launchedCDPURL := "" - if c.launchedBrowser != nil { - launchedCDPURL = c.launchedBrowser.CDPURL - } - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindRawCDP { - if err := transport.Connect(); err != nil { - return err - } + if err := transport.Connect(); err != nil { + return err } - c.transport = transport + c.Upstream = transport transportURL := transportURL(transport) - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindRawCDP { - c.CDPURL = firstNonEmptyString(transportURL, launchedCDPURL) - } else { - c.CDPURL = launchedCDPURL - } - if wsTransport, ok := transport.(*WebSocketUpstreamTransport); ok && wsTransport.URL != "" { + c.CDPURL = firstNonEmptyString(transportURL, launchedCDPURL) + if wsTransport, ok := transport.(*WSUpstreamTransport); ok && wsTransport.URL != "" { // For ws mode, cdp_url has been resolved to the concrete WebSocket CDP endpoint after connect(). - c.Upstream.UpstreamCDPURL = wsTransport.URL + c.Config.Upstream.UpstreamWSCDPURL = wsTransport.URL } serverConfig := map[string]any{} - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindModCDPServer && - launchedCDPURL != "" && - !strings.HasPrefix(launchedCDPURL, "pipe://") { - serverConfig["server_loopback_cdp_url"] = launchedCDPURL - } - for key, value := range launcher.GetServerConfig() { + for key, value := range launcher.ConfigForServer(c.Config.Upstream) { serverConfig[key] = value } - for key, value := range transport.GetServerConfig() { - serverConfig[key] = value - } - if c.Server != nil { - if loopbackCDPURL, _ := serverConfig["server_loopback_cdp_url"].(string); loopbackCDPURL != "" { - initialCDPURL, _ := initialTransportConfig["cdp_url"].(string) - if c.Server.ServerLoopbackCDPURL == "" || - c.Server.ServerLoopbackCDPURL == initialCDPURL || - c.Server.ServerLoopbackCDPURL == launchedCDPURL { - c.Server.ServerLoopbackCDPURL = loopbackCDPURL + if c.Config.ServerConfig != nil { + if upstreamConfig, _ := serverConfig["upstream"].(map[string]any); upstreamConfig != nil { + loopbackCDPURL, _ := upstreamConfig["upstream_ws_cdp_url"].(string) + initialCDPURL, _ := initialTransportConfig["upstream_ws_cdp_url"].(string) + if loopbackCDPURL != "" && + (c.Config.ServerConfig.Upstream.UpstreamWSCDPURL == "" || + c.Config.ServerConfig.Upstream.UpstreamWSCDPURL == initialCDPURL || + c.Config.ServerConfig.Upstream.UpstreamWSCDPURL == launchedCDPURL) { + c.Config.ServerConfig.Upstream.UpstreamMode = "ws" + c.Config.ServerConfig.Upstream.UpstreamWSCDPURL = loopbackCDPURL } } } @@ -828,29 +716,24 @@ func (c *ModCDPClient) connectUpstreamTransport() error { } func (c *ModCDPClient) serverNeedsLoopbackCDP() bool { - if c.Server == nil || c.Server.ServerLoopbackCDPURL != "" { + if c.Config.ServerConfig == nil || c.Config.ServerConfig.Upstream.UpstreamWSCDPURL != "" { return false } - for _, route := range c.Server.ServerRoutes { - if route == "loopback_cdp" { - return true - } - } - return false + return c.Config.ServerConfig.Router.RouterRoutes["*.*"] == "loopback_cdp" } func (c *ModCDPClient) ensureModCDPServerConfigured() error { - if c.Server == nil || c.transport == nil { + if c.Config.ServerConfig == nil || c.Upstream == nil { return nil } - if err := c.transport.WaitForPeer(); err != nil { + if err := c.Upstream.WaitForPeer(); err != nil { return err } - peerGeneration := c.transport.PeerGeneration() + peerGeneration := c.Upstream.PeerGeneration() if peerGeneration == c.configuredPeerGeneration { return nil } - if _, err := c.sendMessage("Mod.configure", c.serverConfigureParams(nil, nil, nil), ""); err != nil { + if _, err := c.Upstream.Send("Mod.configure", c.serverConfigureParams(nil, nil, nil), ""); err != nil { return err } c.configuredPeerGeneration = peerGeneration @@ -859,39 +742,16 @@ func (c *ModCDPClient) ensureModCDPServerConfigured() error { func (c *ModCDPClient) upstreamTransportConfig() map[string]any { return map[string]any{ - "cdp_url": c.Upstream.UpstreamCDPURL, - "upstream_nats_url": c.Upstream.UpstreamNATSURL, - "upstream_nats_subject_prefix": c.Upstream.UpstreamNATSSubjectPrefix, - "upstream_nats_wait_timeout_ms": c.Upstream.UpstreamNATSWaitTimeoutMS, - "upstream_reversews_bind": c.Upstream.UpstreamReverseWSBind, - "upstream_reversews_wait_timeout_ms": c.Upstream.UpstreamReverseWSWaitTimeoutMS, - "upstream_nativemessaging_manifest": c.Upstream.UpstreamNativeMessagingManifest, - "upstream_nativemessaging_manifests": c.Upstream.UpstreamNativeMessagingManifests, - "upstream_nativemessaging_host_name": c.Upstream.UpstreamNativeMessagingHostName, - "upstream_nativemessaging_wait_timeout_ms": c.Upstream.UpstreamNativeMessagingWaitTimeoutMS, - "injector_extension_id": c.Injector.InjectorExtensionID, + "upstream_mode": c.Config.Upstream.UpstreamMode, + "upstream_ws_cdp_url": c.Config.Upstream.UpstreamWSCDPURL, + "upstream_ws_connect_error_settle_timeout_ms": c.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS, + "upstream_cdp_send_timeout_ms": c.Config.Upstream.UpstreamCDPSendTimeoutMS, } } -func (c *ModCDPClient) initializeRawCDPTransport() error { - if _, err := c.sendMessage("Target.setAutoAttach", map[string]any{ - "autoAttach": true, - "waitForDebuggerOnStart": false, - "flatten": true, - }, ""); err != nil { - return err - } - if _, err := c.sendMessage("Target.setDiscoverTargets", map[string]any{"discover": true}, ""); err != nil { - return err - } - return nil -} - -func transportURL(transport upstreamTransportClient) string { - switch typed := transport.(type) { - case *WebSocketUpstreamTransport: - return typed.URL - case *PipeUpstreamTransport: +func transportURL(upstream upstreamTransportClient) string { + switch typed := upstream.(type) { + case *WSUpstreamTransport: return typed.URL default: return "" @@ -908,54 +768,67 @@ func (c *ModCDPClient) serverConfigureParams(customCommands []map[string]any, cu if customMiddlewares == nil { customMiddlewares = []map[string]any{} } - server := map[string]any{ - "server_cdp_send_timeout_ms": c.Client.ClientCDPSendTimeoutMS, - "server_loopback_execution_context_timeout_ms": c.Injector.InjectorExecutionContextTimeoutMS, - "server_ws_connect_error_settle_timeout_ms": c.Upstream.UpstreamWSConnectErrorSettleTimeoutMS, - "server_downstream_client_timeout_ms": maxInt(c.Client.ClientHeartbeatIntervalMS*4, 1_000), + upstream := map[string]any{} + hasUpstreamConfig := false + router := map[string]any{ + "loopback_execution_context_timeout_ms": c.Config.Injector.InjectorExecutionContextTimeoutMS, } - if c.Server != nil { - server["server_loopback_cdp_url"] = c.Server.ServerLoopbackCDPURL - server["server_routes"] = c.Server.ServerRoutes - if c.Server.ServerBrowserToken != "" { - server["server_browser_token"] = c.Server.ServerBrowserToken + clientConfig := map[string]any{ + "client_cdp_send_timeout_ms": c.Config.ClientConfig.ClientCDPSendTimeoutMS, + } + params := map[string]any{} + if c.Config.ServerConfig != nil { + serverUpstream := c.Config.ServerConfig.Upstream + if serverUpstream.UpstreamMode != "" { + upstream["upstream_mode"] = serverUpstream.UpstreamMode } - if c.Server.ServerCDPSendTimeoutMS != 0 { - server["server_cdp_send_timeout_ms"] = c.Server.ServerCDPSendTimeoutMS + if serverUpstream.UpstreamWSCDPURL != "" { + upstream["upstream_ws_cdp_url"] = serverUpstream.UpstreamWSCDPURL } - if c.Server.ServerLoopbackExecutionContextTimeoutMS != 0 { - server["server_loopback_execution_context_timeout_ms"] = c.Server.ServerLoopbackExecutionContextTimeoutMS + if serverUpstream.UpstreamWSConnectErrorSettleTimeoutMS != 0 { + upstream["upstream_ws_connect_error_settle_timeout_ms"] = serverUpstream.UpstreamWSConnectErrorSettleTimeoutMS } - if c.Server.ServerWSConnectErrorSettleTimeoutMS != 0 { - server["server_ws_connect_error_settle_timeout_ms"] = c.Server.ServerWSConnectErrorSettleTimeoutMS + if serverUpstream.UpstreamCDPSendTimeoutMS != 0 { + upstream["upstream_cdp_send_timeout_ms"] = serverUpstream.UpstreamCDPSendTimeoutMS } - if c.Server.ServerDownstreamClientTimeoutMS != 0 { - server["server_downstream_client_timeout_ms"] = c.Server.ServerDownstreamClientTimeoutMS + if len(upstream) > 0 { + hasUpstreamConfig = true } - if c.Server.ServerCloseBrowserOnDownstreamDisconnect != nil { - server["server_close_browser_on_downstream_disconnect"] = *c.Server.ServerCloseBrowserOnDownstreamDisconnect + if c.Config.ServerConfig.Router.RouterRoutes != nil { + router["router_routes"] = c.Config.ServerConfig.Router.RouterRoutes } - for key, value := range c.Server.Options { - server[key] = value + if c.Config.ServerConfig.Router.LoopbackExecutionContextTimeoutMS != 0 { + router["loopback_execution_context_timeout_ms"] = c.Config.ServerConfig.Router.LoopbackExecutionContextTimeoutMS + } + if c.Config.ServerConfig.ClientConfig.ClientCDPSendTimeoutMS != 0 { + clientConfig["client_cdp_send_timeout_ms"] = c.Config.ServerConfig.ClientConfig.ClientCDPSendTimeoutMS + } + downstream := map[string]any{} + if c.Config.ServerConfig.Downstream.DownstreamClientTimeoutMS != 0 { + downstream["downstream_client_timeout_ms"] = c.Config.ServerConfig.Downstream.DownstreamClientTimeoutMS + } + if c.Config.ServerConfig.Downstream.DownstreamCloseBrowserOnDisconnect != nil { + downstream["downstream_close_browser_on_disconnect"] = *c.Config.ServerConfig.Downstream.DownstreamCloseBrowserOnDisconnect + } + if len(downstream) > 0 { + params["downstream"] = downstream + } + if c.Config.ServerConfig.ServerBrowserToken != "" { + params["server_browser_token"] = c.Config.ServerConfig.ServerBrowserToken } } - upstream := map[string]any{"upstream_mode": c.Upstream.UpstreamMode} - if c.Upstream.UpstreamNATSURL != "" { - upstream["upstream_nats_url"] = c.Upstream.UpstreamNATSURL - } - if c.Upstream.UpstreamNATSSubjectPrefix != "" { - upstream["upstream_nats_subject_prefix"] = c.Upstream.UpstreamNATSSubjectPrefix - } - return map[string]any{ - "upstream": upstream, - "client": map[string]any{ - "client_routes": c.Client.ClientRoutes, - }, - "server": server, - "custom_commands": customCommands, - "custom_events": customEvents, - "custom_middlewares": customMiddlewares, + if hasUpstreamConfig { + if _, ok := upstream["upstream_ws_connect_error_settle_timeout_ms"]; !ok { + upstream["upstream_ws_connect_error_settle_timeout_ms"] = c.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS + } + params["upstream"] = upstream } + params["router"] = router + params["client_config"] = clientConfig + params["custom_commands"] = customCommands + params["custom_events"] = customEvents + params["custom_middlewares"] = customMiddlewares + return params } func normalizeModCDPName(name string) (string, error) { @@ -1018,191 +891,12 @@ func allowNativeResultExtensions(schema map[string]any) { } } -func resultUnwrapKeyFromSchema(schema map[string]any) string { - properties, _ := schema["properties"].(map[string]any) - if len(properties) != 1 { - return "" - } - for key := range properties { - return key - } - return "" -} - -func (c *ModCDPClient) setCommandResultSchema(name string, schema map[string]any) { - c.commandResultSchemas[name] = schema - if unwrapKey := resultUnwrapKeyFromSchema(schema); unwrapKey != "" { - c.commandResultUnwrapKeys[name] = unwrapKey - } else { - delete(c.commandResultUnwrapKeys, name) - } -} - -func (c *ModCDPClient) hydrateCustomSurface() { - c.schemaMu.Lock() - defer c.schemaMu.Unlock() - for _, command := range c.CustomCommands { - if command.Name == "" { - continue - } - name, err := normalizeModCDPName(command.Name) - if err != nil { - continue - } - if schema := cloneSchema(command.ParamsSchema); schema != nil { - c.commandParamsSchemas[name] = schema - } - if schema := cloneSchema(command.ResultSchema); schema != nil { - c.setCommandResultSchema(name, schema) - } - } - for _, event := range c.CustomEvents { - if event.Name == "" { - continue - } - name, err := normalizeModCDPName(event.Name) - if err != nil { - continue - } - if schema := cloneSchema(event.EventSchema); schema != nil { - c.eventSchemas[name] = schema - } - } -} - -func (c *ModCDPClient) registerCustomCommandParams(params map[string]any) (string, bool, error) { - rawName, _ := params["name"].(string) - name, err := normalizeModCDPName(rawName) - if err != nil { - return "", false, err - } - c.schemaMu.Lock() - defer c.schemaMu.Unlock() - if rawSchema, exists := params["params_schema"]; exists { - schemaObject, ok := rawSchema.(map[string]any) - if !ok { - return "", false, fmt.Errorf("params_schema must be a JSON Schema object") - } - if schema := cloneSchema(schemaObject); schema != nil { - c.commandParamsSchemas[name] = schema - } - } - if rawSchema, exists := params["result_schema"]; exists { - schemaObject, ok := rawSchema.(map[string]any) - if !ok { - return "", false, fmt.Errorf("result_schema must be a JSON Schema object") - } - if schema := cloneSchema(schemaObject); schema != nil { - c.setCommandResultSchema(name, schema) - } - } - expression, _ := params["expression"].(string) - return name, expression != "", nil -} - -func (c *ModCDPClient) registerCustomEventParams(params map[string]any) (string, error) { - rawName, _ := params["name"].(string) - name, err := normalizeModCDPName(rawName) - if err != nil { - return "", err - } - c.schemaMu.Lock() - defer c.schemaMu.Unlock() - if rawSchema, exists := params["event_schema"]; exists { - schemaObject, ok := rawSchema.(map[string]any) - if !ok { - return "", fmt.Errorf("event_schema must be a JSON Schema object") - } - if schema := cloneSchema(schemaObject); schema != nil { - c.eventSchemas[name] = schema - } - } - found := false - for index, event := range c.CustomEvents { - if event.Name == name { - found = true - if rawSchema, exists := params["event_schema"]; exists { - if schemaObject, ok := rawSchema.(map[string]any); ok { - c.CustomEvents[index].EventSchema = cloneSchema(schemaObject) - } - } - break - } - } - if !found { - event := CustomEvent{Name: name} - if rawSchema, exists := params["event_schema"]; exists { - if schemaObject, ok := rawSchema.(map[string]any); ok { - event.EventSchema = cloneSchema(schemaObject) - } - } - c.CustomEvents = append(c.CustomEvents, event) - } - return name, nil -} - -func (c *ModCDPClient) validateCommandParams(method string, params map[string]any) error { - c.schemaMu.RLock() - schema := c.commandParamsSchemas[method] - c.schemaMu.RUnlock() - if schema == nil { - return nil - } - if err := abxjsonschema.Validate(schema, params); err != nil { - return fmt.Errorf("%s params did not match params_schema: %w", method, err) - } - return nil -} - -func (c *ModCDPClient) validateCommandResult(method string, result any) error { - c.schemaMu.RLock() - schema := c.commandResultSchemas[method] - c.schemaMu.RUnlock() - if schema == nil { - return nil - } - if err := abxjsonschema.Validate(schema, result); err != nil { - return fmt.Errorf("%s result did not match result_schema: %w", method, err) - } - return nil -} - -func (c *ModCDPClient) validateAndUnwrapCommandResult(method string, result any) (any, error) { - if err := c.validateCommandResult(method, result); err != nil { - return nil, err - } - c.schemaMu.RLock() - unwrapKey := c.commandResultUnwrapKeys[method] - c.schemaMu.RUnlock() - if unwrapKey == "" { - return result, nil - } - resultObject, ok := result.(map[string]any) - if !ok { - return result, nil - } - return resultObject[unwrapKey], nil -} - -func (c *ModCDPClient) validateEventData(event string, data any) (any, bool) { - c.schemaMu.RLock() - schema := c.eventSchemas[event] - c.schemaMu.RUnlock() - if schema == nil { - return data, true - } - if err := abxjsonschema.Validate(schema, data); err != nil { - panic(fmt.Errorf("%s event did not match event_schema: %w", event, err)) - } - return data, true -} - func (c *ModCDPClient) Send(method string, params map[string]any, sessionID ...string) (any, error) { - targetSessionID := "" + cdpSessionID := "" if len(sessionID) > 0 { - targetSessionID = sessionID[0] + cdpSessionID = sessionID[0] } - return c.sendCommand(method, params, targetSessionID, true) + return c.sendCommand(method, params, cdpSessionID, true) } func (d ModDomain) Evaluate(params map[string]any) (any, error) { @@ -1250,61 +944,48 @@ func (d ModDomain) Ping(params map[string]any) (any, error) { return d.client.Send("Mod.ping", params) } -func (c *ModCDPClient) sendCommand(method string, params map[string]any, targetSessionID string, validateSchema bool) (any, error) { +func (d ModDomain) GetTopology(params map[string]any) (any, error) { + return d.client.Send("Mod.getTopology", params) +} + +func (c *ModCDPClient) sendCommand(method string, params map[string]any, cdpSessionID string, validateSchema bool) (any, error) { startedAt := time.Now().UnixMilli() if params == nil { params = map[string]any{} } - if method == "Mod.addCustomCommand" { - name, hasExpression, err := c.registerCustomCommandParams(params) - if err != nil { - return nil, err - } - if !hasExpression { - completedAt := time.Now().UnixMilli() - c.LastCommandTiming = map[string]any{ - "method": method, - "target": "client", - "started_at": startedAt, - "completed_at": completedAt, - "duration_ms": completedAt - startedAt, - } - return map[string]any{"name": name, "registered": true}, nil - } - } else if method == "Mod.addCustomEvent" { - name, err := c.registerCustomEventParams(params) - if err != nil { - return nil, err - } - if c.ExtSessionID == "" && transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) != UpstreamEndpointKindModCDPServer { - completedAt := time.Now().UnixMilli() - c.LastCommandTiming = map[string]any{ - "method": method, - "target": "client", - "started_at": startedAt, - "completed_at": completedAt, - "duration_ms": completedAt - startedAt, - } - return map[string]any{"name": name, "registered": true}, nil - } + preparation, err := c.Types.PrepareCommand( + method, + params, + method == "Mod.addCustomCommand" || ((method == "Mod.addCustomEvent" || method == "Mod.addMiddleware") && (c.Injector == nil || c.Injector.SessionID == "")), + ) + if err != nil { + return nil, err } - if validateSchema { - if err := c.validateCommandParams(method, params); err != nil { - return nil, err + if preparation.LocalResult != nil { + completedAt := time.Now().UnixMilli() + c.LastCommandTiming = map[string]any{ + "method": method, + "target": "client", + "started_at": startedAt, + "completed_at": completedAt, + "duration_ms": completedAt - startedAt, } - } - if transportpkg.EndpointKindForUpstream(c.Upstream.UpstreamMode) == UpstreamEndpointKindModCDPServer { - if method != "Mod.configure" { - if err := c.ensureModCDPServerConfigured(); err != nil { - return nil, err + if validateSchema { + parsed, parseErr := c.Types.ParseCommandResult(method, preparation.LocalResult) + if parseErr != nil { + return nil, parseErr } + return parsed, nil } - rawResult, err := c.sendMessage(method, params, "") - var result any = rawResult + return preparation.LocalResult, nil + } + params = preparation.Params + if c.Config.Injector.InjectorMode == "none" && c.Config.ServerConfig == nil { + result, err := c.Router.Send(method, params, cdpSessionID) completedAt := time.Now().UnixMilli() c.LastCommandTiming = map[string]any{ "method": method, - "target": "modcdp_server", + "target": "browser_targets", "started_at": startedAt, "completed_at": completedAt, "duration_ms": completedAt - startedAt, @@ -1312,23 +993,39 @@ func (c *ModCDPClient) sendCommand(method string, params map[string]any, targetS if err != nil { return nil, err } - if method == "Mod.configure" && c.transport != nil { - c.configuredPeerGeneration = c.transport.PeerGeneration() - } if validateSchema { - var err error - result, err = c.validateAndUnwrapCommandResult(method, result) - if err != nil { - return nil, err + parsed, parseErr := c.Types.ParseCommandResult(method, result) + if parseErr != nil { + return nil, parseErr } + return parsed, nil } return result, nil } - command, err := translate.WrapCommandIfNeeded(method, params, c.Client.ClientRoutes, c.ExtSessionID, targetSessionID) + command, err := translate.WrapCommandIfNeeded(method, params, c.Config.Router.RouterRoutes, cdpSessionID) if err != nil { return nil, err } - result, err := c.sendRaw(command) + var result any + if command.Target == "direct_cdp" { + step := command.Steps[0] + result, err = c.Router.Send(step.Method, step.Params, step.SessionID) + } else if command.Target == "service_worker" { + if c.Injector == nil || c.Injector.SessionID == "" { + return nil, fmt.Errorf("service_worker commands require an injected ModCDP extension target") + } + step, stepErr := c.Types.ServiceWorkerCommandStep(method, params, cdpSessionID, 0) + if stepErr != nil { + return nil, stepErr + } + rawResult, routeErr := c.Router.Send(step.Method, step.Params, c.Injector.SessionID) + if routeErr != nil { + return nil, routeErr + } + result, err = translate.UnwrapResponseIfNeeded(rawResult, step.Unwrap) + } else { + err = fmt.Errorf("unsupported command target %q", command.Target) + } completedAt := time.Now().UnixMilli() c.LastCommandTiming = map[string]any{ "method": method, @@ -1342,7 +1039,7 @@ func (c *ModCDPClient) sendCommand(method string, params map[string]any, targetS } if validateSchema { var err error - result, err = c.validateAndUnwrapCommandResult(method, result) + result, err = c.Types.ParseCommandResult(method, result) if err != nil { return nil, err } @@ -1350,55 +1047,28 @@ func (c *ModCDPClient) sendCommand(method string, params map[string]any, targetS return result, nil } -func (c *ModCDPClient) SendRaw(method string, params map[string]any, sessionID ...string) (map[string]any, error) { - startedAt := time.Now().UnixMilli() - if params == nil { - params = map[string]any{} - } - targetSessionID := "" - if len(sessionID) > 0 { - targetSessionID = sessionID[0] - } - result, err := c.sendMessage(method, params, targetSessionID) - completedAt := time.Now().UnixMilli() - c.LastRawTiming = map[string]any{ - "method": method, - "started_at": startedAt, - "completed_at": completedAt, - "duration_ms": completedAt - startedAt, - } - return result, err -} - -func (c *ModCDPClient) OnRaw(event string, handler Handler) *ModCDPClient { - return c.On(event, handler) -} - -func (c *ModCDPClient) OnCDP(event string, handler func(CDPEvent)) *ModCDPClient { - c.handlersMu.Lock() - defer c.handlersMu.Unlock() - c.cdpHandlers[event] = append(c.cdpHandlers[event], handler) - return c -} - func (c *ModCDPClient) On(event string, handler Handler) *ModCDPClient { + handlerValue := reflect.ValueOf(handler) + if handlerValue.Kind() != reflect.Func { + panic("handler must be a function") + } c.handlersMu.Lock() defer c.handlersMu.Unlock() - pointer := handlerPointer(handler) + pointer := handlerValue.Pointer() for _, existing := range c.handlers[event] { if existing.pointer == pointer { return c } } - c.handlers[event] = append(c.handlers[event], handlerEntry{handler: handler, pointer: pointer}) + c.handlers[event] = append(c.handlers[event], handlerEntry{handler: handlerValue, pointer: pointer}) return c } func (c *ModCDPClient) Once(event string, handler Handler) *ModCDPClient { var wrapped Handler - wrapped = func(data any) { + wrapped = func(args ...any) { c.Off(event, wrapped) - handler(data) + callHandler(reflect.ValueOf(handler), args...) } return c.On(event, wrapped) } @@ -1429,15 +1099,40 @@ func handlerPointer(handler Handler) uintptr { return reflect.ValueOf(handler).Pointer() } +func callHandler(handler reflect.Value, args ...any) { + handlerType := handler.Type() + inputs := make([]reflect.Value, 0, len(args)) + if handlerType.IsVariadic() { + for _, arg := range args { + inputs = append(inputs, reflect.ValueOf(arg)) + } + handler.Call(inputs) + return + } + for index := 0; index < handlerType.NumIn() && index < len(args); index++ { + input := reflect.ValueOf(args[index]) + expected := handlerType.In(index) + if input.IsValid() && input.Type().AssignableTo(expected) { + inputs = append(inputs, input) + continue + } + if input.IsValid() && input.Type().ConvertibleTo(expected) { + inputs = append(inputs, input.Convert(expected)) + continue + } + inputs = append(inputs, reflect.Zero(expected)) + } + handler.Call(inputs) +} + func (c *ModCDPClient) Close() { c.stopHeartbeat() - if c.launchedBrowser != nil { - c.launchedBrowser.Close() - c.launchedBrowser = nil + c.Router.Stop() + if c.Launcher != nil { + c.Launcher.Close() } - if c.transport != nil { - _ = c.transport.Close() - c.transport = nil + if c.Upstream != nil { + _ = c.Upstream.Close() } for _, injector := range c.extensionInjectors { _ = injector.Close() @@ -1445,77 +1140,51 @@ func (c *ModCDPClient) Close() { c.extensionInjectors = nil } -func (c *ModCDPClient) Transport() any { - return c.transport -} - -func (c *ModCDPClient) LaunchedBrowser() *LaunchedBrowser { - return c.launchedBrowser -} - func (c *ModCDPClient) browserLauncher() browserLauncherClient { - switch c.Launcher.LauncherMode { + switch c.Config.Launcher.LauncherMode { case "local": - return NewLocalBrowserLauncher(c.Launcher.LauncherOptions) + return NewLocalBrowserLauncher(c.Config.Launcher) case "remote": - return NewRemoteBrowserLauncher(c.Launcher.LauncherOptions, c.Upstream.UpstreamCDPURL) + return NewRemoteBrowserLauncher(c.Config.Launcher) case "bb": - return NewBrowserbaseBrowserLauncher(c.Launcher.LauncherOptions) + return NewBBBrowserLauncher(c.Config.Launcher) case "none": - return NewNoopBrowserLauncher(c.Launcher.LauncherOptions) + return NewNoneBrowserLauncher(c.Config.Launcher) default: return nil } } func (c *ModCDPClient) upstreamTransport() upstreamTransportClient { - switch c.Upstream.UpstreamMode { + switch c.Config.Upstream.UpstreamMode { case "ws": - return NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{}) - case "pipe": - return NewPipeUpstreamTransport(PipeUpstreamTransportOptions{}) - case "reversews": - return NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{}) - case "nativemessaging": - return NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{}) - case "nats": - return NewNatsUpstreamTransport(NatsUpstreamTransportOptions{}) + return NewWSUpstreamTransport(c.Config.Upstream) default: return nil } } -func (c *ModCDPClient) extensionInjectorsForConfig() []extensionInjector { - if c.Injector.InjectorMode == "none" { - return nil +func (c *ModCDPClient) extensionInjectorsForConfig() ([]extensionInjector, *ExtensionInjector) { + if c.Config.Injector.InjectorMode == "none" { + return nil, nil } - var injectors []extensionInjector - preferLaunchInjection := c.Injector.InjectorMode == "auto" && c.Launcher.LauncherMode == "local" - if (c.Injector.InjectorMode == "auto" || c.Injector.InjectorMode == "discover") && !preferLaunchInjection { - injector := NewDiscoveredExtensionInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) + if c.Config.Injector.InjectorMode == "cli" { + injector := NewCLIExtensionInjector(InjectorConfig{}) + return []extensionInjector{&injector}, &injector.ExtensionInjector } - if c.Injector.InjectorMode == "auto" || c.Injector.InjectorMode == "inject" { - if c.Launcher.LauncherMode == "bb" { - injector := NewBBBrowserExtensionInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) - } - if c.Launcher.LauncherMode == "local" { - injector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) - } - injector := NewExtensionsLoadUnpackedInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) + if c.Config.Injector.InjectorMode == "cdp" { + injector := NewCDPExtensionInjector(InjectorConfig{}) + return []extensionInjector{&injector}, &injector.ExtensionInjector } - if preferLaunchInjection { - injector := NewDiscoveredExtensionInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) + if c.Config.Injector.InjectorMode == "bb" { + injector := NewBBExtensionInjector(InjectorConfig{}) + return []extensionInjector{&injector}, &injector.ExtensionInjector } - if c.Injector.InjectorMode == "auto" || c.Injector.InjectorMode == "borrow" { - injector := NewBorrowedExtensionInjector(ExtensionInjectorConfig{}) - injectors = append(injectors, &injector) + if c.Config.Injector.InjectorMode == "discover" { + injector := NewDiscoverExtensionInjector(InjectorConfig{}) + return []extensionInjector{&injector}, &injector.ExtensionInjector } - return injectors + return nil, nil } func isKnownLaunchMode(mode string) bool { @@ -1523,55 +1192,54 @@ func isKnownLaunchMode(mode string) bool { } func isKnownUpstreamMode(mode string) bool { - return mode == "ws" || mode == "pipe" || mode == "nativemessaging" || mode == "reversews" || mode == "nats" + return mode == "ws" } func isKnownExtensionMode(mode string) bool { - return mode == "auto" || mode == "discover" || mode == "inject" || mode == "borrow" || mode == "none" + return mode == "cli" || mode == "cdp" || mode == "bb" || mode == "discover" || mode == "none" } -func (c *ModCDPClient) baseExtensionInjectorConfig(send SendCDP) ExtensionInjectorConfig { +func (c *ModCDPClient) baseInjectorConfig(send SendCDP) InjectorConfig { trustMatchedServiceWorker := c.trustServiceWorkerTarget() - var attachToTarget AttachToTarget - if send != nil { - attachToTarget = func(targetID string) string { - return c.ensureSessionIDForTarget(targetID, time.Duration(c.Injector.InjectorServiceWorkerProbeTimeoutMS)*time.Millisecond, true) - } - } - return ExtensionInjectorConfig{ - Send: send, - SessionIDForTarget: func(targetID string) string { return c.autoSessions.SessionIDForTarget(targetID) }, - AttachToTarget: attachToTarget, - WaitForExecutionContext: func(sessionID string, timeoutMS int) int { - contextID, _ := c.autoSessions.WaitForExecutionContext(sessionID, timeoutMS) - return contextID - }, - InjectorExtensionPath: c.Injector.InjectorExtensionPath, - InjectorExtensionID: c.Injector.InjectorExtensionID, - InjectorServiceWorkerURLIncludes: c.Injector.InjectorServiceWorkerURLIncludes, - InjectorServiceWorkerURLSuffixes: c.Injector.InjectorServiceWorkerURLSuffixes, + return InjectorConfig{ + Send: send, + InjectorCLIExtensionPath: c.Config.Injector.InjectorCLIExtensionPath, + InjectorCLIExtensionID: c.Config.Injector.InjectorCLIExtensionID, + InjectorCDPExtensionPath: c.Config.Injector.InjectorCDPExtensionPath, + InjectorCDPExtensionID: c.Config.Injector.InjectorCDPExtensionID, + InjectorBBExtensionPath: c.Config.Injector.InjectorBBExtensionPath, + InjectorBBExtensionID: c.Config.Injector.InjectorBBExtensionID, + InjectorDiscoverExtensionPath: c.Config.Injector.InjectorDiscoverExtensionPath, + InjectorServiceWorkerExtensionID: c.Config.Injector.InjectorServiceWorkerExtensionID, + InjectorServiceWorkerURLIncludes: c.Config.Injector.InjectorServiceWorkerURLIncludes, + InjectorServiceWorkerURLSuffixes: c.Config.Injector.InjectorServiceWorkerURLSuffixes, InjectorTrustServiceWorkerTarget: trustMatchedServiceWorker, - InjectorRequireServiceWorkerTarget: c.Injector.InjectorRequireServiceWorkerTarget || c.Injector.InjectorMode == "discover", - InjectorServiceWorkerReadyExpression: c.Injector.InjectorServiceWorkerReadyExpression, - InjectorCDPSendTimeoutMS: c.Client.ClientCDPSendTimeoutMS, - InjectorExecutionContextTimeoutMS: c.Injector.InjectorExecutionContextTimeoutMS, - InjectorServiceWorkerProbeTimeoutMS: c.Injector.InjectorServiceWorkerProbeTimeoutMS, - InjectorServiceWorkerReadyTimeoutMS: c.Injector.InjectorServiceWorkerReadyTimeoutMS, - InjectorServiceWorkerPollIntervalMS: c.Injector.InjectorServiceWorkerPollIntervalMS, - InjectorTargetSessionPollIntervalMS: c.Injector.InjectorTargetSessionPollIntervalMS, + InjectorRequireServiceWorkerTarget: c.Config.Injector.InjectorRequireServiceWorkerTarget || c.Config.Injector.InjectorMode == "discover", + InjectorServiceWorkerReadyExpression: c.Config.Injector.InjectorServiceWorkerReadyExpression, + InjectorCDPSendTimeoutMS: c.Config.ClientConfig.ClientCDPSendTimeoutMS, + InjectorExecutionContextTimeoutMS: c.Config.Injector.InjectorExecutionContextTimeoutMS, + InjectorServiceWorkerProbeTimeoutMS: c.Config.Injector.InjectorServiceWorkerProbeTimeoutMS, + InjectorServiceWorkerReadyTimeoutMS: c.Config.Injector.InjectorServiceWorkerReadyTimeoutMS, + InjectorServiceWorkerPollIntervalMS: c.Config.Injector.InjectorServiceWorkerPollIntervalMS, + InjectorTargetSessionPollIntervalMS: c.Config.Injector.InjectorTargetSessionPollIntervalMS, + InjectorBBAPIKey: c.Config.Injector.InjectorBBAPIKey, + InjectorBBBaseURL: c.Config.Injector.InjectorBBBaseURL, } } -func (c *ModCDPClient) injectExtension(injectors []extensionInjector) (*ExtensionInjectionResult, error) { +func (c *ModCDPClient) injectExtension(injectors []extensionInjector) (*ExtensionInjectionResult, *ExtensionInjector, error) { if len(injectors) == 0 { - return nil, fmt.Errorf("injector.injector_mode='none' cannot be used with a raw_cdp upstream") + return nil, nil, fmt.Errorf("injector.injector_mode=none cannot be used with an extension-routed browser upstream") } send := func(method string, params map[string]any, sessionID string) (map[string]any, error) { - return c.sendMessageTimeout(method, params, sessionID, time.Duration(c.Client.ClientCDPSendTimeoutMS)*time.Millisecond) + if c.Upstream == nil { + return nil, fmt.Errorf("ModCDP upstream is not connected") + } + return c.Upstream.Send(method, params, sessionID, time.Duration(c.Config.ClientConfig.ClientCDPSendTimeoutMS)*time.Millisecond) } var errors []string for _, injector := range injectors { - injector.Update(c.baseExtensionInjectorConfig(send)) + injector.Update(c.baseInjectorConfig(send)) if err := injector.Prepare(); err != nil { errors = append(errors, fmt.Sprintf("%T: %v", injector, err)) continue @@ -1582,10 +1250,12 @@ func (c *ModCDPClient) injectExtension(injectors []extensionInjector) (*Extensio continue } if result != nil { - return result, nil + state := injector.RecordInjectionResult(result) + c.Injector = state + return result, state, nil } } - return nil, fmt.Errorf("cannot install, discover, or borrow the ModCDP extension in the running browser.%s", formatInjectorErrors(errors)) + return nil, nil, fmt.Errorf("cannot install or discover the ModCDP extension in the running browser.%s", formatInjectorErrors(errors)) } func formatInjectorErrors(errors []string) string { @@ -1595,53 +1265,6 @@ func formatInjectorErrors(errors []string) string { return "\n\n" + strings.Join(errors, "\n") } -func cloneMap(value map[string]any) map[string]any { - cloned := map[string]any{} - for key, item := range value { - cloned[key] = item - } - return cloned -} - -func (c *ModCDPClient) sendRaw(command translate.RawCommand) (any, error) { - if command.Target == "direct_cdp" { - step := command.Steps[0] - return c.sendMessage(step.Method, step.Params, step.SessionID) - } - if command.Target != "service_worker" { - return nil, fmt.Errorf("unsupported command target %q", command.Target) - } - - var result map[string]any - unwrap := "" - for _, step := range command.Steps { - params := step.Params - if params == nil { - params = map[string]any{} - } - if step.Method == "Runtime.callFunctionOn" { - if _, exists := params["executionContextId"]; !exists { - if c.ExtExecutionContextID == 0 { - contextID, err := c.autoSessions.WaitForExecutionContext(c.ExtSessionID, c.Injector.InjectorExecutionContextTimeoutMS) - if err != nil { - return nil, err - } - c.ExtExecutionContextID = contextID - } - params = cloneMap(params) - params["executionContextId"] = c.ExtExecutionContextID - } - } - r, err := c.sendMessage(step.Method, params, c.ExtSessionID) - if err != nil { - return nil, err - } - result = r - unwrap = step.Unwrap - } - return translate.UnwrapResponseIfNeeded(result, unwrap) -} - func (c *ModCDPClient) measurePingLatency() error { sent_at := time.Now().UnixMilli() ch := make(chan any, 1) @@ -1674,39 +1297,35 @@ func (c *ModCDPClient) measurePingLatency() error { } c.Latency = latency return nil - case <-time.After(time.Duration(c.Client.ClientEventWaitTimeoutMS) * time.Millisecond): + case <-time.After(time.Duration(c.Config.ClientConfig.ClientEventWaitTimeoutMS) * time.Millisecond): return fmt.Errorf("Mod.pong timed out") } } func (c *ModCDPClient) startPingLatencyMeasurement() { - go func() { - _ = c.measurePingLatency() - }() + _ = c.measurePingLatency() } func (c *ModCDPClient) startHeartbeat() { c.stopHeartbeat() - if c.Server == nil || c.Server.ServerCloseBrowserOnDownstreamDisconnect == nil || !*c.Server.ServerCloseBrowserOnDownstreamDisconnect { + if c.Config.ServerConfig == nil || c.Config.ServerConfig.Downstream.DownstreamCloseBrowserOnDisconnect == nil { return } - interval := c.Client.ClientHeartbeatIntervalMS - if interval <= 0 { + if !*c.Config.ServerConfig.Downstream.DownstreamCloseBrowserOnDisconnect { return } stop := make(chan struct{}) c.heartbeatStop = stop + interval := time.Duration(c.Config.ClientConfig.ClientHeartbeatIntervalMS) * time.Millisecond go func() { - ticker := time.NewTicker(time.Duration(interval) * time.Millisecond) + ticker := time.NewTicker(interval) defer ticker.Stop() for { select { - case <-ticker.C: - if _, err := c.Send("Mod.ping", map[string]any{"sent_at": time.Now().UnixMilli()}); err != nil { - return - } case <-stop: return + case <-ticker.C: + _, _ = c.Send("Mod.ping", map[string]any{"sent_at": time.Now().UnixMilli()}) } } }() @@ -1720,13 +1339,6 @@ func (c *ModCDPClient) stopHeartbeat() { c.heartbeatStop = nil } -func maxInt(left int, right int) int { - if left > right { - return left - } - return right -} - func numberAsInt64(value any) (int64, bool) { switch v := value.(type) { case int64: @@ -1740,91 +1352,8 @@ func numberAsInt64(value any) (int64, bool) { } } -func (c *ModCDPClient) sendMessage(method string, params map[string]any, sessionID string) (map[string]any, error) { - return c.sendMessageTimeout(method, params, sessionID, time.Duration(c.Client.ClientCDPSendTimeoutMS)*time.Millisecond) -} - -func (c *ModCDPClient) sendMessageTimeout(method string, params map[string]any, sessionID string, timeout time.Duration) (map[string]any, error) { - c.mu.Lock() - c.nextID++ - id := c.nextID - ch := make(chan map[string]any, 1) - c.pending[id] = ch - c.mu.Unlock() - - msg := map[string]any{"id": id, "method": method, "params": params} - if sessionID != "" { - msg["sessionId"] = sessionID - } - var err error - if c.transport != nil { - err = c.transport.Send(msg) - } else { - err = fmt.Errorf("ModCDP upstream is not connected") - } - if err != nil { - c.mu.Lock() - delete(c.pending, id) - c.mu.Unlock() - return nil, err - } - if timeout <= 0 { - resp := <-ch - if errObj, ok := resp["error"].(map[string]any); ok { - return nil, fmt.Errorf("%s failed: %v", method, errObj["message"]) - } - if r, ok := resp["result"].(map[string]any); ok { - return r, nil - } - return map[string]any{}, nil - } - select { - case <-time.After(timeout): - c.mu.Lock() - delete(c.pending, id) - c.mu.Unlock() - return nil, fmt.Errorf("%s timed out after %s", method, timeout) - case resp := <-ch: - if errObj, ok := resp["error"].(map[string]any); ok { - return nil, fmt.Errorf("%s failed: %v", method, errObj["message"]) - } - if r, ok := resp["result"].(map[string]any); ok { - return r, nil - } - return map[string]any{}, nil - } -} - -func (c *ModCDPClient) rejectAll(err error) { - c.mu.Lock() - pending := c.pending - c.pending = map[int64]chan map[string]any{} - c.mu.Unlock() - for _, ch := range pending { - ch <- map[string]any{"error": map[string]any{"message": fmt.Sprintf("connection closed: %v", err)}} - } -} - func (c *ModCDPClient) handleMessage(msg map[string]any) { - if idF, ok := msg["id"].(float64); ok { - id := int64(idF) - c.mu.Lock() - ch, ok := c.pending[id] - delete(c.pending, id) - c.mu.Unlock() - if ok { - ch <- msg - } - return - } - if id, ok := msg["id"].(int); ok { - c.mu.Lock() - ch, found := c.pending[int64(id)] - delete(c.pending, int64(id)) - c.mu.Unlock() - if found { - ch <- msg - } + if _, ok := msg["id"]; ok { return } c.handleEventMessage(msg) @@ -1834,63 +1363,52 @@ func (c *ModCDPClient) handleEventMessage(msg map[string]any) { method, _ := msg["method"].(string) sessionID, _ := msg["sessionId"].(string) params, _ := msg["params"].(map[string]any) - c.autoSessions.RecordProtocolEvent(method, params, sessionID) - if c.ExtSessionID != "" && sessionID == c.ExtSessionID { - bindingName, _ := params["name"].(string) - if event, data, ok := translate.UnwrapEventIfNeeded(method, params, sessionID, c.ExtSessionID); ok { - validatedData, valid := c.validateEventData(event, data) - if !valid { - return + if c.Injector != nil && c.Injector.SessionID != "" && sessionID == c.Injector.SessionID { + if unwrapped, ok := translate.UnwrapEventIfNeeded(method, params, sessionID, c.Injector.SessionID); ok { + validatedData, err := c.Types.ParseEventPayload(unwrapped.Event, unwrapped.Data) + if err != nil { + panic(err) } c.handlersMu.Lock() - hs := append([]handlerEntry(nil), c.handlers[event]...) - cdpHandlers := append([]func(CDPEvent){}, c.cdpHandlers["*"]...) - cdpHandlers = append(cdpHandlers, c.cdpHandlers[event]...) + hs := append([]handlerEntry(nil), c.handlers[unwrapped.Event]...) c.handlersMu.Unlock() for _, h := range hs { - go h.handler(validatedData) + go callHandler(h.handler, validatedData, unwrapped.SessionID) } - if bindingName == translate.UpstreamEventBindingName { - dataMap, _ := validatedData.(map[string]any) - cdpEvent := CDPEvent{Method: event, Params: dataMap, CDPSessionID: sessionID, SessionID: sessionID} - for _, h := range cdpHandlers { - go h(cdpEvent) - } + c.handlersMu.Lock() + wildcardHandlers := append([]handlerEntry(nil), c.handlers["*"]...) + c.handlersMu.Unlock() + for _, h := range wildcardHandlers { + go callHandler(h.handler, unwrapped.Event, validatedData, unwrapped.SessionID) } } return } if method != "" { - validatedParams, valid := c.validateEventData(method, params) - if !valid { - return - } - validatedParamsMap, _ := validatedParams.(map[string]any) - if validatedParamsMap == nil { - validatedParamsMap = map[string]any{} + validatedParams, err := c.Types.ParseEventPayload(method, params) + if err != nil { + panic(err) } c.handlersMu.Lock() hs := append([]handlerEntry(nil), c.handlers[method]...) - cdpHandlers := append([]func(CDPEvent){}, c.cdpHandlers["*"]...) - cdpHandlers = append(cdpHandlers, c.cdpHandlers[method]...) c.handlersMu.Unlock() for _, h := range hs { - go h.handler(validatedParams) + go callHandler(h.handler, validatedParams, sessionID) } - if len(cdpHandlers) > 0 { - event := CDPEvent{Method: method, Params: validatedParamsMap, CDPSessionID: sessionID, SessionID: sessionID} - for _, h := range cdpHandlers { - go h(event) - } + c.handlersMu.Lock() + wildcardHandlers := append([]handlerEntry(nil), c.handlers["*"]...) + c.handlersMu.Unlock() + for _, h := range wildcardHandlers { + go callHandler(h.handler, method, validatedParams, sessionID) } } } func (c *ModCDPClient) trustServiceWorkerTarget() bool { - if c.Injector.InjectorTrustServiceWorkerTarget || len(c.Injector.InjectorServiceWorkerURLIncludes) > 0 { + if c.Config.Injector.InjectorTrustServiceWorkerTarget || len(c.Config.Injector.InjectorServiceWorkerURLIncludes) > 0 { return true } - for _, suffix := range c.Injector.InjectorServiceWorkerURLSuffixes { + for _, suffix := range c.Config.Injector.InjectorServiceWorkerURLSuffixes { parts := 0 for _, part := range strings.Split(suffix, "/") { if part != "" { @@ -1903,32 +1421,3 @@ func (c *ModCDPClient) trustServiceWorkerTarget() bool { } return false } - -func (c *ModCDPClient) sessionIDForTarget(targetID string, timeout time.Duration) string { - if timeout <= 0 { - return c.autoSessions.SessionIDForTarget(targetID) - } - deadline := time.Now().Add(timeout) - for time.Now().Before(deadline.Add(time.Millisecond)) { - sessionID := c.autoSessions.SessionIDForTarget(targetID) - if sessionID != "" { - return sessionID - } - time.Sleep(time.Duration(c.Injector.InjectorTargetSessionPollIntervalMS) * time.Millisecond) - } - return "" -} - -func (c *ModCDPClient) ensureSessionIDForTarget(targetID string, timeout time.Duration, allowAttach bool) string { - sessionID := c.autoSessions.SessionIDForTarget(targetID) - if sessionID != "" { - return sessionID - } - if allowAttach { - attachedSessionID := c.autoSessions.AttachToTarget(targetID) - if attachedSessionID != "" { - return attachedSessionID - } - } - return c.sessionIDForTarget(targetID, timeout) -} diff --git a/go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go b/go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go index 76fc9435..e124df21 100644 --- a/go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go +++ b/go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.ModCDPClientCustomFlatNamespace.ts +// - ./python/tests/test_ModCDPClientCustomFlatNamespace.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package client import ( @@ -8,69 +14,87 @@ import ( abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" ) -func TestCustomCommandsInstallFlatNamespaceThroughRealServiceWorker(t *testing.T) { +func TestCustomCommandsInstallFlatNamespaceMethodsThroughARealServiceWorker(t *testing.T) { type ParamsSchema struct { - ID string `json:"id"` + ID string `json:"id"` + Suffix string `json:"suffix,omitempty"` } type ResultSchema struct { Success bool `json:"success"` } + type BadResultParamsSchema struct { + ID string `json:"id"` + } extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) if err != nil { t.Fatal(err) } - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(true), - }, + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{UpstreamMode: "ws"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, - Client: ClientConfig{ClientRoutes: map[string]string{ + Router: RouterConfig{RouterRoutes: map[string]string{ "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp", }}, - Server: &ServerConfig{ServerRoutes: map[string]string{"*.*": "loopback_cdp"}}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, + Types: &CDPTypesConfig{ + CustomCommands: []CustomCommand{ + { + Name: "Custom.doSomething", + ParamsSchema: abxjsonschema.SchemaFor[ParamsSchema](), + ResultSchema: abxjsonschema.SchemaFor[ResultSchema](), + Expression: "async ({ id, suffix = '' }) => ({ success: `${id}${suffix}` === 'abcmiddleware' })", + }, + { + Name: "Custom.badResult", + ParamsSchema: abxjsonschema.SchemaFor[BadResultParamsSchema](), + ResultSchema: abxjsonschema.SchemaFor[ResultSchema](), + Expression: "async () => ({ success: 'yes' })", + }, + }, + CustomMiddlewares: []CustomMiddleware{ + { + Name: "Custom.doSomething", + Phase: "request", + Expression: "async (payload, next) => next({ ...payload, suffix: 'middleware' })", + }, + }, + }, }) defer cdp.Close() if err := cdp.Connect(); err != nil { t.Fatal(err) } - registered, err := cdp.Mod.AddCustomCommand(CustomCommand{ - Name: "Custom.doSomething", - ParamsSchema: abxjsonschema.SchemaFor[ParamsSchema](), - ResultSchema: abxjsonschema.SchemaFor[ResultSchema](), - Expression: "async ({ id }) => ({ success: id === 'abc' })", - }) - if err != nil { - t.Fatal(err) - } - registration, ok := registered.(map[string]any) - if !ok || registration["name"] != "Custom.doSomething" || registration["registered"] != true { - t.Fatalf("unexpected custom command registration: %#v", registered) - } result, err := cdp.Send("Custom.doSomething", map[string]any{"id": "abc"}) if err != nil { t.Fatal(err) } - if result != true { + resultMap, ok := result.(map[string]any) + if !ok || resultMap["success"] != true { t.Fatalf("Custom.doSomething = %#v", result) } if _, err := cdp.Send("Custom.doSomething", map[string]any{"id": 123}); err == nil { t.Fatal("expected custom command params schema to reject non-string id") } + if _, err := cdp.Send("Custom.badResult", map[string]any{"id": "abc"}); err == nil { + t.Fatal("expected Custom.badResult result validation error") + } } -func TestCustomEventsValidateRawStringHandlersThroughRealServiceWorker(t *testing.T) { +func TestCustomEventsValidateRawStringHandlersThroughARealServiceWorker(t *testing.T) { type EventSchema struct { Data string `json:"data"` } @@ -79,25 +103,25 @@ func TestCustomEventsValidateRawStringHandlersThroughRealServiceWorker(t *testin if err != nil { t.Fatal(err) } - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(true), - }, + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{UpstreamMode: "ws"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, - Client: ClientConfig{ClientRoutes: map[string]string{ + Router: RouterConfig{RouterRoutes: map[string]string{ "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp", }}, - Server: &ServerConfig{ServerRoutes: map[string]string{"*.*": "loopback_cdp"}}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, }) defer cdp.Close() @@ -123,7 +147,7 @@ func TestCustomEventsValidateRawStringHandlersThroughRealServiceWorker(t *testin } }) if _, err := cdp.Mod.Evaluate(map[string]any{ - "expression": "async () => await globalThis.ModCDP.emit('Custom.someEvent', { data: 'ok' })", + "expression": "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.someEvent', data: { data: 'ok' }, cdpSessionId: null }))", }); err != nil { t.Fatal(err) } @@ -137,104 +161,531 @@ func TestCustomEventsValidateRawStringHandlersThroughRealServiceWorker(t *testin } } -func TestSchemaOnlyAddCustomCommandRegistersWithoutConnection(t *testing.T) { - cdp := New(Options{}) - result, err := cdp.Mod.AddCustomCommand(CustomCommand{ - Name: "Custom.clientOnly", +func TestDynamicCustomCommandEventAndMiddlewareRegistrationValidatesThroughARealServiceWorker(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) + } + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), + }, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + Router: RouterConfig{RouterRoutes: map[string]string{ + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, + }) + defer cdp.Close() + + if err := cdp.Connect(); err != nil { + t.Fatal(err) + } + if result, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.dynamic", ParamsSchema: map[string]any{ "type": "object", - "required": []any{"tabId"}, - "properties": map[string]any{"tabId": map[string]any{"type": "integer"}}, + "properties": map[string]any{"text": map[string]any{"type": "string", "minLength": 1}}, + "required": []any{"text"}, "additionalProperties": false, }, - }) + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + Expression: "async ({ text }) => ({ ok: text === 'live-dynamic' })", + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.dynamic", "registered") + } + if result, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.dynamicBadResult", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"text": map[string]any{"type": "string"}}, + "required": []any{"text"}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + Expression: "async () => ({ ok: 'yes' })", + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.dynamicBadResult", "registered") + } + if result, err := cdp.Mod.AddCustomEvent(CustomEvent{ + Name: "Custom.dynamicReady", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"id": map[string]any{"type": "string", "format": "uuid"}}, + "required": []any{"id"}, + "additionalProperties": false, + }, + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.dynamicReady", "registered") + } + if result, err := cdp.Mod.AddMiddleware(CustomMiddleware{ + Name: "Custom.dynamic", + Phase: "request", + Expression: "async (payload, next) => next({ ...payload, text: `${payload.text}-dynamic` })", + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.dynamic", "request") + } + + result, err := cdp.Send("Custom.dynamic", map[string]any{"text": "live"}) if err != nil { t.Fatal(err) } - registration, ok := result.(map[string]any) - if !ok || registration["name"] != "Custom.clientOnly" || registration["registered"] != true { - t.Fatalf("unexpected schema-only registration result: %#v", result) + resultMap, ok := result.(map[string]any) + if !ok || resultMap["ok"] != true { + t.Fatalf("Custom.dynamic = %#v", result) } - if err := cdp.validateCommandParams("Custom.clientOnly", map[string]any{"tabId": 1}); err != nil { - t.Fatalf("expected registered schema to validate params, got %v", err) + if _, err := cdp.Send("Custom.dynamic", map[string]any{"text": ""}); err == nil { + t.Fatal("expected Custom.dynamic empty text validation error") } - if err := cdp.validateCommandParams("Custom.clientOnly", map[string]any{"tabId": "1"}); err == nil { - t.Fatal("expected registered schema to reject wrong params") + if _, err := cdp.Send("Custom.dynamicBadResult", map[string]any{"text": "live"}); err == nil { + t.Fatal("expected Custom.dynamicBadResult result validation error") + } + if _, err := cdp.Mod.AddMiddleware(CustomMiddleware{Name: "Custom.dynamic", Phase: "after", Expression: "async (payload, next) => next(payload)"}); err == nil { + t.Fatal("expected invalid middleware phase validation error") } -} -func TestTypedCustomCommandRegistrationBuildsSchemas(t *testing.T) { - type ParamsSchema struct { - ID string `json:"id"` + seen := make(chan string, 1) + cdp.On("Custom.dynamicReady", func(any) { seen <- "ready" }) + if _, err := cdp.Mod.Evaluate(map[string]any{ + "expression": "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.dynamicReady', data: { id: '550e8400-e29b-41d4-a716-446655440000' }, cdpSessionId: null }))", + }); err != nil { + t.Fatal(err) } - type ResultSchema struct { - Success bool `json:"success"` + select { + case got := <-seen: + if got != "ready" { + t.Fatalf("Custom.dynamicReady = %q", got) + } + case <-time.After(10 * time.Second): + t.Fatal("timed out waiting for Custom.dynamicReady") } +} - cdp := New(Options{}) - result, err := cdp.Mod.AddCustomCommand(CustomCommand{ - Name: "Custom.doSomething", - ParamsSchema: abxjsonschema.SchemaFor[ParamsSchema](), - ResultSchema: abxjsonschema.SchemaFor[ResultSchema](), - }) +func TestAssignedTypeRegistryValidatesUpdatedCustomCommandEventAndMiddlewareSchemasThroughARealServiceWorker(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) if err != nil { t.Fatal(err) } - registration, ok := result.(map[string]any) - if !ok || registration["name"] != "Custom.doSomething" || registration["registered"] != true { - t.Fatalf("unexpected custom command registration: %#v", result) + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), + }, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + Router: RouterConfig{RouterRoutes: map[string]string{ + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, + }) + cdp.Types = cdp.Types.Update(CDPTypesConfig{ + CustomCommands: []CustomCommand{ + { + Name: "Custom.updated", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"count": map[string]any{"type": "integer", "minimum": 0}}, + "required": []any{"count"}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"done": map[string]any{"type": "boolean"}}, + "required": []any{"done"}, + "additionalProperties": false, + }, + Expression: "async ({ count }) => ({ done: count === 2 })", + }, + { + Name: "Custom.updatedBadResult", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"count": map[string]any{"type": "number"}}, + "required": []any{"count"}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"done": map[string]any{"type": "boolean"}}, + "required": []any{"done"}, + "additionalProperties": false, + }, + Expression: "async () => ({ done: 'yes' })", + }, + }, + CustomEvents: []CustomEvent{{ + Name: "Custom.updatedReady", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ready": map[string]any{"type": "boolean"}}, + "required": []any{"ready"}, + "additionalProperties": false, + }, + }}, + CustomMiddlewares: []CustomMiddleware{{ + Name: "Custom.updated", + Phase: "request", + Expression: "async (payload, next) => next({ ...payload, count: payload.count + 1 })", + }}, + }) + defer cdp.Close() + + if err := cdp.Connect(); err != nil { + t.Fatal(err) } - params, err := cdpParamsMap(ParamsSchema{ID: "abc"}) + result, err := cdp.Send("Custom.updated", map[string]any{"count": 1}) if err != nil { t.Fatal(err) } - if err := cdp.validateCommandParams("Custom.doSomething", params); err != nil { - t.Fatalf("expected typed params schema to validate: %v", err) + resultMap, ok := result.(map[string]any) + if !ok || resultMap["done"] != true { + t.Fatalf("Custom.updated = %#v", result) } - if err := cdp.validateCommandParams("Custom.doSomething", map[string]any{"id": 123}); err == nil { - t.Fatal("expected typed params schema to reject wrong id type") + if _, err := cdp.Send("Custom.updated", map[string]any{"count": -1}); err == nil { + t.Fatal("expected Custom.updated count validation error") } - if err := cdp.validateCommandResult("Custom.doSomething", ResultSchema{Success: true}); err != nil { - t.Fatalf("expected typed result schema to validate: %v", err) + if _, err := cdp.Send("Custom.updatedBadResult", map[string]any{"count": 1}); err == nil { + t.Fatal("expected Custom.updatedBadResult result validation error") } - if err := cdp.validateCommandResult("Custom.doSomething", map[string]any{"success": "yes"}); err == nil { - t.Fatal("expected typed result schema to reject wrong success type") + + seen := make(chan bool, 1) + cdp.On("Custom.updatedReady", func(any) { seen <- true }) + if _, err := cdp.Mod.Evaluate(map[string]any{ + "expression": "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.updatedReady', data: { ready: true }, cdpSessionId: null }))", + }); err != nil { + t.Fatal(err) + } + select { + case got := <-seen: + if !got { + t.Fatal("Custom.updatedReady got false") + } + case <-time.After(10 * time.Second): + t.Fatal("timed out waiting for Custom.updatedReady") } } -func TestTypedCustomEventRegistrationAndHandler(t *testing.T) { - type EventSchema struct { - Data string `json:"data"` +func TestServiceWorkerServerValidatesRegisteredCustomCommandAndEventSchemas(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) } + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), + }, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + Router: RouterConfig{RouterRoutes: map[string]string{ + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, + }) + defer cdp.Close() - cdp := New(Options{}) - result, err := cdp.Mod.AddCustomEvent(CustomEvent{ - Name: "Custom.someEvent", - EventSchema: abxjsonschema.SchemaFor[EventSchema](), + if err := cdp.Connect(); err != nil { + t.Fatal(err) + } + if result, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.double", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"value": map[string]any{"type": "number"}}, + "required": []any{"value"}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"value": map[string]any{"type": "number"}}, + "required": []any{"value"}, + "additionalProperties": false, + }, + Expression: "async (params) => ({ value: params.value * 2 })", + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.double", "registered") + } + if _, err := cdp.Types.ParseCommandParams("Custom.double", map[string]any{"value": 2}); err != nil { + t.Fatalf("expected Custom.double params to validate: %v", err) + } + if _, err := cdp.Types.ParseCommandResult("Custom.double", map[string]any{"value": 4}); err != nil { + t.Fatalf("expected Custom.double result to validate: %v", err) + } + result, err := cdp.Send("Custom.double", map[string]any{"value": 2}) + if err != nil { + t.Fatal(err) + } + resultMap, ok := result.(map[string]any) + if !ok || resultMap["value"] != float64(4) { + t.Fatalf("Custom.double = %#v", result) + } + + if result, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.badResult", + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + Expression: `async () => ({ ok: "yes" })`, + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.badResult", "registered") + } + if _, err := cdp.Send("Custom.badResult", map[string]any{}); err == nil { + t.Fatal("expected Custom.badResult result validation error") + } + + if result, err := cdp.Mod.AddCustomEvent(CustomEvent{ + Name: "Custom.ready", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + }); err != nil { + t.Fatal(err) + } else { + assertRegistration(t, result, "Custom.ready", "registered") + } + if _, err := cdp.Types.ParseEventPayload("Custom.ready", map[string]any{"ok": true}); err != nil { + t.Fatalf("expected Custom.ready event to validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.ready", map[string]any{"ok": "yes"}); err == nil { + t.Fatal("expected Custom.ready event validation to reject string ok") + } + + seen := make(chan bool, 1) + cdp.On("Custom.ready", func(data any) { + event, _ := data.(map[string]any) + if event != nil { + seen <- event["ok"] == true + } + }) + if _, err := cdp.Mod.Evaluate(map[string]any{ + "expression": "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.ready', data: { ok: true }, cdpSessionId: null }))", + }); err != nil { + t.Fatal(err) + } + select { + case got := <-seen: + if !got { + t.Fatal("Custom.ready got false") + } + case <-time.After(10 * time.Second): + t.Fatal("timed out waiting for Custom.ready") + } +} + +func TestSchemaOnlyCustomCommandsRegisterWithoutAWebsocket(t *testing.T) { + cdp := New(Config{}) + result, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.echo", + ParamsSchema: map[string]any{ + "type": "object", + "required": []any{"text"}, + "properties": map[string]any{"text": map[string]any{"type": "string", "minLength": 1}}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "required": []any{"text"}, + "properties": map[string]any{"text": map[string]any{"type": "string"}}, + "additionalProperties": false, + }, }) if err != nil { t.Fatal(err) } registration, ok := result.(map[string]any) - if !ok || registration["name"] != "Custom.someEvent" || registration["registered"] != true { - t.Fatalf("unexpected custom event registration: %#v", result) + if !ok || registration["name"] != "Custom.echo" || registration["registered"] != true { + t.Fatalf("unexpected schema-only registration result: %#v", result) } - seen := make(chan string, 1) - cdp.On("Custom.someEvent", func(data any) { - event := data.(map[string]any) - seen <- event["data"].(string) + if _, err := cdp.Types.ParseCommandParams("Custom.echo", map[string]any{"text": "ok"}); err != nil { + t.Fatalf("expected registered schema to validate params, got %v", err) + } + if _, err := cdp.Types.ParseCommandParams("Custom.echo", map[string]any{"text": ""}); err == nil { + t.Fatal("expected registered schema to reject wrong params") + } + if _, err := cdp.Types.ParseCommandParams("Custom.echo", map[string]any{"text": "ok", "extra": true}); err == nil { + t.Fatal("expected registered schema to reject extra params") + } + if _, err := cdp.Types.ParseCommandResult("Custom.echo", map[string]any{"text": "ok"}); err != nil { + t.Fatalf("expected registered schema to validate result, got %v", err) + } + if _, err := cdp.Types.ParseCommandResult("Custom.echo", map[string]any{"text": 123}); err == nil { + t.Fatal("expected registered schema to reject wrong result") + } +} + +func TestConstructorCustomCommandAndEventSchemasValidateNestedPayloads(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + Types: &CDPTypesConfig{ + CustomCommands: []CustomCommand{{ + Name: "Custom.collect", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "items": map[string]any{ + "type": "array", + "minItems": 1, + "items": map[string]any{ + "type": "object", + "properties": map[string]any{ + "id": map[string]any{"type": "string"}, + "count": map[string]any{"type": "integer", "minimum": 1}, + }, + "required": []any{"id", "count"}, + "additionalProperties": false, + }, + }, + }, + "required": []any{"items"}, + "additionalProperties": false, + }, + }}, + CustomEvents: []CustomEvent{ + { + Name: "Custom.ready", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "url": map[string]any{"type": "string", "pattern": "^https://"}, + "ready": map[string]any{"type": "boolean"}, + }, + "required": []any{"url", "ready"}, + "additionalProperties": false, + }, + }, + {Name: "Custom.count", EventSchema: map[string]any{"type": "integer", "minimum": 1}}, + }, + }, }) - if data, ok := cdp.validateEventData("Custom.someEvent", map[string]any{"data": "ok"}); ok { - for _, entry := range cdp.handlers["Custom.someEvent"] { - entry.handler(data) - } - } else { - t.Fatal("expected valid typed event payload") + + validParams := map[string]any{"items": []any{map[string]any{"id": "a", "count": 1}}} + if _, err := cdp.Types.ParseCommandParams("Custom.collect", validParams); err != nil { + t.Fatalf("expected Custom.collect params to validate: %v", err) + } + if _, err := cdp.Types.ParseCommandParams("Custom.collect", map[string]any{"items": []any{map[string]any{"id": "a", "count": 0}}}); err == nil { + t.Fatal("expected Custom.collect params to reject count below minimum") + } + if _, err := cdp.Types.ParseCommandParams("Custom.collect", map[string]any{"items": []any{}}); err == nil { + t.Fatal("expected Custom.collect params to reject empty items") + } + if _, err := cdp.Types.ParseEventPayload("Custom.ready", map[string]any{"url": "https://example.com", "ready": true}); err != nil { + t.Fatalf("expected Custom.ready event to validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.ready", map[string]any{"url": "http://example.com", "ready": true}); err == nil { + t.Fatal("expected Custom.ready event validation to reject http url") + } + if _, err := cdp.Types.ParseEventPayload("Custom.count", map[string]any{"value": 3}); err != nil { + t.Fatalf("expected Custom.count event to validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.count", map[string]any{"value": 0}); err == nil { + t.Fatal("expected Custom.count event validation to reject zero value") + } +} + +func TestAssignedTypeRegistryUpdatesRuntimeValidationAndAliases(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) + + cdp.Types = cdp.Types.Update(CDPTypesConfig{ + CustomCommands: []CustomCommand{{ + Name: "Custom.later", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"value": map[string]any{"type": "number"}}, + "required": []any{"value"}, + "additionalProperties": false, + }, + ResultSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + }}, + CustomEvents: []CustomEvent{{ + Name: "Custom.laterReady", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"value": map[string]any{"type": "string"}}, + "required": []any{"value"}, + "additionalProperties": false, + }, + }}, + }) + + if _, ok := cdp.Types.CustomCommands["Custom.later"]; !ok { + t.Fatal("expected Custom.later registry entry") + } + if _, err := cdp.Types.ParseCommandParams("Custom.later", map[string]any{"value": 1}); err != nil { + t.Fatalf("expected Custom.later params to validate: %v", err) } - if got := <-seen; got != "ok" { - t.Fatalf("unexpected typed event data %q", got) + if _, err := cdp.Types.ParseCommandResult("Custom.later", map[string]any{"ok": true}); err != nil { + t.Fatalf("expected Custom.later result to validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.laterReady", map[string]any{"value": "ok"}); err != nil { + t.Fatalf("expected Custom.laterReady event to validate: %v", err) } - expectPanic(t, func() { cdp.validateEventData("Custom.someEvent", map[string]any{"data": 123}) }) } func expectPanic(t *testing.T, fn func()) { @@ -246,3 +697,14 @@ func expectPanic(t *testing.T, fn func()) { }() fn() } + +func assertRegistration(t *testing.T, result any, name string, phase string) { + t.Helper() + registration, ok := result.(map[string]any) + if !ok || registration["name"] != name || registration["registered"] != true { + t.Fatalf("unexpected registration result: %#v", result) + } + if phase != "registered" && registration["phase"] != phase { + t.Fatalf("unexpected registration phase: %#v", result) + } +} diff --git a/go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go b/go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go index f91cb10e..3fa934ce 100644 --- a/go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go +++ b/go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.ModCDPClientRoutedDefaultOverrides.ts +// - ./python/tests/test_ModCDPClientRoutedDefaultOverrides.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package client import ( @@ -9,7 +15,7 @@ import ( const getTargetsOverride = ` async (params) => { const [upstream, tabs] = await Promise.all([ - ModCDP.sendLoopback("Target.getTargets", params), + cdp.upstream.send("Target.getTargets", params), chrome.tabs.query({}), ]); @@ -47,7 +53,7 @@ async (payload, next) => { const visit = async value => { if (!value || typeof value !== "object" || seen.has(value)) return; seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { + if (!Array.isArray(value) && typeof value.targetId === "string" && typeof value.type === "string" && value.tabId == null) { const { tabId } = await cdp.send("Custom.tabIdFromTargetId", { targetId: value.targetId }); if (tabId != null) value.tabId = tabId; } @@ -58,21 +64,22 @@ async (payload, next) => { } ` -func TestModCDPClientRoutedDefaultOverrides(t *testing.T) { +func TestServiceWorkerRoutedStandardCDPCommandsAndEventsCanBeTransformed(t *testing.T) { headless := true extensionPath, err := filepath.Abs("../../../dist/extension") if err != nil { t.Fatal(err) } - owner := New(Options{ + owner := New(Config{ Launcher: LauncherConfig{ - LauncherMode: "local", - LauncherOptions: LaunchOptions{Headless: &headless}, + LauncherMode: "local", + LauncherLocalHeadless: &headless, + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{UpstreamMode: "ws"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, @@ -80,24 +87,22 @@ func TestModCDPClientRoutedDefaultOverrides(t *testing.T) { if err := owner.Connect(); err != nil { t.Fatal(err) } - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "remote"}, - Upstream: UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: owner.CDPURL}, + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "remote", LauncherRemoteCDPURL: owner.CDPURL}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws", UpstreamWSCDPURL: owner.CDPURL}, Injector: InjectorConfig{ InjectorMode: "discover", InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, - Client: ClientConfig{ - ClientRoutes: map[string]string{ - "Target.getTargets": "service_worker", - "Target.createTarget": "service_worker", - "Target.setDiscoverTargets": "service_worker", - }, - }, - Server: &ServerConfig{ - ServerLoopbackCDPURL: owner.CDPURL, - ServerRoutes: map[string]string{"*.*": "loopback_cdp"}, + Router: RouterConfig{RouterRoutes: map[string]string{ + "Target.getTargets": "service_worker", + "Target.createTarget": "service_worker", + "Target.setDiscoverTargets": "service_worker", + }}, + ServerConfig: &ServerConfig{ + Upstream: UpstreamTransportConfig{UpstreamWSCDPURL: owner.CDPURL}, + Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}, }, }) defer owner.Close() @@ -109,8 +114,8 @@ func TestModCDPClientRoutedDefaultOverrides(t *testing.T) { if cdp.CDPURL != owner.CDPURL { t.Fatalf("CDPURL = %q, expected %q", cdp.CDPURL, owner.CDPURL) } - if cdp.Server.ServerLoopbackCDPURL != owner.CDPURL { - t.Fatalf("ServerLoopbackCDPURL = %q, expected %q", cdp.Server.ServerLoopbackCDPURL, owner.CDPURL) + if cdp.Config.ServerConfig.Upstream.UpstreamWSCDPURL != owner.CDPURL { + t.Fatalf("server_config upstream cdp url = %q, expected %q", cdp.Config.ServerConfig.Upstream.UpstreamWSCDPURL, owner.CDPURL) } rawTargets, err := cdp.Send("Target.getTargets", nil) @@ -179,6 +184,52 @@ func TestModCDPClientRoutedDefaultOverrides(t *testing.T) { t.Fatal("expected at least one page target to be matched to a chrome.tabs tab id") } + topologyRaw, err := cdp.Mod.GetTopology(nil) + if err != nil { + t.Fatal(err) + } + topology, ok := topologyRaw.(map[string]any) + if !ok { + t.Fatalf("Mod.getTopology returned %T: %#v", topologyRaw, topologyRaw) + } + rootFrameID, ok := topology["rootFrameId"].(string) + if !ok || rootFrameID == "" { + t.Fatalf("Mod.getTopology rootFrameId = %#v", topology["rootFrameId"]) + } + frames, ok := topology["frames"].(map[string]any) + if !ok { + t.Fatalf("Mod.getTopology frames = %T: %#v", topology["frames"], topology["frames"]) + } + if _, ok := frames[rootFrameID]; !ok { + t.Fatalf("Mod.getTopology frames missing rootFrameId %q: %#v", rootFrameID, frames) + } + roots, ok := topology["roots"].(map[string]any) + if !ok { + t.Fatalf("Mod.getTopology roots = %T: %#v", topology["roots"], topology["roots"]) + } + hasDocumentRoot := false + for _, root := range roots { + if rootMap, ok := root.(map[string]any); ok && rootMap["kind"] == "document" { + hasDocumentRoot = true + } + } + if !hasDocumentRoot { + t.Fatalf("Mod.getTopology should include at least one document root: %#v", roots) + } + contexts, ok := topology["contexts"].(map[string]any) + if !ok { + t.Fatalf("Mod.getTopology contexts = %T: %#v", topology["contexts"], topology["contexts"]) + } + hasPiercerContext := false + for _, context := range contexts { + if contextMap, ok := context.(map[string]any); ok && contextMap["world"] == "piercer" { + hasPiercerContext = true + } + } + if !hasPiercerContext { + t.Fatalf("Mod.getTopology should include a piercer execution context: %#v", contexts) + } + if _, err := cdp.Mod.AddCustomEvent(CustomEvent{Name: "Target.targetCreated"}); err != nil { t.Fatal(err) } diff --git a/go/modcdp/client/ModCDPClientTypedEventInference_test.go b/go/modcdp/client/ModCDPClientTypedEventInference_test.go index 7dba1e37..0d0bf6ba 100644 --- a/go/modcdp/client/ModCDPClientTypedEventInference_test.go +++ b/go/modcdp/client/ModCDPClientTypedEventInference_test.go @@ -1,37 +1,41 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.ModCDPClientTypedEventInference.ts +// - ./python/tests/test_ModCDPClientTypedEventInference.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package client import "testing" -func TestTypedCDPEventsWrapRawHandlers(t *testing.T) { - cdp := New(Options{}) - typedEvents := make(chan TargetTargetCreatedEvent, 1) - rawEvents := make(chan any, 1) +func TestTypedCDPEventTokensInferCallbackPayloadsWithoutLocalTypeAliases(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) + seen := make(chan string, 1) cdp.Target.On.TargetCreated(func(event TargetTargetCreatedEvent) { - typedEvents <- event - }) - cdp.On("Target.targetCreated", func(event any) { - rawEvents <- event + seen <- string(event.TargetInfo.TargetID) }) - payload := map[string]any{ - "targetInfo": map[string]any{ - "targetId": "target-1", - "type": "page", - "url": "https://example.com", + cdp.handleEventMessage(map[string]any{ + "method": "Target.targetCreated", + "params": map[string]any{ + "targetInfo": map[string]any{ + "targetId": "target-1", + "type": "page", + "title": "Example", + "url": "https://example.com", + "attached": true, + "canAccessOpener": false, + }, }, - } - for _, entry := range cdp.handlers["Target.targetCreated"] { - entry.handler(payload) - } + }) - typed := <-typedEvents - if typed.TargetID() != "target-1" || typed.TargetInfo.URL != "https://example.com" { - t.Fatalf("unexpected typed event: %#v", typed) - } - raw := <-rawEvents - rawMap, ok := raw.(map[string]any) - if !ok || rawMap["targetInfo"] == nil { - t.Fatalf("unexpected raw event: %#v", raw) + if got := <-seen; got != "target-1" { + t.Fatalf("seen = %q", got) } } diff --git a/go/modcdp/client/ModCDPClient_protocol_validation_test.go b/go/modcdp/client/ModCDPClient_protocol_validation_test.go index 97d0f69e..8aa46545 100644 --- a/go/modcdp/client/ModCDPClient_protocol_validation_test.go +++ b/go/modcdp/client/ModCDPClient_protocol_validation_test.go @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/ModCDPClient_protocol_validation.test.ts +// - ./python/tests/test_ModCDPClient_protocol_validation.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package client import ( @@ -6,24 +12,43 @@ import ( abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" ) -type protocolValidationCustomEchoParams struct { - Text string `json:"text"` +type protocolValidationSumParams struct { + Left int `json:"left"` + Right int `json:"right"` } -type protocolValidationCustomEchoResult struct { - OK bool `json:"ok"` +type protocolValidationSumResult struct { + Value int `json:"value"` +} + +type protocolValidationFinishedEvent struct { + Total int `json:"total"` + Label string `json:"label"` } -type protocolValidationCustomReadyEvent struct { +type protocolValidationDynamicResult struct { OK bool `json:"ok"` } -func TestProtocolValidationCoversNativeMethodsNativeEventsCustomMethodsCustomEventsAndNativeOverrides(t *testing.T) { - cdp := New(Options{}) +type protocolValidationUpdatedResult struct { + Done bool `json:"done"` +} + +type protocolValidationUpdatedReadyEvent struct { + Ready bool `json:"ready"` +} +func TestNativeCDPSchemasValidateMethodParamsReturnValuesAndEventPayloadsStaticallyAndAtRuntime(t *testing.T) { + types := NewCDPTypes(nil, nil, nil) + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) runtimeParams := RuntimeEvaluateParams{Expression: "1 + 1", ReturnByValue: Bool(true)} - runtimeResult := RuntimeEvaluateResult{Result: RuntimeRemoteObject{Type: "number", Value: 2}} - nativeEvent := TargetTargetCreatedEvent{ + runtimeResult := RuntimeEvaluateResult{Result: RuntimeRemoteObject{Type: "number", Value: 2, Description: String("2")}} + targetEvent := TargetTargetCreatedEvent{ TargetInfo: TargetTargetInfo{ TargetID: TargetTargetID("target-1"), Type: "page", @@ -33,105 +58,278 @@ func TestProtocolValidationCoversNativeMethodsNativeEventsCustomMethodsCustomEve CanAccessOpener: false, }, } - customParams := protocolValidationCustomEchoParams{Text: "ok"} - customResult := protocolValidationCustomEchoResult{OK: true} - customEvent := protocolValidationCustomReadyEvent{OK: true} - if err := cdp.validateCommandParams("Runtime.evaluate", mustParamsMap(t, runtimeParams)); err != nil { - t.Fatalf("native Runtime.evaluate params should validate: %v", err) + _ = cdp.Runtime + if _, err := types.ParseCommandParams("Runtime.evaluate", mustParamsMap(t, runtimeParams)); err != nil { + t.Fatalf("Runtime.evaluate params should validate: %v", err) } - if err := cdp.validateCommandResult("Runtime.evaluate", runtimeResult); err != nil { - t.Fatalf("native Runtime.evaluate result should validate: %v", err) + if _, err := types.ParseCommandResult("Runtime.evaluate", runtimeResult); err != nil { + t.Fatalf("Runtime.evaluate result should validate: %v", err) } - if _, ok := cdp.validateEventData("Target.targetCreated", nativeEvent); !ok { - t.Fatal("native Target.targetCreated event should validate") + if _, err := types.ParseEventPayload("Target.targetCreated", targetEvent); err != nil { + t.Fatalf("Target.targetCreated event should validate: %v", err) } - if err := cdp.validateCommandParams("Runtime.evaluate", map[string]any{}); err == nil { + if _, err := types.ParseCommandParams("Runtime.evaluate", map[string]any{"returnByValue": true}); err == nil { t.Fatal("expected Runtime.evaluate params validation to reject missing expression") } - if err := cdp.validateCommandResult("Runtime.evaluate", map[string]any{}); err == nil { + if _, err := types.ParseCommandResult("Runtime.evaluate", map[string]any{}); err == nil { t.Fatal("expected Runtime.evaluate result validation to reject missing result") } - expectPanic(t, func() { cdp.validateEventData("Target.targetCreated", map[string]any{}) }) + if _, err := types.ParseEventPayload("Target.targetCreated", map[string]any{ + "targetInfo": map[string]any{ + "targetId": 1, + "type": "page", + "title": "Example", + "url": "https://example.com", + "attached": false, + "canAccessOpener": false, + }, + }); err == nil { + t.Fatal("expected Target.targetCreated event validation to reject numeric targetId") + } +} - if _, err := cdp.Mod.AddCustomCommand(CustomCommand{ - Name: "Custom.echo", - ParamsSchema: abxjsonschema.SchemaFor[protocolValidationCustomEchoParams](), - ResultSchema: abxjsonschema.SchemaFor[protocolValidationCustomEchoResult](), - }); err != nil { - t.Fatal(err) +func TestModSchemasValidateMethodParamsReturnValuesEventPayloadsAndMiddlewareRegistrationsStaticallyAndAtRuntime(t *testing.T) { + types := NewCDPTypes(nil, nil, nil) + pingParams := map[string]any{"sent_at": 123} + pingResult := map[string]any{"ok": true} + pongEvent := map[string]any{"sent_at": 123, "received_at": 124, "from": "extension-service-worker"} + middlewareParams := map[string]any{ + "name": "Target.getTargets", + "phase": "response", + "expression": "async (payload, next) => next(payload)", } - if _, err := cdp.Mod.AddCustomEvent(CustomEvent{ - Name: "Custom.ready", - EventSchema: abxjsonschema.SchemaFor[protocolValidationCustomReadyEvent](), - }); err != nil { - t.Fatal(err) + middlewareResult := map[string]any{"name": "Target.getTargets", "phase": "response", "registered": true} + + if parsed, err := types.ParseCommandParams("Mod.ping", pingParams); err != nil || parsed["sent_at"] != 123 { + t.Fatalf("Mod.ping params = %#v, %v", parsed, err) + } + if parsed, err := types.ParseCommandResult("Mod.ping", pingResult); err != nil { + t.Fatalf("Mod.ping result should validate: %#v, %v", parsed, err) + } + if parsed, err := types.ParseEventPayload("Mod.pong", pongEvent); err != nil || parsed == nil { + t.Fatalf("Mod.pong event = %#v, %v", parsed, err) + } + if parsed, err := types.ParseCommandParams("Mod.addMiddleware", middlewareParams); err != nil || parsed["phase"] != "response" { + t.Fatalf("Mod.addMiddleware params = %#v, %v", parsed, err) + } + if parsed, err := types.ParseCommandResult("Mod.addMiddleware", middlewareResult); err != nil { + t.Fatalf("Mod.addMiddleware result should validate: %#v, %v", parsed, err) } + if _, err := types.ParseCommandParams("Mod.ping", map[string]any{"sent_at": "123"}); err == nil { + t.Fatal("expected Mod.ping params validation to reject string sent_at") + } + if _, err := types.ParseCommandResult("Mod.ping", map[string]any{"ok": "true"}); err == nil { + t.Fatal("expected Mod.ping result validation to reject string ok") + } + if _, err := types.ParseEventPayload("Mod.pong", map[string]any{"sent_at": 123, "from": "extension-service-worker"}); err == nil { + t.Fatal("expected Mod.pong event validation to reject missing received_at") + } + if _, err := types.ParseCommandParams("Mod.addMiddleware", map[string]any{"name": "Custom.any", "phase": "after", "expression": "async (payload, next) => next(payload)"}); err == nil { + t.Fatal("expected Mod.addMiddleware params validation to reject invalid phase") + } + if _, err := types.ParseCommandResult("Mod.addMiddleware", map[string]any{"name": "Custom.any", "phase": "after", "registered": true}); err == nil { + t.Fatal("expected Mod.addMiddleware result validation to reject invalid phase") + } +} - if err := cdp.validateCommandParams("Custom.echo", mustParamsMap(t, customParams)); err != nil { - t.Fatalf("custom params should validate: %v", err) - } - if err := cdp.validateCommandResult("Custom.echo", customResult); err != nil { - t.Fatalf("custom result should validate: %v", err) - } - if _, ok := cdp.validateEventData("Custom.ready", customEvent); !ok { - t.Fatal("custom event should validate") - } - if err := cdp.validateCommandParams("Custom.echo", map[string]any{"text": 1}); err == nil { - t.Fatal("expected custom params validation to reject wrong text type") - } - if err := cdp.validateCommandResult("Custom.echo", map[string]any{"ok": "yes"}); err == nil { - t.Fatal("expected custom result validation to reject wrong ok type") - } - expectPanic(t, func() { cdp.validateEventData("Custom.ready", map[string]any{"ok": "yes"}) }) - - if _, err := cdp.Mod.AddCustomCommand(CustomCommand{ - Name: "Target.getTargets", - ResultSchema: map[string]any{ - "type": "object", - "properties": map[string]any{ - "targetInfos": map[string]any{ - "type": "array", - "items": map[string]any{ - "type": "object", - "properties": map[string]any{ - "targetId": map[string]any{"type": "string"}, - "type": map[string]any{"type": "string"}, - "title": map[string]any{"type": "string"}, - "url": map[string]any{"type": "string"}, - "attached": map[string]any{"type": "boolean"}, - "canAccessOpener": map[string]any{"type": "boolean"}, - "tabId": map[string]any{"type": "integer"}, - }, - "required": []any{"targetId", "type", "title", "url", "attached", "canAccessOpener"}, - "additionalProperties": true, - }, - }, - }, - "required": []any{"targetInfos"}, - "additionalProperties": true, +func TestConstructorCustomSchemasValidateCommandParamsReturnValuesEventsAndMiddlewareRegistrationsStaticallyAndAtRuntime(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + Types: &CDPTypesConfig{ + CustomCommands: []CustomCommand{{ + Name: "Custom.sum", + ParamsSchema: abxjsonschema.SchemaFor[protocolValidationSumParams](), + ResultSchema: abxjsonschema.SchemaFor[protocolValidationSumResult](), + Expression: "async ({ left, right }) => ({ value: left + right })", + }}, + CustomEvents: []CustomEvent{{ + Name: "Custom.finished", + EventSchema: abxjsonschema.SchemaFor[protocolValidationFinishedEvent](), + }}, + CustomMiddlewares: []CustomMiddleware{{ + Name: "Custom.sum", + Phase: "response", + Expression: "async (payload, next) => next(payload)", + }}, }, - }); err != nil { + }) + + if _, err := cdp.Types.ParseCommandParams("Custom.sum", map[string]any{"left": 1, "right": 2}); err != nil { + t.Fatalf("Custom.sum params should validate: %v", err) + } + if _, err := cdp.Types.ParseCommandResult("Custom.sum", map[string]any{"value": 3}); err != nil { + t.Fatalf("Custom.sum result should validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.finished", map[string]any{"total": 3, "label": "ok"}); err != nil { + t.Fatalf("Custom.finished event should validate: %v", err) + } + if _, err := cdp.Types.ParseCommandParams("Custom.sum", map[string]any{"left": "1", "right": 2}); err == nil { + t.Fatal("expected Custom.sum params validation to reject string left") + } + if _, err := cdp.Types.ParseCommandResult("Custom.sum", map[string]any{"value": "3"}); err == nil { + t.Fatal("expected Custom.sum result validation to reject string value") + } + if _, err := cdp.Types.ParseEventPayload("Custom.finished", map[string]any{"total": "3", "label": "ok"}); err == nil { + t.Fatal("expected Custom.finished event validation to reject string total") + } + middlewares := cdp.Types.CustomMiddlewareWireRegistrations() + if len(middlewares) != 1 || middlewares[0].Name != "Custom.sum" || middlewares[0].Phase != "response" || middlewares[0].Expression != "async (payload, next) => next(payload)" { + t.Fatalf("custom middlewares = %#v", middlewares) + } + expectPanic(t, func() { + NewCDPTypes(nil, nil, []CustomMiddleware{{Name: "Custom.sum", Phase: "after", Expression: "async (payload, next) => next(payload)"}}) + }) +} + +func TestDynamicModRegistrationUpdatesCustomCommandEventAndMiddlewareValidation(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) + + registered, err := cdp.Mod.AddCustomCommand(CustomCommand{ + Name: "Custom.dynamic", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"text": map[string]any{"type": "string", "minLength": 1}}, + "required": []any{"text"}, + "additionalProperties": false, + }, + ResultSchema: abxjsonschema.SchemaFor[protocolValidationDynamicResult](), + }) + if err != nil { + t.Fatal(err) + } + if registration, ok := registered.(map[string]any); !ok || registration["name"] != "Custom.dynamic" || registration["registered"] != true { + t.Fatalf("Custom.dynamic registration = %#v", registered) + } + registered, err = cdp.Mod.AddCustomEvent(CustomEvent{ + Name: "Custom.dynamicReady", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"id": map[string]any{"type": "string", "pattern": "^[0-9a-f-]{36}$"}}, + "required": []any{"id"}, + "additionalProperties": false, + }, + }) + if err != nil { t.Fatal(err) } - if _, err := cdp.Mod.AddCustomEvent(CustomEvent{Name: "Target.targetCreated"}); err != nil { + if registration, ok := registered.(map[string]any); !ok || registration["name"] != "Custom.dynamicReady" || registration["registered"] != true { + t.Fatalf("Custom.dynamicReady registration = %#v", registered) + } + registered, err = cdp.Mod.AddMiddleware(CustomMiddleware{ + Name: "Custom.dynamic", + Phase: "response", + Expression: "async (payload, next) => next(payload)", + }) + if err != nil { t.Fatal(err) } + if registration, ok := registered.(map[string]any); !ok || registration["name"] != "Custom.dynamic" || registration["phase"] != "response" || registration["registered"] != true { + t.Fatalf("Custom.dynamic middleware registration = %#v", registered) + } - extendedTargetInfo := map[string]any{ - "targetId": "target-1", - "type": "page", - "title": "Example", - "url": "https://example.com", - "attached": false, - "canAccessOpener": false, - "tabId": 7, + if _, ok := cdp.Types.CustomCommands["Custom.dynamic"]; !ok { + t.Fatal("expected Custom.dynamic registry entry") + } + if _, err := cdp.Types.ParseCommandParams("Custom.dynamic", map[string]any{"text": "ok"}); err != nil { + t.Fatalf("Custom.dynamic params should validate: %v", err) + } + if _, err := cdp.Types.ParseCommandResult("Custom.dynamic", map[string]any{"ok": true}); err != nil { + t.Fatalf("Custom.dynamic result should validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.dynamicReady", map[string]any{"id": "550e8400-e29b-41d4-a716-446655440000"}); err != nil { + t.Fatalf("Custom.dynamicReady event should validate: %v", err) + } + middlewares := cdp.Types.CustomMiddlewareWireRegistrations() + if len(middlewares) != 1 || middlewares[0].Name != "Custom.dynamic" || middlewares[0].Phase != "response" { + t.Fatalf("custom middlewares = %#v", middlewares) + } + if _, err := cdp.Types.ParseCommandParams("Custom.dynamic", map[string]any{"text": ""}); err == nil { + t.Fatal("expected Custom.dynamic params validation to reject empty text") + } + if _, err := cdp.Types.ParseCommandResult("Custom.dynamic", map[string]any{"ok": "yes"}); err == nil { + t.Fatal("expected Custom.dynamic result validation to reject string ok") + } + if _, err := cdp.Types.ParseEventPayload("Custom.dynamicReady", map[string]any{"id": "nope"}); err == nil { + t.Fatal("expected Custom.dynamicReady event validation to reject invalid uuid") + } + if _, err := cdp.Mod.AddMiddleware(CustomMiddleware{Name: "Custom.dynamic", Phase: "after", Expression: "async (payload, next) => next(payload)"}); err == nil { + t.Fatal("expected invalid middleware phase to fail") + } +} + +func TestClientTypesUpdateReplacesTheRegistryWithExtendedRuntimeValidationAndPreservesStaticCustomAliasesOnTypedClients(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) + updatedTypes := cdp.Types.Update(CDPTypesConfig{ + CustomCommands: []CustomCommand{{ + Name: "Custom.updated", + ParamsSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"count": map[string]any{"type": "integer", "minimum": 1}}, + "required": []any{"count"}, + "additionalProperties": false, + }, + ResultSchema: abxjsonschema.SchemaFor[protocolValidationUpdatedResult](), + }}, + CustomEvents: []CustomEvent{{ + Name: "Custom.updatedReady", + EventSchema: abxjsonschema.SchemaFor[protocolValidationUpdatedReadyEvent](), + }}, + CustomMiddlewares: []CustomMiddleware{{ + Name: "Custom.updated", + Phase: "request", + Expression: "async (payload, next) => next(payload)", + }}, + }) + typedClient := New(Config{ + Launcher: LauncherConfig{LauncherMode: "none"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: InjectorConfig{InjectorMode: "none"}, + ServerConfig: ServerConfigNone, + }) + typedClient.Types = updatedTypes + cdp.Types = updatedTypes + + if _, ok := typedClient.Types.CustomCommands["Custom.updated"]; !ok { + t.Fatal("expected typed client Custom.updated registry entry") + } + if _, ok := cdp.Types.CustomCommands["Custom.updated"]; !ok { + t.Fatal("expected client Custom.updated registry entry") + } + if _, err := cdp.Types.ParseCommandParams("Custom.updated", map[string]any{"count": 1}); err != nil { + t.Fatalf("Custom.updated params should validate: %v", err) + } + if _, err := cdp.Types.ParseCommandResult("Custom.updated", map[string]any{"done": true}); err != nil { + t.Fatalf("Custom.updated result should validate: %v", err) + } + if _, err := cdp.Types.ParseEventPayload("Custom.updatedReady", map[string]any{"ready": true}); err != nil { + t.Fatalf("Custom.updatedReady event should validate: %v", err) + } + middlewares := cdp.Types.CustomMiddlewareWireRegistrations() + if len(middlewares) != 1 || middlewares[0].Name != "Custom.updated" || middlewares[0].Phase != "request" { + t.Fatalf("custom middlewares = %#v", middlewares) + } + if _, err := cdp.Types.ParseCommandParams("Custom.updated", map[string]any{"count": 0}); err == nil { + t.Fatal("expected Custom.updated params validation to reject zero count") } - if err := cdp.validateCommandResult("Target.getTargets", map[string]any{"targetInfos": []any{extendedTargetInfo}}); err != nil { - t.Fatalf("extended native command result should validate: %v", err) + if _, err := cdp.Types.ParseCommandResult("Custom.updated", map[string]any{"done": "true"}); err == nil { + t.Fatal("expected Custom.updated result validation to reject string done") } - if _, ok := cdp.validateEventData("Target.targetCreated", map[string]any{"targetInfo": extendedTargetInfo}); !ok { - t.Fatal("extended native event should validate") + if _, err := cdp.Types.ParseEventPayload("Custom.updatedReady", map[string]any{"ready": "true"}); err == nil { + t.Fatal("expected Custom.updatedReady event validation to reject string ready") } } diff --git a/go/modcdp/client/ModCDPClient_test.go b/go/modcdp/client/ModCDPClient_test.go index 7dad0781..6df3ca84 100644 --- a/go/modcdp/client/ModCDPClient_test.go +++ b/go/modcdp/client/ModCDPClient_test.go @@ -1,12 +1,14 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.ModCDPClient.ts +// - ./python/tests/test_ModCDPClient.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package client import ( - "context" - "encoding/json" - "fmt" "os" "path/filepath" - "reflect" "regexp" "runtime" "sort" @@ -15,39 +17,26 @@ import ( "testing" "time" - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" + transportpkg "github.com/browserbase/modcdp/go/modcdp/transport" ) -func boolPtr(value bool) *bool { - return &value -} - -func TestModCDPClientNormalizesNestedConfigOwners(t *testing.T) { - cdp := New(Options{ +func TestModCDPClientUsesFlatOwnerPrefixedConfig(t *testing.T) { + cdp := New(Config{ Launcher: LauncherConfig{ - LauncherMode: "local", - LauncherExecutablePath: "/tmp/chrome", - LauncherUserDataDir: "/tmp/profile", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(true), - }, + LauncherMode: "local", + LauncherLocalExecutablePath: "/tmp/chrome", + LauncherLocalUserDataDir: "/tmp/profile", + LauncherLocalHeadless: boolPtr(true), }, - Upstream: UpstreamConfig{ + Upstream: UpstreamTransportConfig{ UpstreamMode: "ws", - UpstreamCDPURL: "http://127.0.0.1:9222", - UpstreamNATSWaitTimeoutMS: 345, - UpstreamReverseWSWaitTimeoutMS: 456, - UpstreamNativeMessagingManifest: "/tmp/native-host.json", - UpstreamNativeMessagingManifests: []string{"/tmp/native-host-extra.json"}, - UpstreamNativeMessagingHostName: "com.modcdp.custom", - UpstreamNativeMessagingWaitTimeoutMS: 567, + UpstreamWSCDPURL: "http://127.0.0.1:9222", UpstreamWSConnectErrorSettleTimeoutMS: 321, }, Injector: InjectorConfig{ InjectorMode: "discover", - InjectorExtensionPath: "/tmp/ext", - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + InjectorDiscoverExtensionPath: "/tmp/ext", + InjectorServiceWorkerExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", InjectorServiceWorkerURLIncludes: []string{"modcdp"}, InjectorServiceWorkerURLSuffixes: []string{"/custom/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, @@ -58,109 +47,87 @@ func TestModCDPClientNormalizesNestedConfigOwners(t *testing.T) { InjectorServiceWorkerPollIntervalMS: 76, InjectorTargetSessionPollIntervalMS: 87, }, - Client: ClientConfig{ - ClientRoutes: map[string]string{"*.*": "direct_cdp"}, + Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "direct_cdp"}, LoopbackExecutionContextTimeoutMS: 4321}, + ClientConfig: ClientConfig{ ClientHydrateAliases: boolPtr(false), ClientMirrorUpstreamEvents: boolPtr(false), ClientCDPSendTimeoutMS: 1234, ClientEventWaitTimeoutMS: 2345, + ClientHeartbeatIntervalMS: 3456, }, - Server: &ServerConfig{ - ServerRoutes: map[string]string{"*.*": "loopback_cdp"}, - ServerBrowserToken: "token-1", - ServerCDPSendTimeoutMS: 9876, - ServerLoopbackExecutionContextTimeoutMS: 8765, - ServerWSConnectErrorSettleTimeoutMS: 7654, + ServerConfig: &ServerConfig{ + Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}, + ClientConfig: ClientConfig{ClientCDPSendTimeoutMS: 9876}, + Upstream: UpstreamTransportConfig{UpstreamWSConnectErrorSettleTimeoutMS: 7654}, }, }) - if cdp.Launcher.LauncherOptions.ExecutablePath != "/tmp/chrome" { - t.Fatalf("Launcher.LauncherOptions.ExecutablePath = %q", cdp.Launcher.LauncherOptions.ExecutablePath) - } - if cdp.Launcher.LauncherOptions.UserDataDir != "/tmp/profile" { - t.Fatalf("Launcher.LauncherOptions.UserDataDir = %q", cdp.Launcher.LauncherOptions.UserDataDir) - } - if cdp.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 321 { - t.Fatalf("Upstream.UpstreamWSConnectErrorSettleTimeoutMS = %d", cdp.Upstream.UpstreamWSConnectErrorSettleTimeoutMS) - } - if cdp.Upstream.UpstreamReverseWSWaitTimeoutMS != 456 { - t.Fatalf("Upstream.UpstreamReverseWSWaitTimeoutMS = %d", cdp.Upstream.UpstreamReverseWSWaitTimeoutMS) + if cdp.Config.Launcher.LauncherLocalExecutablePath != "/tmp/chrome" { + t.Fatalf("Launcher.LauncherLocalExecutablePath = %q", cdp.Config.Launcher.LauncherLocalExecutablePath) } - if cdp.Upstream.UpstreamNATSWaitTimeoutMS != 345 { - t.Fatalf("Upstream.UpstreamNATSWaitTimeoutMS = %d", cdp.Upstream.UpstreamNATSWaitTimeoutMS) + if cdp.Config.Launcher.LauncherLocalUserDataDir != "/tmp/profile" { + t.Fatalf("Launcher.LauncherLocalUserDataDir = %q", cdp.Config.Launcher.LauncherLocalUserDataDir) } - if cdp.Upstream.UpstreamNativeMessagingManifest != "/tmp/native-host.json" { - t.Fatalf("Upstream.UpstreamNativeMessagingManifest = %q", cdp.Upstream.UpstreamNativeMessagingManifest) + if cdp.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS != 321 { + t.Fatalf("Upstream.UpstreamWSConnectErrorSettleTimeoutMS = %d", cdp.Config.Upstream.UpstreamWSConnectErrorSettleTimeoutMS) } - if len(cdp.Upstream.UpstreamNativeMessagingManifests) != 1 || cdp.Upstream.UpstreamNativeMessagingManifests[0] != "/tmp/native-host-extra.json" { - t.Fatalf("Upstream.UpstreamNativeMessagingManifests = %#v", cdp.Upstream.UpstreamNativeMessagingManifests) + if cdp.Config.Injector.InjectorExecutionContextTimeoutMS != 4321 { + t.Fatalf("Injector.InjectorExecutionContextTimeoutMS = %d", cdp.Config.Injector.InjectorExecutionContextTimeoutMS) } - if cdp.Upstream.UpstreamNativeMessagingHostName != "com.modcdp.custom" { - t.Fatalf("Upstream.UpstreamNativeMessagingHostName = %q", cdp.Upstream.UpstreamNativeMessagingHostName) + if cdp.Config.Injector.InjectorServiceWorkerProbeTimeoutMS != 5432 { + t.Fatalf("Injector.InjectorServiceWorkerProbeTimeoutMS = %d", cdp.Config.Injector.InjectorServiceWorkerProbeTimeoutMS) } - if cdp.Upstream.UpstreamNativeMessagingWaitTimeoutMS != 567 { - t.Fatalf("Upstream.UpstreamNativeMessagingWaitTimeoutMS = %d", cdp.Upstream.UpstreamNativeMessagingWaitTimeoutMS) + if cdp.Config.Injector.InjectorServiceWorkerReadyTimeoutMS != 6543 { + t.Fatalf("Injector.InjectorServiceWorkerReadyTimeoutMS = %d", cdp.Config.Injector.InjectorServiceWorkerReadyTimeoutMS) } - if cdp.Injector.InjectorExecutionContextTimeoutMS != 4321 { - t.Fatalf("Injector.InjectorExecutionContextTimeoutMS = %d", cdp.Injector.InjectorExecutionContextTimeoutMS) + if cdp.Config.Injector.InjectorServiceWorkerPollIntervalMS != 76 { + t.Fatalf("Injector.InjectorServiceWorkerPollIntervalMS = %d", cdp.Config.Injector.InjectorServiceWorkerPollIntervalMS) } - if cdp.Injector.InjectorServiceWorkerProbeTimeoutMS != 5432 { - t.Fatalf("Injector.InjectorServiceWorkerProbeTimeoutMS = %d", cdp.Injector.InjectorServiceWorkerProbeTimeoutMS) + if cdp.Config.Injector.InjectorTargetSessionPollIntervalMS != 87 { + t.Fatalf("Injector.InjectorTargetSessionPollIntervalMS = %d", cdp.Config.Injector.InjectorTargetSessionPollIntervalMS) } - if cdp.Injector.InjectorServiceWorkerReadyTimeoutMS != 6543 { - t.Fatalf("Injector.InjectorServiceWorkerReadyTimeoutMS = %d", cdp.Injector.InjectorServiceWorkerReadyTimeoutMS) + if cdp.Config.Router.RouterRoutes["*.*"] != "direct_cdp" { + t.Fatalf("Router.RouterRoutes[*.*] = %q", cdp.Config.Router.RouterRoutes["*.*"]) } - if cdp.Injector.InjectorServiceWorkerPollIntervalMS != 76 { - t.Fatalf("Injector.InjectorServiceWorkerPollIntervalMS = %d", cdp.Injector.InjectorServiceWorkerPollIntervalMS) - } - if cdp.Injector.InjectorTargetSessionPollIntervalMS != 87 { - t.Fatalf("Injector.InjectorTargetSessionPollIntervalMS = %d", cdp.Injector.InjectorTargetSessionPollIntervalMS) - } - if cdp.Client.ClientRoutes["*.*"] != "direct_cdp" { - t.Fatalf("Client.ClientRoutes[*.*] = %q", cdp.Client.ClientRoutes["*.*"]) - } - if cdp.Client.ClientHydrateAliases == nil || *cdp.Client.ClientHydrateAliases { - t.Fatalf("Client.ClientHydrateAliases = %#v", cdp.Client.ClientHydrateAliases) + if cdp.Config.ClientConfig.ClientHydrateAliases == nil || *cdp.Config.ClientConfig.ClientHydrateAliases { + t.Fatalf("ClientConfig.ClientHydrateAliases = %#v", cdp.Config.ClientConfig.ClientHydrateAliases) } if _, err := cdp.Browser.GetVersion(); err == nil || !strings.Contains(err.Error(), "client_hydrate_aliases is false") { t.Fatalf("Browser.GetVersion with aliases disabled error = %v", err) } - if cdp.Client.ClientMirrorUpstreamEvents == nil || *cdp.Client.ClientMirrorUpstreamEvents { - t.Fatalf("Client.ClientMirrorUpstreamEvents = %#v", cdp.Client.ClientMirrorUpstreamEvents) + if cdp.Config.ClientConfig.ClientMirrorUpstreamEvents == nil || *cdp.Config.ClientConfig.ClientMirrorUpstreamEvents { + t.Fatalf("ClientConfig.ClientMirrorUpstreamEvents = %#v", cdp.Config.ClientConfig.ClientMirrorUpstreamEvents) } - if cdp.Client.ClientCDPSendTimeoutMS != 1234 { - t.Fatalf("Client.ClientCDPSendTimeoutMS = %d", cdp.Client.ClientCDPSendTimeoutMS) + if cdp.Config.ClientConfig.ClientCDPSendTimeoutMS != 1234 { + t.Fatalf("ClientConfig.ClientCDPSendTimeoutMS = %d", cdp.Config.ClientConfig.ClientCDPSendTimeoutMS) } - if cdp.Client.ClientEventWaitTimeoutMS != 2345 { - t.Fatalf("Client.ClientEventWaitTimeoutMS = %d", cdp.Client.ClientEventWaitTimeoutMS) + if cdp.Config.ClientConfig.ClientEventWaitTimeoutMS != 2345 { + t.Fatalf("ClientConfig.ClientEventWaitTimeoutMS = %d", cdp.Config.ClientConfig.ClientEventWaitTimeoutMS) } - if cdp.UpstreamEndpointKind != UpstreamEndpointKindRawCDP { - t.Fatalf("UpstreamEndpointKind = %q", cdp.UpstreamEndpointKind) + if cdp.Config.ClientConfig.ClientHeartbeatIntervalMS != 3456 { + t.Fatalf("ClientConfig.ClientHeartbeatIntervalMS = %d", cdp.Config.ClientConfig.ClientHeartbeatIntervalMS) } - params := cdp.serverConfigureParams(nil, nil, nil) - clientConfig := params["client"].(map[string]any) - routes := clientConfig["client_routes"].(map[string]string) - serverConfig := params["server"].(map[string]any) - if routes["*.*"] != "direct_cdp" { - t.Fatalf("configure client routes = %#v", routes) + clientConfigConfig := params["client_config"].(map[string]any) + routerConfig := params["router"].(map[string]any) + upstreamConfig := params["upstream"].(map[string]any) + routes := routerConfig["router_routes"].(map[string]string) + if routes["*.*"] != "loopback_cdp" { + t.Fatalf("configure router routes = %#v", routes) } - if serverConfig["server_browser_token"] != "token-1" { - t.Fatalf("configure browser_token = %#v", serverConfig["server_browser_token"]) + if clientConfigConfig["client_cdp_send_timeout_ms"] != 9876 { + t.Fatalf("configure cdp_send_timeout_ms = %#v", clientConfigConfig["client_cdp_send_timeout_ms"]) } - if serverConfig["server_cdp_send_timeout_ms"] != 9876 { - t.Fatalf("configure cdp_send_timeout_ms = %#v", serverConfig["server_cdp_send_timeout_ms"]) + if routerConfig["loopback_execution_context_timeout_ms"] != 4321 { + t.Fatalf("configure loopback_execution_context_timeout_ms = %#v", routerConfig["loopback_execution_context_timeout_ms"]) } - if serverConfig["server_loopback_execution_context_timeout_ms"] != 8765 { - t.Fatalf("configure loopback_execution_context_timeout_ms = %#v", serverConfig["server_loopback_execution_context_timeout_ms"]) - } - if serverConfig["server_ws_connect_error_settle_timeout_ms"] != 7654 { - t.Fatalf("configure ws_connect_error_settle_timeout_ms = %#v", serverConfig["server_ws_connect_error_settle_timeout_ms"]) + if upstreamConfig["upstream_ws_connect_error_settle_timeout_ms"] != 7654 { + t.Fatalf("configure ws_connect_error_settle_timeout_ms = %#v", upstreamConfig["upstream_ws_connect_error_settle_timeout_ms"]) } } -func TestModCDPClientDispatchesRootEventsBeforeExtensionSessionAttached(t *testing.T) { - cdp := New(Options{}) +func TestModCDPClientDispatchesRootEventsBeforeExtensionSessionIsAttached(t *testing.T) { + cdp := New(Config{}) seen := make(chan string, 1) cdp.On("Target.targetCreated", func(payload any) { event, _ := payload.(map[string]any) @@ -194,8 +161,7 @@ func TestModCDPClientDispatchesRootEventsBeforeExtensionSessionAttached(t *testi } func TestModCDPClientEventDispatchSnapshotsHandlersWhenOnceRemovesItself(t *testing.T) { - cdp := New(Options{}) - cdp.ExtSessionID = "ext-session" + cdp := New(Config{}) seen := make(chan string, 3) cdp.Once("Target.targetCreated", func(payload any) { seen <- "once" @@ -260,231 +226,111 @@ func TestModCDPClientEventDispatchSnapshotsHandlersWhenOnceRemovesItself(t *test } } -func TestModCDPClientOptionsMarshalToSnakeCaseConfigShape(t *testing.T) { - encoded, err := json.Marshal(Options{ - Launcher: LauncherConfig{ - LauncherMode: "local", - LauncherExecutablePath: "/tmp/chrome", - LauncherUserDataDir: "/tmp/profile", - LauncherOptions: LaunchOptions{ - RemoteDebugging: "pipe", - ChromeReadyTimeoutMS: 45_000, - BrowserbaseAPIKey: "test-key", - BrowserbaseSessionCreateParams: map[string]any{"keepAlive": true}, - }, - }, - Upstream: UpstreamConfig{ - UpstreamMode: "nativemessaging", - UpstreamNATSSubjectPrefix: "modcdp.test", - UpstreamNATSWaitTimeoutMS: 789, - UpstreamReverseWSWaitTimeoutMS: 1_234, - UpstreamNativeMessagingManifest: "/tmp/native.json", - UpstreamNativeMessagingManifests: []string{"/tmp/native-extra.json"}, - UpstreamNativeMessagingHostName: "com.modcdp.custom", - UpstreamNativeMessagingWaitTimeoutMS: 2_345, - UpstreamWSConnectErrorSettleTimeoutMS: 321, - }, - Injector: InjectorConfig{ - InjectorMode: "discover", - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - InjectorRequireServiceWorkerTarget: true, - InjectorServiceWorkerReadyExpression: "Boolean(globalThis.ModCDP)", - InjectorExecutionContextTimeoutMS: 4_321, - }, - Client: ClientConfig{ - ClientRoutes: map[string]string{"*.*": "service_worker"}, - ClientHydrateAliases: boolPtr(false), - ClientMirrorUpstreamEvents: boolPtr(false), - ClientCDPSendTimeoutMS: 987, - }, - Server: &ServerConfig{ - ServerLoopbackCDPURL: "http://127.0.0.1:9222", - }, - CustomCommands: []CustomCommand{{Name: "Custom.echo", Expression: "async () => null"}}, - }) - if err != nil { - t.Fatal(err) - } - raw := string(encoded) - for _, wrong := range []string{ - "Launcher", "ExecutablePath", "RemoteDebugging", "BrowserbaseAPIKey", - "Upstream", "UpstreamNATSSubjectPrefix", "UpstreamNATSWaitTimeoutMS", "UpstreamReverseWSWaitTimeoutMS", "UpstreamNativeMessagingHostName", "UpstreamNativeMessagingWaitTimeoutMS", - "Injector", "InjectorServiceWorkerURLSuffixes", "InjectorTrustServiceWorkerTarget", - "Client", "HydrateAliases", "CustomCommands", - } { - if strings.Contains(raw, wrong) { - t.Fatalf("encoded options leaked Go field name %q in %s", wrong, raw) - } - } - for _, expected := range []string{ - `"launcher"`, - `"launcher_mode"`, - `"launcher_executable_path"`, - `"launcher_user_data_dir"`, - `"launcher_options"`, - `"remote_debugging"`, - `"browserbase_api_key"`, - `"browserbase_session_create_params"`, - `"upstream"`, - `"upstream_mode"`, - `"upstream_nats_subject_prefix"`, - `"upstream_nats_wait_timeout_ms"`, - `"upstream_reversews_wait_timeout_ms"`, - `"upstream_nativemessaging_manifest"`, - `"upstream_nativemessaging_manifests"`, - `"upstream_nativemessaging_host_name"`, - `"upstream_nativemessaging_wait_timeout_ms"`, - `"injector"`, - `"injector_mode"`, - `"injector_service_worker_url_suffixes"`, - `"injector_trust_service_worker_target"`, - `"injector_require_service_worker_target"`, - `"injector_service_worker_ready_expression"`, - `"injector_execution_context_timeout_ms"`, - `"client"`, - `"client_hydrate_aliases"`, - `"client_mirror_upstream_events"`, - `"client_cdp_send_timeout_ms"`, - `"custom_commands"`, - } { - if !strings.Contains(raw, expected) { - t.Fatalf("encoded options missing %s in %s", expected, raw) - } +func TestModCDPClientValidatesNativeCommandParamsBeforeSending(t *testing.T) { + cdp := New(Config{}) + + if _, err := cdp.Send("Runtime.evaluate", map[string]any{}); err == nil || !strings.Contains(err.Error(), "expression") { + t.Fatalf("Runtime.evaluate validation error = %v", err) } } -func TestModCDPClientOptionsUnmarshalNullServerDisablesServerConfig(t *testing.T) { - var options Options - if err := json.Unmarshal([]byte(`{"server": null}`), &options); err != nil { - t.Fatal(err) - } - cdp := New(options) +func TestModCDPClientValidatesNativeAndRegisteredCustomEventsBeforeDispatch(t *testing.T) { + cdp := New(Config{}) + + expectPanic(t, func() { + cdp.handleEventMessage(map[string]any{"method": "Target.targetCreated", "params": map[string]any{}}) + }) - if cdp.Server != nil { - t.Fatalf("Server = %#v", cdp.Server) + if _, err := cdp.Mod.AddCustomEvent(CustomEvent{ + Name: "Custom.ready", + EventSchema: map[string]any{ + "type": "object", + "properties": map[string]any{"ok": map[string]any{"type": "boolean"}}, + "required": []any{"ok"}, + "additionalProperties": false, + }, + }); err != nil { + t.Fatal(err) } + expectPanic(t, func() { + cdp.handleEventMessage(map[string]any{"method": "Custom.ready", "params": map[string]any{"ok": "yes"}}) + }) } func TestModCDPClientPreservesExplicitEmptyServiceWorkerSuffixConfig(t *testing.T) { - cdp := New(Options{ + cdp := New(Config{ Injector: InjectorConfig{ - InjectorMode: "borrow", + InjectorMode: "discover", InjectorServiceWorkerURLSuffixes: []string{}, }, }) - if len(cdp.Injector.InjectorServiceWorkerURLSuffixes) != 0 { - t.Fatalf("InjectorServiceWorkerURLSuffixes = %#v", cdp.Injector.InjectorServiceWorkerURLSuffixes) + if len(cdp.Config.Injector.InjectorServiceWorkerURLSuffixes) != 0 { + t.Fatalf("InjectorServiceWorkerURLSuffixes = %#v", cdp.Config.Injector.InjectorServiceWorkerURLSuffixes) } - injectorConfig := cdp.baseExtensionInjectorConfig(nil) + injectorConfig := cdp.baseInjectorConfig(nil) if len(injectorConfig.InjectorServiceWorkerURLSuffixes) != 0 { t.Fatalf("injector InjectorServiceWorkerURLSuffixes = %#v", injectorConfig.InjectorServiceWorkerURLSuffixes) } } -func TestModCDPClientPreservesExplicitNoneServerConfig(t *testing.T) { - cdp := New(Options{Server: ServerNone}) - - if cdp.Server != nil { - t.Fatalf("Server = %#v", cdp.Server) - } -} +func TestModCDPClientPreservesExplicitNullServerConfig(t *testing.T) { + cdp := New(Config{ServerConfig: ServerConfigNone}) -func TestModCDPClientAllowsDisabledServerWithModCDPServerUpstreams(t *testing.T) { - for _, mode := range []string{"nativemessaging", "reversews", "nats"} { - cdp := New(Options{ - Upstream: UpstreamConfig{UpstreamMode: mode}, - Server: ServerNone, - }) - if cdp.Server != nil { - t.Fatalf("%s Server = %#v", mode, cdp.Server) - } - if cdp.UpstreamEndpointKind != UpstreamEndpointKindModCDPServer { - t.Fatalf("%s UpstreamEndpointKind = %q", mode, cdp.UpstreamEndpointKind) - } + if cdp.Config.ServerConfig != nil { + t.Fatalf("ServerConfig = %#v", cdp.Config.ServerConfig) } } -func TestModCDPClientDefaultsServiceWorkerSuffixConfigToModCDPWorker(t *testing.T) { - cdp := New(Options{}) +func TestModCDPClientDefaultsServiceWorkerSuffixConfigToTheModCDPWorker(t *testing.T) { + cdp := New(Config{Injector: InjectorConfig{InjectorMode: "discover"}}) - if len(cdp.Injector.InjectorServiceWorkerURLSuffixes) != 1 || cdp.Injector.InjectorServiceWorkerURLSuffixes[0] != "/modcdp/service_worker.js" { - t.Fatalf("InjectorServiceWorkerURLSuffixes = %#v", cdp.Injector.InjectorServiceWorkerURLSuffixes) + if len(cdp.Config.Injector.InjectorServiceWorkerURLSuffixes) != 1 || cdp.Config.Injector.InjectorServiceWorkerURLSuffixes[0] != "/modcdp/service_worker.js" { + t.Fatalf("InjectorServiceWorkerURLSuffixes = %#v", cdp.Config.Injector.InjectorServiceWorkerURLSuffixes) } - injectorConfig := cdp.baseExtensionInjectorConfig(nil) + injectorConfig := cdp.baseInjectorConfig(nil) if len(injectorConfig.InjectorServiceWorkerURLSuffixes) != 1 || injectorConfig.InjectorServiceWorkerURLSuffixes[0] != "/modcdp/service_worker.js" { t.Fatalf("injector InjectorServiceWorkerURLSuffixes = %#v", injectorConfig.InjectorServiceWorkerURLSuffixes) } } -func TestModCDPClientDefaultsLaunchedModCDPServerUpstreamsToExtensionAuto(t *testing.T) { - for _, mode := range []string{"nativemessaging", "reversews", "nats"} { - launched := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local"}, - Upstream: UpstreamConfig{UpstreamMode: mode}, - }) - if launched.Launcher.LauncherMode != "local" { - t.Fatalf("%s launched Launcher.LauncherMode = %q", mode, launched.Launcher.LauncherMode) - } - if endpointKindForUpstream(launched.Upstream.UpstreamMode) != UpstreamEndpointKindModCDPServer { - t.Fatalf("%s launched endpoint kind = %q", mode, endpointKindForUpstream(launched.Upstream.UpstreamMode)) - } - if launched.UpstreamEndpointKind != UpstreamEndpointKindModCDPServer { - t.Fatalf("%s launched UpstreamEndpointKind = %q", mode, launched.UpstreamEndpointKind) - } - if launched.Injector.InjectorMode != "auto" { - t.Fatalf("%s launched Injector.InjectorMode = %q", mode, launched.Injector.InjectorMode) - } - - attachOnly := New(Options{ - Upstream: UpstreamConfig{UpstreamMode: mode}, - }) - if attachOnly.Launcher.LauncherMode != "none" { - t.Fatalf("%s attach-only Launcher.LauncherMode = %q", mode, attachOnly.Launcher.LauncherMode) - } - if endpointKindForUpstream(attachOnly.Upstream.UpstreamMode) != UpstreamEndpointKindModCDPServer { - t.Fatalf("%s attach-only endpoint kind = %q", mode, endpointKindForUpstream(attachOnly.Upstream.UpstreamMode)) - } - if attachOnly.UpstreamEndpointKind != UpstreamEndpointKindModCDPServer { - t.Fatalf("%s attach-only UpstreamEndpointKind = %q", mode, attachOnly.UpstreamEndpointKind) - } - if attachOnly.Injector.InjectorMode != "none" { - t.Fatalf("%s attach-only Injector.InjectorMode = %q", mode, attachOnly.Injector.InjectorMode) - } +func TestModCDPClientSelectsExactlyOneInjectorFromExplicitInjectorMode(t *testing.T) { + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "local"}, + Injector: InjectorConfig{InjectorMode: "cli"}, + }) + if _, ok := cdp.extensionInjectors[0].(*CLIExtensionInjector); !ok { + t.Fatalf("Injector = %T", cdp.extensionInjectors[0]) + } + if _, ok := New(Config{Launcher: LauncherConfig{LauncherMode: "remote"}, Injector: InjectorConfig{InjectorMode: "cdp"}}).extensionInjectors[0].(*CDPExtensionInjector); !ok { + t.Fatalf("cdp injector type mismatch") + } + if _, ok := New(Config{Launcher: LauncherConfig{LauncherMode: "bb"}, Injector: InjectorConfig{InjectorMode: "bb"}}).extensionInjectors[0].(*BBExtensionInjector); !ok { + t.Fatalf("bb injector type mismatch") + } + if _, ok := New(Config{Launcher: LauncherConfig{LauncherMode: "remote"}, Injector: InjectorConfig{InjectorMode: "discover"}}).extensionInjectors[0].(*DiscoverExtensionInjector); !ok { + t.Fatalf("discover injector type mismatch") } } -func TestModCDPClientOrdersLocalAutoInjectionAsLaunchFlagThenLoadUnpackedFallback(t *testing.T) { - cdp := New(Options{ +func TestModCDPClientUsesNoInjectorUnlessInjectorModeIsExplicit(t *testing.T) { + launched := New(Config{ Launcher: LauncherConfig{LauncherMode: "local"}, - Injector: InjectorConfig{InjectorMode: "auto"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, }) - - got := []string{} - for _, injector := range cdp.extensionInjectorsForConfig() { - switch injector.(type) { - case *LocalBrowserLaunchExtensionInjector: - got = append(got, "LocalBrowserLaunchExtensionInjector") - case *ExtensionsLoadUnpackedInjector: - got = append(got, "ExtensionsLoadUnpackedInjector") - case *DiscoveredExtensionInjector: - got = append(got, "DiscoveredExtensionInjector") - case *BorrowedExtensionInjector: - got = append(got, "BorrowedExtensionInjector") - default: - got = append(got, fmt.Sprintf("%T", injector)) - } + if launched.Config.Launcher.LauncherMode != "local" { + t.Fatalf("launcher mode = %q", launched.Config.Launcher.LauncherMode) } - want := []string{ - "LocalBrowserLaunchExtensionInjector", - "ExtensionsLoadUnpackedInjector", - "DiscoveredExtensionInjector", - "BorrowedExtensionInjector", + if launched.Config.Injector.InjectorMode != "none" { + t.Fatalf("injector mode = %q", launched.Config.Injector.InjectorMode) } - if !reflect.DeepEqual(got, want) { - t.Fatalf("injector order = %#v", got) + + attachOnly := New(Config{Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}}) + if attachOnly.Config.Launcher.LauncherMode != "none" { + t.Fatalf("launcher mode = %q", attachOnly.Config.Launcher.LauncherMode) + } + if attachOnly.Config.Injector.InjectorMode != "none" { + t.Fatalf("injector mode = %q", attachOnly.Config.Injector.InjectorMode) } } @@ -496,22 +342,22 @@ func TestModCDPClientRejectsUnknownComponentModesAtTheirOwningFactoryBoundary(t }{ { name: "upstream", - cdp: New(Options{Upstream: UpstreamConfig{UpstreamMode: "bogus"}}), - want: "unknown upstream.upstream_mode=bogus", + cdp: New(Config{Upstream: UpstreamTransportConfig{UpstreamMode: "bogus"}}), + want: "unknown upstream_mode=bogus", }, { name: "launch", - cdp: New(Options{ + cdp: New(Config{ Launcher: LauncherConfig{LauncherMode: "bogus"}, - Upstream: UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: "ws://127.0.0.1:1/devtools/browser/test"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws", UpstreamWSCDPURL: "ws://127.0.0.1:1/devtools/browser/test"}, }), - want: "unknown launcher.launcher_mode=bogus", + want: "unknown launcher_mode=bogus", }, { name: "injector", - cdp: New(Options{ + cdp: New(Config{ Launcher: LauncherConfig{LauncherMode: "none"}, - Upstream: UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: "ws://127.0.0.1:1/devtools/browser/test"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws", UpstreamWSCDPURL: "ws://127.0.0.1:1/devtools/browser/test"}, Injector: InjectorConfig{InjectorMode: "bogus"}, }), want: "unknown injector.injector_mode=bogus", @@ -524,55 +370,41 @@ func TestModCDPClientRejectsUnknownComponentModesAtTheirOwningFactoryBoundary(t } } -func TestModCDPClientOnlyExposesInjectorAttachAfterCDPSendIsAvailable(t *testing.T) { - cdp := New(Options{}) - disconnectedConfig := cdp.baseExtensionInjectorConfig(nil) - if disconnectedConfig.Send != nil { - t.Fatalf("disconnected Send = %#v", disconnectedConfig.Send) - } - if disconnectedConfig.AttachToTarget != nil { - t.Fatalf("disconnected AttachToTarget = %#v", disconnectedConfig.AttachToTarget) - } - - connectedConfig := cdp.baseExtensionInjectorConfig(func(method string, params map[string]any, sessionID string) (map[string]any, error) { - return map[string]any{}, nil - }) - if connectedConfig.Send == nil { - t.Fatal("connected Send is nil") - } - if connectedConfig.AttachToTarget == nil { - t.Fatal("connected AttachToTarget is nil") - } -} - -func TestModCDPClientConnectsWithLocalLaunchAndInjectorChain(t *testing.T) { +func TestModCDPClientConnectsWithNestedLaunchUpstreamExtensionClientServerConfig(t *testing.T) { headless := runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" extensionPath, err := filepath.Abs("../../../dist/extension") if err != nil { t.Fatal(err) } - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(headless), - ChromeReadyTimeoutMS: 60_000, - }, + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(headless), + LauncherLocalChromeReadyTimeoutMS: 60_000, + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{UpstreamMode: "ws"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, InjectorServiceWorkerProbeTimeoutMS: 30_000, }, - Client: ClientConfig{ - ClientRoutes: map[string]string{"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}, - ClientCDPSendTimeoutMS: 30_000, - ClientEventWaitTimeoutMS: 30_000, + Router: RouterConfig{RouterRoutes: map[string]string{"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + ClientConfig: ClientConfig{ + ClientHydrateAliases: boolPtr(true), + ClientMirrorUpstreamEvents: boolPtr(true), + ClientCDPSendTimeoutMS: 30_000, + ClientEventWaitTimeoutMS: 30_000, }, - Server: &ServerConfig{ - ServerRoutes: map[string]string{"*.*": "loopback_cdp"}, + ServerConfig: &ServerConfig{ + ClientConfig: ClientConfig{ClientCDPSendTimeoutMS: 30_000}, + Router: RouterConfig{ + RouterRoutes: map[string]string{"*.*": "loopback_cdp"}, + LoopbackExecutionContextTimeoutMS: 30_000, + }, + Upstream: UpstreamTransportConfig{UpstreamWSConnectErrorSettleTimeoutMS: 250}, }, }) defer cdp.Close() @@ -581,12 +413,27 @@ func TestModCDPClientConnectsWithLocalLaunchAndInjectorChain(t *testing.T) { t.Fatal(err) } switch cdp.ConnectTiming["injector_source"] { - case "discovered", "local_launch", "extensions_load_unpacked", "borrowed": + case "discover", "cli", "cdp": default: t.Fatalf("injector_source = %v", cdp.ConnectTiming["injector_source"]) } - if cdp.ExtensionID != DefaultModCDPExtensionID { - t.Fatalf("ExtensionID = %q", cdp.ExtensionID) + if cdp.Injector.ExtensionID != DefaultModCDPExtensionID { + t.Fatalf("Injector.ExtensionID = %q", cdp.Injector.ExtensionID) + } + if cdp.Config.Launcher.LauncherMode != "local" { + t.Fatalf("launcher mode = %q", cdp.Config.Launcher.LauncherMode) + } + if cdp.Config.Upstream.UpstreamMode != "ws" { + t.Fatalf("upstream mode = %q", cdp.Config.Upstream.UpstreamMode) + } + if cdp.Config.Injector.InjectorMode != "cli" { + t.Fatalf("injector mode = %q", cdp.Config.Injector.InjectorMode) + } + if cdp.Config.Router.RouterRoutes["*.*"] != "direct_cdp" { + t.Fatalf("router route *.* = %q", cdp.Config.Router.RouterRoutes["*.*"]) + } + if !strings.HasPrefix(cdp.Config.Upstream.UpstreamWSCDPURL, "ws://") { + t.Fatalf("upstream ws url = %q", cdp.Config.Upstream.UpstreamWSCDPURL) } result, err := cdp.Mod.Evaluate(map[string]any{ "expression": "chrome.runtime.getURL('modcdp/service_worker.js')", @@ -597,23 +444,14 @@ func TestModCDPClientConnectsWithLocalLaunchAndInjectorChain(t *testing.T) { if result != "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js" { t.Fatalf("Mod.evaluate = %#v", result) } - contextsRaw, err := cdp.Mod.Evaluate(map[string]any{ - "expression": "chrome.runtime.getContexts({}).then((contexts) => contexts.map((context) => ({ type: context.contextType, url: context.documentUrl || context.origin || '' })))", + offscreenReady, err := cdp.Mod.Evaluate(map[string]any{ + "expression": "chrome.runtime.getContexts({}).then((contexts) => contexts.some((context) => context.contextType === 'OFFSCREEN_DOCUMENT'))", }) if err != nil { t.Fatal(err) } - contexts, _ := contextsRaw.([]any) - foundOffscreen := false - for _, rawContext := range contexts { - context, _ := rawContext.(map[string]any) - if context["type"] == "OFFSCREEN_DOCUMENT" && - context["url"] == "chrome-extension://"+DefaultModCDPExtensionID+"/offscreen/keepalive.html" { - foundOffscreen = true - } - } - if !foundOffscreen { - t.Fatalf("expected offscreen keepalive context, got %#v", contextsRaw) + if offscreenReady != true { + t.Fatalf("expected offscreen keepalive context, got %#v", offscreenReady) } directTargetRaw, err := cdp.Send("Target.createTarget", map[string]any{"url": "about:blank#direct-session-routing"}) if err != nil { @@ -704,44 +542,42 @@ func TestModCDPClientConnectsWithLocalLaunchAndInjectorChain(t *testing.T) { } } -func TestModCDPClientCloseDoesNotCloseRemoteBrowserItDidNotLaunch(t *testing.T) { +func TestModCDPClientCloseDoesNotCloseARemoteBrowserItDidNotLaunch(t *testing.T) { headless := true extensionPath, err := filepath.Abs("../../../dist/extension") if err != nil { t.Fatal(err) } - chrome, err := NewLocalBrowserLauncher(LaunchOptions{ - Headless: &headless, - ChromeReadyTimeoutMS: 60_000, + chrome, err := NewLocalBrowserLauncher(LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalChromeReadyTimeoutMS: 60_000, // This test manually supplies --load-extension, so it intentionally uses // the launch-flag browser path instead of relying on the client fallback. - ExecutablePath: reverseWSTestBrowserPath(t), - ExtraArgs: []string{"--load-extension=" + extensionPath}, - }).Launch(LaunchOptions{}) + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), + LauncherLocalExtraArgs: []string{"--load-extension=" + extensionPath}, + }).Launch(LauncherConfig{}) if err != nil { t.Fatal(err) } defer chrome.Close() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - rawConn, _, _, err := ws.Dial(ctx, chrome.CDPURL) - if err != nil { + cdp_transport := transportpkg.NewWSUpstreamTransport(transportpkg.UpstreamTransportConfig{UpstreamWSCDPURL: chrome.CDPURL}) + if err := cdp_transport.Connect(); err != nil { t.Fatal(err) } - defer rawConn.Close() - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "remote"}, - Upstream: UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: chrome.CDPURL}, + defer cdp_transport.Close() + cdp := New(Config{ + Launcher: LauncherConfig{LauncherMode: "remote", LauncherRemoteCDPURL: chrome.CDPURL}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws", UpstreamWSCDPURL: chrome.CDPURL}, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, InjectorServiceWorkerReadyTimeoutMS: 30_000, InjectorServiceWorkerProbeTimeoutMS: 30_000, }, - Client: ClientConfig{ClientRoutes: map[string]string{"*.*": "direct_cdp"}}, + Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "direct_cdp"}}, }) if err := cdp.Connect(); err != nil { t.Fatal(err) @@ -749,27 +585,13 @@ func TestModCDPClientCloseDoesNotCloseRemoteBrowserItDidNotLaunch(t *testing.T) cdp.Close() time.Sleep(500 * time.Millisecond) - if err := wsutil.WriteClientText(rawConn, []byte(`{"id":1,"method":"Browser.getVersion","params":{}}`)); err != nil { - t.Fatal(err) - } - body, err := wsutil.ReadServerText(rawConn) + response, err := cdp_transport.Send("Browser.getVersion", map[string]any{}, "", 10*time.Second) if err != nil { t.Fatal(err) } - var response struct { - ID int `json:"id"` - Result struct { - Product string `json:"product"` - } `json:"result"` - } - if err := json.Unmarshal(body, &response); err != nil { - t.Fatal(err) - } - if response.ID != 1 { - t.Fatalf("unexpected response id %d", response.ID) - } - if !strings.Contains(response.Result.Product, "Chrome") && !strings.Contains(response.Result.Product, "Chromium") { - t.Fatalf("unexpected product %q", response.Result.Product) + product, _ := response["product"].(string) + if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { + t.Fatalf("unexpected product %q", product) } } @@ -778,49 +600,52 @@ func TestModCDPClientCloseKeepsInjectorFilesUntilAfterLaunchedBrowserShutdown(t if err != nil { t.Fatal(err) } - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(true), - // After explicit CHROME_PATH and CI /usr/bin/chromium, this test uses - // Chrome for Testing because Canary rejects --load-extension in this - // local launch injector path. - ExecutablePath: reverseWSTestBrowserPath(t), - }, + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + // After explicit CHROME_PATH and CI /usr/bin/chromium, this test uses + // Chrome for Testing because Canary rejects --load-extension in this + // local launch injector path. + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{ + Upstream: UpstreamTransportConfig{ UpstreamMode: "ws", }, Injector: InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, - Server: &ServerConfig{ServerRoutes: map[string]string{"*.*": "loopback_cdp"}}, + ServerConfig: &ServerConfig{Router: RouterConfig{RouterRoutes: map[string]string{"*.*": "loopback_cdp"}}}, }) defer cdp.Close() if err := cdp.Connect(); err != nil { t.Fatal(err) } - var localLaunchInjector *LocalBrowserLaunchExtensionInjector + var localLaunchInjector *CLIExtensionInjector for _, injector := range cdp.extensionInjectors { - if typed, ok := injector.(*LocalBrowserLaunchExtensionInjector); ok { + if typed, ok := injector.(*CLIExtensionInjector); ok { localLaunchInjector = typed } } if localLaunchInjector == nil { - t.Fatal("expected LocalBrowserLaunchExtensionInjector") + t.Fatal("expected CLIExtensionInjector") } unpackedExtensionPath := localLaunchInjector.UnpackedExtensionPath if unpackedExtensionPath == extensionPath { t.Fatalf("UnpackedExtensionPath = %q", unpackedExtensionPath) } - originalClose := cdp.launchedBrowser.Close + launcher, ok := cdp.Launcher.(*LocalBrowserLauncher) + if !ok || launcher.Launched == nil { + t.Fatalf("expected local launcher state, got %T", cdp.Launcher) + } + originalClose := launcher.Launched.Close browserCloseSawExtension := false - cdp.launchedBrowser.Close = func() { + launcher.Launched.Close = func() { _, err := os.Stat(unpackedExtensionPath) browserCloseSawExtension = err == nil originalClose() @@ -833,11 +658,8 @@ func TestModCDPClientCloseKeepsInjectorFilesUntilAfterLaunchedBrowserShutdown(t if _, err := os.Stat(unpackedExtensionPath); err == nil { t.Fatalf("expected prepared temp extension files to be cleaned up after close") } - if cdp.transport != nil { - t.Fatal("expected transport to be nil") - } - if cdp.launchedBrowser != nil { - t.Fatal("expected launchedBrowser to be nil") + if launcher.Launched != nil { + t.Fatal("expected launcher launched state to be nil") } if cdp.extensionInjectors != nil { t.Fatal("expected extensionInjectors to be nil") @@ -845,15 +667,20 @@ func TestModCDPClientCloseKeepsInjectorFilesUntilAfterLaunchedBrowserShutdown(t } func TestModCDPClientCloseClearsTopLevelConnectionState(t *testing.T) { - cdp := New(Options{ - Launcher: LauncherConfig{LauncherMode: "local", - LauncherOptions: LaunchOptions{ - Headless: boolPtr(true), - }, + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) + } + cdp := New(Config{ + Launcher: LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: boolPtr(true), + LauncherLocalExecutablePath: reverseWSTestBrowserPath(t), }, - Upstream: UpstreamConfig{UpstreamMode: "ws"}, + Upstream: UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: InjectorConfig{ - InjectorMode: "auto", + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, @@ -861,22 +688,28 @@ func TestModCDPClientCloseClearsTopLevelConnectionState(t *testing.T) { if err := cdp.Connect(); err != nil { t.Fatal(err) } - transport, ok := cdp.transport.(*WebSocketUpstreamTransport) + transport, ok := cdp.Upstream.(*WSUpstreamTransport) if !ok { - t.Fatalf("transport = %T", cdp.transport) + t.Fatalf("transport = %T", cdp.Upstream) } if transport.Conn == nil { t.Fatal("expected transport-owned websocket conn") } cdp.Close() - if cdp.transport != nil { - t.Fatal("Close left transport set") + if transport.Conn != nil { + t.Fatal("Close left websocket connection set") } - if _, err := cdp.SendRaw("Browser.getVersion", map[string]any{}); err == nil || !strings.Contains(err.Error(), "ModCDP upstream is not connected") { - t.Fatalf("SendRaw after close error = %v", err) + if launcher, ok := cdp.Launcher.(*LocalBrowserLauncher); !ok || launcher.Launched != nil { + t.Fatalf("Close left launcher launched state set: %T", cdp.Launcher) } } +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +func boolPtr(value bool) *bool { + return &value +} + func reverseWSTestBrowserPath(t *testing.T) string { t.Helper() explicitCandidates := []string{os.Getenv("CHROME_PATH")} @@ -978,81 +811,3 @@ func maxPathNumber(value string) int { } return maxValue } - -func TestCustomCommandSchemasValidateParamsAndResults(t *testing.T) { - cdp := New(Options{ - CustomCommands: []CustomCommand{ - { - Name: "Custom.echo", - ParamsSchema: map[string]any{ - "type": "object", - "required": []any{"value"}, - "properties": map[string]any{"value": map[string]any{"type": "string"}}, - "additionalProperties": false, - }, - ResultSchema: map[string]any{ - "type": "object", - "required": []any{"value"}, - "properties": map[string]any{"value": map[string]any{"type": "string"}}, - "additionalProperties": false, - }, - }, - }, - }) - - if err := cdp.validateCommandParams("Custom.echo", map[string]any{"value": "ok"}); err != nil { - t.Fatalf("expected valid params, got %v", err) - } - if err := cdp.validateCommandParams("Custom.echo", map[string]any{"value": 42}); err == nil || !strings.Contains(err.Error(), "params_schema") { - t.Fatalf("expected params schema error, got %v", err) - } - if err := cdp.validateCommandResult("Custom.echo", map[string]any{"value": "ok"}); err != nil { - t.Fatalf("expected valid result, got %v", err) - } - if err := cdp.validateCommandResult("Custom.echo", map[string]any{"value": 42}); err == nil || !strings.Contains(err.Error(), "result_schema") { - t.Fatalf("expected result schema error, got %v", err) - } -} - -func TestCustomEventSchemasValidatePayloads(t *testing.T) { - cdp := New(Options{ - CustomEvents: []CustomEvent{ - { - Name: "Custom.changed", - EventSchema: map[string]any{ - "type": "object", - "required": []any{"targetId"}, - "properties": map[string]any{"targetId": map[string]any{"type": "string"}}, - "additionalProperties": false, - }, - }, - }, - }) - - if _, ok := cdp.validateEventData("Custom.changed", map[string]any{"targetId": "target-1"}); !ok { - t.Fatal("expected valid event payload") - } - expectPanic(t, func() { cdp.validateEventData("Custom.changed", map[string]any{"targetId": 1}) }) -} - -func TestTypedCDPSurfaceInitializesAndEncodesParams(t *testing.T) { - cdp := New(Options{}) - if cdp.Target.client != cdp { - t.Fatal("expected Target domain to be initialized with the client") - } - - params := TargetCreateTargetParams{ - URL: "https://example.com", - Background: Bool(true), - } - raw, err := cdpParamsMap(params) - if err != nil { - t.Fatal(err) - } - if raw["url"] != "https://example.com" || raw["background"] != true { - t.Fatalf("unexpected encoded Target.createTarget params: %#v", raw) - } - if _, ok := raw["sessionId"]; ok { - t.Fatalf("SessionID must stay transport-only, got %#v", raw) - } -} diff --git a/go/modcdp/client/ModCDPPayloadSchemaNormalization_test.go b/go/modcdp/client/ModCDPPayloadSchemaNormalization_test.go deleted file mode 100644 index b49102c8..00000000 --- a/go/modcdp/client/ModCDPPayloadSchemaNormalization_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package client - -import ( - "strings" - "testing" - - abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" -) - -func TestPayloadSchemaNormalizationAcceptsEmptyJSONSchemaObjects(t *testing.T) { - schema := cloneSchema(map[string]any{}) - if schema == nil { - t.Fatal("expected empty schema object to normalize") - } - if err := abxjsonschema.Validate(schema, map[string]any{"value": 1}); err != nil { - t.Fatalf("expected empty schema to accept payload: %v", err) - } -} - -func TestPayloadSchemaNormalizationRejectsUnsupportedSchemaSpecs(t *testing.T) { - _, err := New(Options{}).Send("Mod.addCustomCommand", map[string]any{ - "name": "Custom.bad", - "params_schema": "not-a-schema", - }) - if err == nil || !strings.Contains(err.Error(), "params_schema must be a JSON Schema object") { - t.Fatalf("expected unsupported schema error, got %v", err) - } -} - -func TestPayloadSchemaNormalizationAcceptsNonEmptyJSONSchemaObjects(t *testing.T) { - schema := cloneSchema(map[string]any{ - "type": "object", - "required": []any{"value"}, - "properties": map[string]any{"value": map[string]any{"type": "string"}}, - }) - if schema == nil { - t.Fatal("expected schema object to normalize") - } - if err := abxjsonschema.Validate(schema, map[string]any{"value": "ok", "extra": true}); err != nil { - t.Fatalf("expected valid payload: %v", err) - } - if err := abxjsonschema.Validate(schema, map[string]any{"value": 1}); err == nil { - t.Fatal("expected invalid payload to fail validation") - } -} diff --git a/go/modcdp/client/generated.go b/go/modcdp/client/generated.go index 2c8269e2..69a88e34 100644 --- a/go/modcdp/client/generated.go +++ b/go/modcdp/client/generated.go @@ -106,7 +106,7 @@ func cdpParamsMap(params any) (map[string]any, error) { func sendCDPCommand[T any](client *ModCDPClient, method string, params any) (T, error) { var typed T if client == nil { - return typed, fmt.Errorf("client_hydrate_aliases is false; use Send or SendRaw for %s", method) + return typed, fmt.Errorf("client_hydrate_aliases is false; use Send for %s", method) } rawParams, err := cdpParamsMap(params) if err != nil { @@ -5665,7 +5665,7 @@ type ExtensionsTriggerActionParams struct { type ExtensionsTriggerActionResult struct { } -type ExtensionsLoadUnpackedParams struct { +type CDPParams struct { SessionID string `json:"-"` // Absolute file path. Path string `json:"path"` @@ -5673,7 +5673,7 @@ type ExtensionsLoadUnpackedParams struct { EnableInIncognito *bool `json:"enableInIncognito,omitempty"` } -type ExtensionsLoadUnpackedResult struct { +type CDPResult struct { // Extension id. ID string `json:"id"` } diff --git a/go/modcdp/client/generated_domains.go b/go/modcdp/client/generated_domains.go index adac7dce..15a22508 100644 --- a/go/modcdp/client/generated_domains.go +++ b/go/modcdp/client/generated_domains.go @@ -1568,8 +1568,8 @@ func (d ExtensionsDomain) TriggerAction(params ExtensionsTriggerActionParams) (E return sendCDPCommand[ExtensionsTriggerActionResult](d.client, "Extensions.triggerAction", params) } -func (d ExtensionsDomain) LoadUnpacked(params ExtensionsLoadUnpackedParams) (ExtensionsLoadUnpackedResult, error) { - return sendCDPCommand[ExtensionsLoadUnpackedResult](d.client, "Extensions.loadUnpacked", params) +func (d ExtensionsDomain) LoadUnpacked(params CDPParams) (CDPResult, error) { + return sendCDPCommand[CDPResult](d.client, "Extensions.loadUnpacked", params) } func (d ExtensionsDomain) GetExtensions(params ...ExtensionsGetExtensionsParams) (ExtensionsGetExtensionsResult, error) { diff --git a/go/modcdp/client/generated_protocol_schemas.go b/go/modcdp/client/generated_protocol_schemas.go index 507ff76a..79746650 100644 --- a/go/modcdp/client/generated_protocol_schemas.go +++ b/go/modcdp/client/generated_protocol_schemas.go @@ -3,1574 +3,2904 @@ package client import abxjsonschema "github.com/ArchiveBox/abxbus/abxbus-go/v2/jsonschema" -func (c *ModCDPClient) hydrateNativeProtocolSchemas() { - c.schemaMu.Lock() - defer c.schemaMu.Unlock() - c.commandParamsSchemas["Accessibility.disable"] = abxjsonschema.SchemaFor[AccessibilityDisableParams]() - c.commandResultSchemas["Accessibility.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityDisableResult]()) - c.commandParamsSchemas["Accessibility.enable"] = abxjsonschema.SchemaFor[AccessibilityEnableParams]() - c.commandResultSchemas["Accessibility.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityEnableResult]()) - c.commandParamsSchemas["Accessibility.getPartialAXTree"] = abxjsonschema.SchemaFor[AccessibilityGetPartialAXTreeParams]() - c.commandResultSchemas["Accessibility.getPartialAXTree"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetPartialAXTreeResult]()) - c.commandParamsSchemas["Accessibility.getFullAXTree"] = abxjsonschema.SchemaFor[AccessibilityGetFullAXTreeParams]() - c.commandResultSchemas["Accessibility.getFullAXTree"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetFullAXTreeResult]()) - c.commandParamsSchemas["Accessibility.getRootAXNode"] = abxjsonschema.SchemaFor[AccessibilityGetRootAXNodeParams]() - c.commandResultSchemas["Accessibility.getRootAXNode"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetRootAXNodeResult]()) - c.commandParamsSchemas["Accessibility.getAXNodeAndAncestors"] = abxjsonschema.SchemaFor[AccessibilityGetAXNodeAndAncestorsParams]() - c.commandResultSchemas["Accessibility.getAXNodeAndAncestors"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetAXNodeAndAncestorsResult]()) - c.commandParamsSchemas["Accessibility.getChildAXNodes"] = abxjsonschema.SchemaFor[AccessibilityGetChildAXNodesParams]() - c.commandResultSchemas["Accessibility.getChildAXNodes"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetChildAXNodesResult]()) - c.commandParamsSchemas["Accessibility.queryAXTree"] = abxjsonschema.SchemaFor[AccessibilityQueryAXTreeParams]() - c.commandResultSchemas["Accessibility.queryAXTree"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityQueryAXTreeResult]()) - c.commandParamsSchemas["Animation.disable"] = abxjsonschema.SchemaFor[AnimationDisableParams]() - c.commandResultSchemas["Animation.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationDisableResult]()) - c.commandParamsSchemas["Animation.enable"] = abxjsonschema.SchemaFor[AnimationEnableParams]() - c.commandResultSchemas["Animation.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationEnableResult]()) - c.commandParamsSchemas["Animation.getCurrentTime"] = abxjsonschema.SchemaFor[AnimationGetCurrentTimeParams]() - c.commandResultSchemas["Animation.getCurrentTime"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationGetCurrentTimeResult]()) - c.commandParamsSchemas["Animation.getPlaybackRate"] = abxjsonschema.SchemaFor[AnimationGetPlaybackRateParams]() - c.commandResultSchemas["Animation.getPlaybackRate"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationGetPlaybackRateResult]()) - c.commandParamsSchemas["Animation.releaseAnimations"] = abxjsonschema.SchemaFor[AnimationReleaseAnimationsParams]() - c.commandResultSchemas["Animation.releaseAnimations"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationReleaseAnimationsResult]()) - c.commandParamsSchemas["Animation.resolveAnimation"] = abxjsonschema.SchemaFor[AnimationResolveAnimationParams]() - c.commandResultSchemas["Animation.resolveAnimation"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationResolveAnimationResult]()) - c.commandParamsSchemas["Animation.seekAnimations"] = abxjsonschema.SchemaFor[AnimationSeekAnimationsParams]() - c.commandResultSchemas["Animation.seekAnimations"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationSeekAnimationsResult]()) - c.commandParamsSchemas["Animation.setPaused"] = abxjsonschema.SchemaFor[AnimationSetPausedParams]() - c.commandResultSchemas["Animation.setPaused"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetPausedResult]()) - c.commandParamsSchemas["Animation.setPlaybackRate"] = abxjsonschema.SchemaFor[AnimationSetPlaybackRateParams]() - c.commandResultSchemas["Animation.setPlaybackRate"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetPlaybackRateResult]()) - c.commandParamsSchemas["Animation.setTiming"] = abxjsonschema.SchemaFor[AnimationSetTimingParams]() - c.commandResultSchemas["Animation.setTiming"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetTimingResult]()) - c.commandParamsSchemas["Audits.getEncodedResponse"] = abxjsonschema.SchemaFor[AuditsGetEncodedResponseParams]() - c.commandResultSchemas["Audits.getEncodedResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsGetEncodedResponseResult]()) - c.commandParamsSchemas["Audits.disable"] = abxjsonschema.SchemaFor[AuditsDisableParams]() - c.commandResultSchemas["Audits.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsDisableResult]()) - c.commandParamsSchemas["Audits.enable"] = abxjsonschema.SchemaFor[AuditsEnableParams]() - c.commandResultSchemas["Audits.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsEnableResult]()) - c.commandParamsSchemas["Audits.checkFormsIssues"] = abxjsonschema.SchemaFor[AuditsCheckFormsIssuesParams]() - c.commandResultSchemas["Audits.checkFormsIssues"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsCheckFormsIssuesResult]()) - c.commandParamsSchemas["Autofill.trigger"] = abxjsonschema.SchemaFor[AutofillTriggerParams]() - c.commandResultSchemas["Autofill.trigger"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillTriggerResult]()) - c.commandParamsSchemas["Autofill.setAddresses"] = abxjsonschema.SchemaFor[AutofillSetAddressesParams]() - c.commandResultSchemas["Autofill.setAddresses"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillSetAddressesResult]()) - c.commandParamsSchemas["Autofill.disable"] = abxjsonschema.SchemaFor[AutofillDisableParams]() - c.commandResultSchemas["Autofill.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillDisableResult]()) - c.commandParamsSchemas["Autofill.enable"] = abxjsonschema.SchemaFor[AutofillEnableParams]() - c.commandResultSchemas["Autofill.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillEnableResult]()) - c.commandParamsSchemas["BackgroundService.startObserving"] = abxjsonschema.SchemaFor[BackgroundServiceStartObservingParams]() - c.commandResultSchemas["BackgroundService.startObserving"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceStartObservingResult]()) - c.commandParamsSchemas["BackgroundService.stopObserving"] = abxjsonschema.SchemaFor[BackgroundServiceStopObservingParams]() - c.commandResultSchemas["BackgroundService.stopObserving"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceStopObservingResult]()) - c.commandParamsSchemas["BackgroundService.setRecording"] = abxjsonschema.SchemaFor[BackgroundServiceSetRecordingParams]() - c.commandResultSchemas["BackgroundService.setRecording"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceSetRecordingResult]()) - c.commandParamsSchemas["BackgroundService.clearEvents"] = abxjsonschema.SchemaFor[BackgroundServiceClearEventsParams]() - c.commandResultSchemas["BackgroundService.clearEvents"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceClearEventsResult]()) - c.commandParamsSchemas["BluetoothEmulation.enable"] = abxjsonschema.SchemaFor[BluetoothEmulationEnableParams]() - c.commandResultSchemas["BluetoothEmulation.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationEnableResult]()) - c.commandParamsSchemas["BluetoothEmulation.setSimulatedCentralState"] = abxjsonschema.SchemaFor[BluetoothEmulationSetSimulatedCentralStateParams]() - c.commandResultSchemas["BluetoothEmulation.setSimulatedCentralState"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSetSimulatedCentralStateResult]()) - c.commandParamsSchemas["BluetoothEmulation.disable"] = abxjsonschema.SchemaFor[BluetoothEmulationDisableParams]() - c.commandResultSchemas["BluetoothEmulation.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationDisableResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulatePreconnectedPeripheral"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulatePreconnectedPeripheralParams]() - c.commandResultSchemas["BluetoothEmulation.simulatePreconnectedPeripheral"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulatePreconnectedPeripheralResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulateAdvertisement"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulateAdvertisementParams]() - c.commandResultSchemas["BluetoothEmulation.simulateAdvertisement"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateAdvertisementResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulateGATTOperationResponse"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTOperationResponseParams]() - c.commandResultSchemas["BluetoothEmulation.simulateGATTOperationResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTOperationResponseResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulateCharacteristicOperationResponse"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulateCharacteristicOperationResponseParams]() - c.commandResultSchemas["BluetoothEmulation.simulateCharacteristicOperationResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateCharacteristicOperationResponseResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulateDescriptorOperationResponse"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulateDescriptorOperationResponseParams]() - c.commandResultSchemas["BluetoothEmulation.simulateDescriptorOperationResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateDescriptorOperationResponseResult]()) - c.commandParamsSchemas["BluetoothEmulation.addService"] = abxjsonschema.SchemaFor[BluetoothEmulationAddServiceParams]() - c.commandResultSchemas["BluetoothEmulation.addService"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddServiceResult]()) - c.commandParamsSchemas["BluetoothEmulation.removeService"] = abxjsonschema.SchemaFor[BluetoothEmulationRemoveServiceParams]() - c.commandResultSchemas["BluetoothEmulation.removeService"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveServiceResult]()) - c.commandParamsSchemas["BluetoothEmulation.addCharacteristic"] = abxjsonschema.SchemaFor[BluetoothEmulationAddCharacteristicParams]() - c.commandResultSchemas["BluetoothEmulation.addCharacteristic"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddCharacteristicResult]()) - c.commandParamsSchemas["BluetoothEmulation.removeCharacteristic"] = abxjsonschema.SchemaFor[BluetoothEmulationRemoveCharacteristicParams]() - c.commandResultSchemas["BluetoothEmulation.removeCharacteristic"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveCharacteristicResult]()) - c.commandParamsSchemas["BluetoothEmulation.addDescriptor"] = abxjsonschema.SchemaFor[BluetoothEmulationAddDescriptorParams]() - c.commandResultSchemas["BluetoothEmulation.addDescriptor"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddDescriptorResult]()) - c.commandParamsSchemas["BluetoothEmulation.removeDescriptor"] = abxjsonschema.SchemaFor[BluetoothEmulationRemoveDescriptorParams]() - c.commandResultSchemas["BluetoothEmulation.removeDescriptor"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveDescriptorResult]()) - c.commandParamsSchemas["BluetoothEmulation.simulateGATTDisconnection"] = abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTDisconnectionParams]() - c.commandResultSchemas["BluetoothEmulation.simulateGATTDisconnection"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTDisconnectionResult]()) - c.commandParamsSchemas["Browser.setPermission"] = abxjsonschema.SchemaFor[BrowserSetPermissionParams]() - c.commandResultSchemas["Browser.setPermission"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetPermissionResult]()) - c.commandParamsSchemas["Browser.grantPermissions"] = abxjsonschema.SchemaFor[BrowserGrantPermissionsParams]() - c.commandResultSchemas["Browser.grantPermissions"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGrantPermissionsResult]()) - c.commandParamsSchemas["Browser.resetPermissions"] = abxjsonschema.SchemaFor[BrowserResetPermissionsParams]() - c.commandResultSchemas["Browser.resetPermissions"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserResetPermissionsResult]()) - c.commandParamsSchemas["Browser.setDownloadBehavior"] = abxjsonschema.SchemaFor[BrowserSetDownloadBehaviorParams]() - c.commandResultSchemas["Browser.setDownloadBehavior"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetDownloadBehaviorResult]()) - c.commandParamsSchemas["Browser.cancelDownload"] = abxjsonschema.SchemaFor[BrowserCancelDownloadParams]() - c.commandResultSchemas["Browser.cancelDownload"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserCancelDownloadResult]()) - c.commandParamsSchemas["Browser.close"] = abxjsonschema.SchemaFor[BrowserCloseParams]() - c.commandResultSchemas["Browser.close"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserCloseResult]()) - c.commandParamsSchemas["Browser.crash"] = abxjsonschema.SchemaFor[BrowserCrashParams]() - c.commandResultSchemas["Browser.crash"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserCrashResult]()) - c.commandParamsSchemas["Browser.crashGpuProcess"] = abxjsonschema.SchemaFor[BrowserCrashGPUProcessParams]() - c.commandResultSchemas["Browser.crashGpuProcess"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserCrashGPUProcessResult]()) - c.commandParamsSchemas["Browser.getVersion"] = abxjsonschema.SchemaFor[BrowserGetVersionParams]() - c.commandResultSchemas["Browser.getVersion"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetVersionResult]()) - c.commandParamsSchemas["Browser.getBrowserCommandLine"] = abxjsonschema.SchemaFor[BrowserGetBrowserCommandLineParams]() - c.commandResultSchemas["Browser.getBrowserCommandLine"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetBrowserCommandLineResult]()) - c.commandParamsSchemas["Browser.getHistograms"] = abxjsonschema.SchemaFor[BrowserGetHistogramsParams]() - c.commandResultSchemas["Browser.getHistograms"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetHistogramsResult]()) - c.commandParamsSchemas["Browser.getHistogram"] = abxjsonschema.SchemaFor[BrowserGetHistogramParams]() - c.commandResultSchemas["Browser.getHistogram"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetHistogramResult]()) - c.commandParamsSchemas["Browser.getWindowBounds"] = abxjsonschema.SchemaFor[BrowserGetWindowBoundsParams]() - c.commandResultSchemas["Browser.getWindowBounds"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetWindowBoundsResult]()) - c.commandParamsSchemas["Browser.getWindowForTarget"] = abxjsonschema.SchemaFor[BrowserGetWindowForTargetParams]() - c.commandResultSchemas["Browser.getWindowForTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetWindowForTargetResult]()) - c.commandParamsSchemas["Browser.setWindowBounds"] = abxjsonschema.SchemaFor[BrowserSetWindowBoundsParams]() - c.commandResultSchemas["Browser.setWindowBounds"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetWindowBoundsResult]()) - c.commandParamsSchemas["Browser.setContentsSize"] = abxjsonschema.SchemaFor[BrowserSetContentsSizeParams]() - c.commandResultSchemas["Browser.setContentsSize"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetContentsSizeResult]()) - c.commandParamsSchemas["Browser.setDockTile"] = abxjsonschema.SchemaFor[BrowserSetDockTileParams]() - c.commandResultSchemas["Browser.setDockTile"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetDockTileResult]()) - c.commandParamsSchemas["Browser.executeBrowserCommand"] = abxjsonschema.SchemaFor[BrowserExecuteBrowserCommandParams]() - c.commandResultSchemas["Browser.executeBrowserCommand"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserExecuteBrowserCommandResult]()) - c.commandParamsSchemas["Browser.addPrivacySandboxEnrollmentOverride"] = abxjsonschema.SchemaFor[BrowserAddPrivacySandboxEnrollmentOverrideParams]() - c.commandResultSchemas["Browser.addPrivacySandboxEnrollmentOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserAddPrivacySandboxEnrollmentOverrideResult]()) - c.commandParamsSchemas["Browser.addPrivacySandboxCoordinatorKeyConfig"] = abxjsonschema.SchemaFor[BrowserAddPrivacySandboxCoordinatorKeyConfigParams]() - c.commandResultSchemas["Browser.addPrivacySandboxCoordinatorKeyConfig"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserAddPrivacySandboxCoordinatorKeyConfigResult]()) - c.commandParamsSchemas["CSS.addRule"] = abxjsonschema.SchemaFor[CSSAddRuleParams]() - c.commandResultSchemas["CSS.addRule"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSAddRuleResult]()) - c.commandParamsSchemas["CSS.collectClassNames"] = abxjsonschema.SchemaFor[CSSCollectClassNamesParams]() - c.commandResultSchemas["CSS.collectClassNames"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSCollectClassNamesResult]()) - c.commandParamsSchemas["CSS.createStyleSheet"] = abxjsonschema.SchemaFor[CSSCreateStyleSheetParams]() - c.commandResultSchemas["CSS.createStyleSheet"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSCreateStyleSheetResult]()) - c.commandParamsSchemas["CSS.disable"] = abxjsonschema.SchemaFor[CSSDisableParams]() - c.commandResultSchemas["CSS.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSDisableResult]()) - c.commandParamsSchemas["CSS.enable"] = abxjsonschema.SchemaFor[CSSEnableParams]() - c.commandResultSchemas["CSS.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSEnableResult]()) - c.commandParamsSchemas["CSS.forcePseudoState"] = abxjsonschema.SchemaFor[CSSForcePseudoStateParams]() - c.commandResultSchemas["CSS.forcePseudoState"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSForcePseudoStateResult]()) - c.commandParamsSchemas["CSS.forceStartingStyle"] = abxjsonschema.SchemaFor[CSSForceStartingStyleParams]() - c.commandResultSchemas["CSS.forceStartingStyle"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSForceStartingStyleResult]()) - c.commandParamsSchemas["CSS.getBackgroundColors"] = abxjsonschema.SchemaFor[CSSGetBackgroundColorsParams]() - c.commandResultSchemas["CSS.getBackgroundColors"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetBackgroundColorsResult]()) - c.commandParamsSchemas["CSS.getComputedStyleForNode"] = abxjsonschema.SchemaFor[CSSGetComputedStyleForNodeParams]() - c.commandResultSchemas["CSS.getComputedStyleForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetComputedStyleForNodeResult]()) - c.commandParamsSchemas["CSS.resolveValues"] = abxjsonschema.SchemaFor[CSSResolveValuesParams]() - c.commandResultSchemas["CSS.resolveValues"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSResolveValuesResult]()) - c.commandParamsSchemas["CSS.getLonghandProperties"] = abxjsonschema.SchemaFor[CSSGetLonghandPropertiesParams]() - c.commandResultSchemas["CSS.getLonghandProperties"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLonghandPropertiesResult]()) - c.commandParamsSchemas["CSS.getInlineStylesForNode"] = abxjsonschema.SchemaFor[CSSGetInlineStylesForNodeParams]() - c.commandResultSchemas["CSS.getInlineStylesForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetInlineStylesForNodeResult]()) - c.commandParamsSchemas["CSS.getAnimatedStylesForNode"] = abxjsonschema.SchemaFor[CSSGetAnimatedStylesForNodeParams]() - c.commandResultSchemas["CSS.getAnimatedStylesForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetAnimatedStylesForNodeResult]()) - c.commandParamsSchemas["CSS.getMatchedStylesForNode"] = abxjsonschema.SchemaFor[CSSGetMatchedStylesForNodeParams]() - c.commandResultSchemas["CSS.getMatchedStylesForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetMatchedStylesForNodeResult]()) - c.commandParamsSchemas["CSS.getEnvironmentVariables"] = abxjsonschema.SchemaFor[CSSGetEnvironmentVariablesParams]() - c.commandResultSchemas["CSS.getEnvironmentVariables"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetEnvironmentVariablesResult]()) - c.commandParamsSchemas["CSS.getMediaQueries"] = abxjsonschema.SchemaFor[CSSGetMediaQueriesParams]() - c.commandResultSchemas["CSS.getMediaQueries"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetMediaQueriesResult]()) - c.commandParamsSchemas["CSS.getPlatformFontsForNode"] = abxjsonschema.SchemaFor[CSSGetPlatformFontsForNodeParams]() - c.commandResultSchemas["CSS.getPlatformFontsForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetPlatformFontsForNodeResult]()) - c.commandParamsSchemas["CSS.getStyleSheetText"] = abxjsonschema.SchemaFor[CSSGetStyleSheetTextParams]() - c.commandResultSchemas["CSS.getStyleSheetText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetStyleSheetTextResult]()) - c.commandParamsSchemas["CSS.getLayersForNode"] = abxjsonschema.SchemaFor[CSSGetLayersForNodeParams]() - c.commandResultSchemas["CSS.getLayersForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLayersForNodeResult]()) - c.commandParamsSchemas["CSS.getLocationForSelector"] = abxjsonschema.SchemaFor[CSSGetLocationForSelectorParams]() - c.commandResultSchemas["CSS.getLocationForSelector"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLocationForSelectorResult]()) - c.commandParamsSchemas["CSS.trackComputedStyleUpdatesForNode"] = abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesForNodeParams]() - c.commandResultSchemas["CSS.trackComputedStyleUpdatesForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesForNodeResult]()) - c.commandParamsSchemas["CSS.trackComputedStyleUpdates"] = abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesParams]() - c.commandResultSchemas["CSS.trackComputedStyleUpdates"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesResult]()) - c.commandParamsSchemas["CSS.takeComputedStyleUpdates"] = abxjsonschema.SchemaFor[CSSTakeComputedStyleUpdatesParams]() - c.commandResultSchemas["CSS.takeComputedStyleUpdates"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSTakeComputedStyleUpdatesResult]()) - c.commandParamsSchemas["CSS.setEffectivePropertyValueForNode"] = abxjsonschema.SchemaFor[CSSSetEffectivePropertyValueForNodeParams]() - c.commandResultSchemas["CSS.setEffectivePropertyValueForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetEffectivePropertyValueForNodeResult]()) - c.commandParamsSchemas["CSS.setPropertyRulePropertyName"] = abxjsonschema.SchemaFor[CSSSetPropertyRulePropertyNameParams]() - c.commandResultSchemas["CSS.setPropertyRulePropertyName"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetPropertyRulePropertyNameResult]()) - c.commandParamsSchemas["CSS.setKeyframeKey"] = abxjsonschema.SchemaFor[CSSSetKeyframeKeyParams]() - c.commandResultSchemas["CSS.setKeyframeKey"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetKeyframeKeyResult]()) - c.commandParamsSchemas["CSS.setMediaText"] = abxjsonschema.SchemaFor[CSSSetMediaTextParams]() - c.commandResultSchemas["CSS.setMediaText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetMediaTextResult]()) - c.commandParamsSchemas["CSS.setContainerQueryText"] = abxjsonschema.SchemaFor[CSSSetContainerQueryTextParams]() - c.commandResultSchemas["CSS.setContainerQueryText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetContainerQueryTextResult]()) - c.commandParamsSchemas["CSS.setSupportsText"] = abxjsonschema.SchemaFor[CSSSetSupportsTextParams]() - c.commandResultSchemas["CSS.setSupportsText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetSupportsTextResult]()) - c.commandParamsSchemas["CSS.setNavigationText"] = abxjsonschema.SchemaFor[CSSSetNavigationTextParams]() - c.commandResultSchemas["CSS.setNavigationText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetNavigationTextResult]()) - c.commandParamsSchemas["CSS.setScopeText"] = abxjsonschema.SchemaFor[CSSSetScopeTextParams]() - c.commandResultSchemas["CSS.setScopeText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetScopeTextResult]()) - c.commandParamsSchemas["CSS.setRuleSelector"] = abxjsonschema.SchemaFor[CSSSetRuleSelectorParams]() - c.commandResultSchemas["CSS.setRuleSelector"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetRuleSelectorResult]()) - c.commandParamsSchemas["CSS.setStyleSheetText"] = abxjsonschema.SchemaFor[CSSSetStyleSheetTextParams]() - c.commandResultSchemas["CSS.setStyleSheetText"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetStyleSheetTextResult]()) - c.commandParamsSchemas["CSS.setStyleTexts"] = abxjsonschema.SchemaFor[CSSSetStyleTextsParams]() - c.commandResultSchemas["CSS.setStyleTexts"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetStyleTextsResult]()) - c.commandParamsSchemas["CSS.startRuleUsageTracking"] = abxjsonschema.SchemaFor[CSSStartRuleUsageTrackingParams]() - c.commandResultSchemas["CSS.startRuleUsageTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStartRuleUsageTrackingResult]()) - c.commandParamsSchemas["CSS.stopRuleUsageTracking"] = abxjsonschema.SchemaFor[CSSStopRuleUsageTrackingParams]() - c.commandResultSchemas["CSS.stopRuleUsageTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStopRuleUsageTrackingResult]()) - c.commandParamsSchemas["CSS.takeCoverageDelta"] = abxjsonschema.SchemaFor[CSSTakeCoverageDeltaParams]() - c.commandResultSchemas["CSS.takeCoverageDelta"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSTakeCoverageDeltaResult]()) - c.commandParamsSchemas["CSS.setLocalFontsEnabled"] = abxjsonschema.SchemaFor[CSSSetLocalFontsEnabledParams]() - c.commandResultSchemas["CSS.setLocalFontsEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSSetLocalFontsEnabledResult]()) - c.commandParamsSchemas["CacheStorage.deleteCache"] = abxjsonschema.SchemaFor[CacheStorageDeleteCacheParams]() - c.commandResultSchemas["CacheStorage.deleteCache"] = nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageDeleteCacheResult]()) - c.commandParamsSchemas["CacheStorage.deleteEntry"] = abxjsonschema.SchemaFor[CacheStorageDeleteEntryParams]() - c.commandResultSchemas["CacheStorage.deleteEntry"] = nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageDeleteEntryResult]()) - c.commandParamsSchemas["CacheStorage.requestCacheNames"] = abxjsonschema.SchemaFor[CacheStorageRequestCacheNamesParams]() - c.commandResultSchemas["CacheStorage.requestCacheNames"] = nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestCacheNamesResult]()) - c.commandParamsSchemas["CacheStorage.requestCachedResponse"] = abxjsonschema.SchemaFor[CacheStorageRequestCachedResponseParams]() - c.commandResultSchemas["CacheStorage.requestCachedResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestCachedResponseResult]()) - c.commandParamsSchemas["CacheStorage.requestEntries"] = abxjsonschema.SchemaFor[CacheStorageRequestEntriesParams]() - c.commandResultSchemas["CacheStorage.requestEntries"] = nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestEntriesResult]()) - c.commandParamsSchemas["Cast.enable"] = abxjsonschema.SchemaFor[CastEnableParams]() - c.commandResultSchemas["Cast.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[CastEnableResult]()) - c.commandParamsSchemas["Cast.disable"] = abxjsonschema.SchemaFor[CastDisableParams]() - c.commandResultSchemas["Cast.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[CastDisableResult]()) - c.commandParamsSchemas["Cast.setSinkToUse"] = abxjsonschema.SchemaFor[CastSetSinkToUseParams]() - c.commandResultSchemas["Cast.setSinkToUse"] = nativeResultSchema(abxjsonschema.SchemaFor[CastSetSinkToUseResult]()) - c.commandParamsSchemas["Cast.startDesktopMirroring"] = abxjsonschema.SchemaFor[CastStartDesktopMirroringParams]() - c.commandResultSchemas["Cast.startDesktopMirroring"] = nativeResultSchema(abxjsonschema.SchemaFor[CastStartDesktopMirroringResult]()) - c.commandParamsSchemas["Cast.startTabMirroring"] = abxjsonschema.SchemaFor[CastStartTabMirroringParams]() - c.commandResultSchemas["Cast.startTabMirroring"] = nativeResultSchema(abxjsonschema.SchemaFor[CastStartTabMirroringResult]()) - c.commandParamsSchemas["Cast.stopCasting"] = abxjsonschema.SchemaFor[CastStopCastingParams]() - c.commandResultSchemas["Cast.stopCasting"] = nativeResultSchema(abxjsonschema.SchemaFor[CastStopCastingResult]()) - c.commandParamsSchemas["Console.clearMessages"] = abxjsonschema.SchemaFor[ConsoleClearMessagesParams]() - c.commandResultSchemas["Console.clearMessages"] = nativeResultSchema(abxjsonschema.SchemaFor[ConsoleClearMessagesResult]()) - c.commandParamsSchemas["Console.disable"] = abxjsonschema.SchemaFor[ConsoleDisableParams]() - c.commandResultSchemas["Console.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[ConsoleDisableResult]()) - c.commandParamsSchemas["Console.enable"] = abxjsonschema.SchemaFor[ConsoleEnableParams]() - c.commandResultSchemas["Console.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[ConsoleEnableResult]()) - c.commandParamsSchemas["DOM.collectClassNamesFromSubtree"] = abxjsonschema.SchemaFor[DOMCollectClassNamesFromSubtreeParams]() - c.commandResultSchemas["DOM.collectClassNamesFromSubtree"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMCollectClassNamesFromSubtreeResult]()) - c.commandParamsSchemas["DOM.copyTo"] = abxjsonschema.SchemaFor[DOMCopyToParams]() - c.commandResultSchemas["DOM.copyTo"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMCopyToResult]()) - c.commandParamsSchemas["DOM.describeNode"] = abxjsonschema.SchemaFor[DOMDescribeNodeParams]() - c.commandResultSchemas["DOM.describeNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDescribeNodeResult]()) - c.commandParamsSchemas["DOM.scrollIntoViewIfNeeded"] = abxjsonschema.SchemaFor[DOMScrollIntoViewIfNeededParams]() - c.commandResultSchemas["DOM.scrollIntoViewIfNeeded"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMScrollIntoViewIfNeededResult]()) - c.commandParamsSchemas["DOM.disable"] = abxjsonschema.SchemaFor[DOMDisableParams]() - c.commandResultSchemas["DOM.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDisableResult]()) - c.commandParamsSchemas["DOM.discardSearchResults"] = abxjsonschema.SchemaFor[DOMDiscardSearchResultsParams]() - c.commandResultSchemas["DOM.discardSearchResults"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDiscardSearchResultsResult]()) - c.commandParamsSchemas["DOM.enable"] = abxjsonschema.SchemaFor[DOMEnableParams]() - c.commandResultSchemas["DOM.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMEnableResult]()) - c.commandParamsSchemas["DOM.focus"] = abxjsonschema.SchemaFor[DOMFocusParams]() - c.commandResultSchemas["DOM.focus"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMFocusResult]()) - c.commandParamsSchemas["DOM.getAttributes"] = abxjsonschema.SchemaFor[DOMGetAttributesParams]() - c.commandResultSchemas["DOM.getAttributes"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetAttributesResult]()) - c.commandParamsSchemas["DOM.getBoxModel"] = abxjsonschema.SchemaFor[DOMGetBoxModelParams]() - c.commandResultSchemas["DOM.getBoxModel"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetBoxModelResult]()) - c.commandParamsSchemas["DOM.getContentQuads"] = abxjsonschema.SchemaFor[DOMGetContentQuadsParams]() - c.commandResultSchemas["DOM.getContentQuads"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetContentQuadsResult]()) - c.commandParamsSchemas["DOM.getDocument"] = abxjsonschema.SchemaFor[DOMGetDocumentParams]() - c.commandResultSchemas["DOM.getDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetDocumentResult]()) - c.commandParamsSchemas["DOM.getFlattenedDocument"] = abxjsonschema.SchemaFor[DOMGetFlattenedDocumentParams]() - c.commandResultSchemas["DOM.getFlattenedDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFlattenedDocumentResult]()) - c.commandParamsSchemas["DOM.getNodesForSubtreeByStyle"] = abxjsonschema.SchemaFor[DOMGetNodesForSubtreeByStyleParams]() - c.commandResultSchemas["DOM.getNodesForSubtreeByStyle"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodesForSubtreeByStyleResult]()) - c.commandParamsSchemas["DOM.getNodeForLocation"] = abxjsonschema.SchemaFor[DOMGetNodeForLocationParams]() - c.commandResultSchemas["DOM.getNodeForLocation"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodeForLocationResult]()) - c.commandParamsSchemas["DOM.getOuterHTML"] = abxjsonschema.SchemaFor[DOMGetOuterHTMLParams]() - c.commandResultSchemas["DOM.getOuterHTML"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetOuterHTMLResult]()) - c.commandParamsSchemas["DOM.getRelayoutBoundary"] = abxjsonschema.SchemaFor[DOMGetRelayoutBoundaryParams]() - c.commandResultSchemas["DOM.getRelayoutBoundary"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetRelayoutBoundaryResult]()) - c.commandParamsSchemas["DOM.getSearchResults"] = abxjsonschema.SchemaFor[DOMGetSearchResultsParams]() - c.commandResultSchemas["DOM.getSearchResults"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetSearchResultsResult]()) - c.commandParamsSchemas["DOM.hideHighlight"] = abxjsonschema.SchemaFor[DOMHideHighlightParams]() - c.commandResultSchemas["DOM.hideHighlight"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMHideHighlightResult]()) - c.commandParamsSchemas["DOM.highlightNode"] = abxjsonschema.SchemaFor[DOMHighlightNodeParams]() - c.commandResultSchemas["DOM.highlightNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMHighlightNodeResult]()) - c.commandParamsSchemas["DOM.highlightRect"] = abxjsonschema.SchemaFor[DOMHighlightRectParams]() - c.commandResultSchemas["DOM.highlightRect"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMHighlightRectResult]()) - c.commandParamsSchemas["DOM.markUndoableState"] = abxjsonschema.SchemaFor[DOMMarkUndoableStateParams]() - c.commandResultSchemas["DOM.markUndoableState"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMMarkUndoableStateResult]()) - c.commandParamsSchemas["DOM.moveTo"] = abxjsonschema.SchemaFor[DOMMoveToParams]() - c.commandResultSchemas["DOM.moveTo"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMMoveToResult]()) - c.commandParamsSchemas["DOM.performSearch"] = abxjsonschema.SchemaFor[DOMPerformSearchParams]() - c.commandResultSchemas["DOM.performSearch"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPerformSearchResult]()) - c.commandParamsSchemas["DOM.pushNodeByPathToFrontend"] = abxjsonschema.SchemaFor[DOMPushNodeByPathToFrontendParams]() - c.commandResultSchemas["DOM.pushNodeByPathToFrontend"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPushNodeByPathToFrontendResult]()) - c.commandParamsSchemas["DOM.pushNodesByBackendIdsToFrontend"] = abxjsonschema.SchemaFor[DOMPushNodesByBackendIdsToFrontendParams]() - c.commandResultSchemas["DOM.pushNodesByBackendIdsToFrontend"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPushNodesByBackendIdsToFrontendResult]()) - c.commandParamsSchemas["DOM.querySelector"] = abxjsonschema.SchemaFor[DOMQuerySelectorParams]() - c.commandResultSchemas["DOM.querySelector"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMQuerySelectorResult]()) - c.commandParamsSchemas["DOM.querySelectorAll"] = abxjsonschema.SchemaFor[DOMQuerySelectorAllParams]() - c.commandResultSchemas["DOM.querySelectorAll"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMQuerySelectorAllResult]()) - c.commandParamsSchemas["DOM.getTopLayerElements"] = abxjsonschema.SchemaFor[DOMGetTopLayerElementsParams]() - c.commandResultSchemas["DOM.getTopLayerElements"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetTopLayerElementsResult]()) - c.commandParamsSchemas["DOM.getElementByRelation"] = abxjsonschema.SchemaFor[DOMGetElementByRelationParams]() - c.commandResultSchemas["DOM.getElementByRelation"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetElementByRelationResult]()) - c.commandParamsSchemas["DOM.redo"] = abxjsonschema.SchemaFor[DOMRedoParams]() - c.commandResultSchemas["DOM.redo"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMRedoResult]()) - c.commandParamsSchemas["DOM.removeAttribute"] = abxjsonschema.SchemaFor[DOMRemoveAttributeParams]() - c.commandResultSchemas["DOM.removeAttribute"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMRemoveAttributeResult]()) - c.commandParamsSchemas["DOM.removeNode"] = abxjsonschema.SchemaFor[DOMRemoveNodeParams]() - c.commandResultSchemas["DOM.removeNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMRemoveNodeResult]()) - c.commandParamsSchemas["DOM.requestChildNodes"] = abxjsonschema.SchemaFor[DOMRequestChildNodesParams]() - c.commandResultSchemas["DOM.requestChildNodes"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMRequestChildNodesResult]()) - c.commandParamsSchemas["DOM.requestNode"] = abxjsonschema.SchemaFor[DOMRequestNodeParams]() - c.commandResultSchemas["DOM.requestNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMRequestNodeResult]()) - c.commandParamsSchemas["DOM.resolveNode"] = abxjsonschema.SchemaFor[DOMResolveNodeParams]() - c.commandResultSchemas["DOM.resolveNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMResolveNodeResult]()) - c.commandParamsSchemas["DOM.setAttributeValue"] = abxjsonschema.SchemaFor[DOMSetAttributeValueParams]() - c.commandResultSchemas["DOM.setAttributeValue"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetAttributeValueResult]()) - c.commandParamsSchemas["DOM.setAttributesAsText"] = abxjsonschema.SchemaFor[DOMSetAttributesAsTextParams]() - c.commandResultSchemas["DOM.setAttributesAsText"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetAttributesAsTextResult]()) - c.commandParamsSchemas["DOM.setFileInputFiles"] = abxjsonschema.SchemaFor[DOMSetFileInputFilesParams]() - c.commandResultSchemas["DOM.setFileInputFiles"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetFileInputFilesResult]()) - c.commandParamsSchemas["DOM.setNodeStackTracesEnabled"] = abxjsonschema.SchemaFor[DOMSetNodeStackTracesEnabledParams]() - c.commandResultSchemas["DOM.setNodeStackTracesEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeStackTracesEnabledResult]()) - c.commandParamsSchemas["DOM.getNodeStackTraces"] = abxjsonschema.SchemaFor[DOMGetNodeStackTracesParams]() - c.commandResultSchemas["DOM.getNodeStackTraces"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodeStackTracesResult]()) - c.commandParamsSchemas["DOM.getFileInfo"] = abxjsonschema.SchemaFor[DOMGetFileInfoParams]() - c.commandResultSchemas["DOM.getFileInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFileInfoResult]()) - c.commandParamsSchemas["DOM.getDetachedDomNodes"] = abxjsonschema.SchemaFor[DOMGetDetachedDOMNodesParams]() - c.commandResultSchemas["DOM.getDetachedDomNodes"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetDetachedDOMNodesResult]()) - c.commandParamsSchemas["DOM.setInspectedNode"] = abxjsonschema.SchemaFor[DOMSetInspectedNodeParams]() - c.commandResultSchemas["DOM.setInspectedNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetInspectedNodeResult]()) - c.commandParamsSchemas["DOM.setNodeName"] = abxjsonschema.SchemaFor[DOMSetNodeNameParams]() - c.commandResultSchemas["DOM.setNodeName"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeNameResult]()) - c.commandParamsSchemas["DOM.setNodeValue"] = abxjsonschema.SchemaFor[DOMSetNodeValueParams]() - c.commandResultSchemas["DOM.setNodeValue"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeValueResult]()) - c.commandParamsSchemas["DOM.setOuterHTML"] = abxjsonschema.SchemaFor[DOMSetOuterHTMLParams]() - c.commandResultSchemas["DOM.setOuterHTML"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetOuterHTMLResult]()) - c.commandParamsSchemas["DOM.undo"] = abxjsonschema.SchemaFor[DOMUndoParams]() - c.commandResultSchemas["DOM.undo"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMUndoResult]()) - c.commandParamsSchemas["DOM.getFrameOwner"] = abxjsonschema.SchemaFor[DOMGetFrameOwnerParams]() - c.commandResultSchemas["DOM.getFrameOwner"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFrameOwnerResult]()) - c.commandParamsSchemas["DOM.getContainerForNode"] = abxjsonschema.SchemaFor[DOMGetContainerForNodeParams]() - c.commandResultSchemas["DOM.getContainerForNode"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetContainerForNodeResult]()) - c.commandParamsSchemas["DOM.getQueryingDescendantsForContainer"] = abxjsonschema.SchemaFor[DOMGetQueryingDescendantsForContainerParams]() - c.commandResultSchemas["DOM.getQueryingDescendantsForContainer"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetQueryingDescendantsForContainerResult]()) - c.commandParamsSchemas["DOM.getAnchorElement"] = abxjsonschema.SchemaFor[DOMGetAnchorElementParams]() - c.commandResultSchemas["DOM.getAnchorElement"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMGetAnchorElementResult]()) - c.commandParamsSchemas["DOM.forceShowPopover"] = abxjsonschema.SchemaFor[DOMForceShowPopoverParams]() - c.commandResultSchemas["DOM.forceShowPopover"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMForceShowPopoverResult]()) - c.commandParamsSchemas["DOMDebugger.getEventListeners"] = abxjsonschema.SchemaFor[DOMDebuggerGetEventListenersParams]() - c.commandResultSchemas["DOMDebugger.getEventListeners"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerGetEventListenersResult]()) - c.commandParamsSchemas["DOMDebugger.removeDOMBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerRemoveDOMBreakpointParams]() - c.commandResultSchemas["DOMDebugger.removeDOMBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveDOMBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.removeEventListenerBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerRemoveEventListenerBreakpointParams]() - c.commandResultSchemas["DOMDebugger.removeEventListenerBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveEventListenerBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.removeInstrumentationBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerRemoveInstrumentationBreakpointParams]() - c.commandResultSchemas["DOMDebugger.removeInstrumentationBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveInstrumentationBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.removeXHRBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerRemoveXHRBreakpointParams]() - c.commandResultSchemas["DOMDebugger.removeXHRBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveXHRBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.setBreakOnCSPViolation"] = abxjsonschema.SchemaFor[DOMDebuggerSetBreakOnCSPViolationParams]() - c.commandResultSchemas["DOMDebugger.setBreakOnCSPViolation"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetBreakOnCSPViolationResult]()) - c.commandParamsSchemas["DOMDebugger.setDOMBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerSetDOMBreakpointParams]() - c.commandResultSchemas["DOMDebugger.setDOMBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetDOMBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.setEventListenerBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerSetEventListenerBreakpointParams]() - c.commandResultSchemas["DOMDebugger.setEventListenerBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetEventListenerBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.setInstrumentationBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerSetInstrumentationBreakpointParams]() - c.commandResultSchemas["DOMDebugger.setInstrumentationBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetInstrumentationBreakpointResult]()) - c.commandParamsSchemas["DOMDebugger.setXHRBreakpoint"] = abxjsonschema.SchemaFor[DOMDebuggerSetXHRBreakpointParams]() - c.commandResultSchemas["DOMDebugger.setXHRBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetXHRBreakpointResult]()) - c.commandParamsSchemas["DOMSnapshot.disable"] = abxjsonschema.SchemaFor[DOMSnapshotDisableParams]() - c.commandResultSchemas["DOMSnapshot.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotDisableResult]()) - c.commandParamsSchemas["DOMSnapshot.enable"] = abxjsonschema.SchemaFor[DOMSnapshotEnableParams]() - c.commandResultSchemas["DOMSnapshot.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotEnableResult]()) - c.commandParamsSchemas["DOMSnapshot.getSnapshot"] = abxjsonschema.SchemaFor[DOMSnapshotGetSnapshotParams]() - c.commandResultSchemas["DOMSnapshot.getSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotGetSnapshotResult]()) - c.commandParamsSchemas["DOMSnapshot.captureSnapshot"] = abxjsonschema.SchemaFor[DOMSnapshotCaptureSnapshotParams]() - c.commandResultSchemas["DOMSnapshot.captureSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotCaptureSnapshotResult]()) - c.commandParamsSchemas["DOMStorage.clear"] = abxjsonschema.SchemaFor[DOMStorageClearParams]() - c.commandResultSchemas["DOMStorage.clear"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageClearResult]()) - c.commandParamsSchemas["DOMStorage.disable"] = abxjsonschema.SchemaFor[DOMStorageDisableParams]() - c.commandResultSchemas["DOMStorage.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDisableResult]()) - c.commandParamsSchemas["DOMStorage.enable"] = abxjsonschema.SchemaFor[DOMStorageEnableParams]() - c.commandResultSchemas["DOMStorage.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageEnableResult]()) - c.commandParamsSchemas["DOMStorage.getDOMStorageItems"] = abxjsonschema.SchemaFor[DOMStorageGetDOMStorageItemsParams]() - c.commandResultSchemas["DOMStorage.getDOMStorageItems"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageGetDOMStorageItemsResult]()) - c.commandParamsSchemas["DOMStorage.removeDOMStorageItem"] = abxjsonschema.SchemaFor[DOMStorageRemoveDOMStorageItemParams]() - c.commandResultSchemas["DOMStorage.removeDOMStorageItem"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageRemoveDOMStorageItemResult]()) - c.commandParamsSchemas["DOMStorage.setDOMStorageItem"] = abxjsonschema.SchemaFor[DOMStorageSetDOMStorageItemParams]() - c.commandResultSchemas["DOMStorage.setDOMStorageItem"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageSetDOMStorageItemResult]()) - c.commandParamsSchemas["Debugger.continueToLocation"] = abxjsonschema.SchemaFor[DebuggerContinueToLocationParams]() - c.commandResultSchemas["Debugger.continueToLocation"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerContinueToLocationResult]()) - c.commandParamsSchemas["Debugger.disable"] = abxjsonschema.SchemaFor[DebuggerDisableParams]() - c.commandResultSchemas["Debugger.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerDisableResult]()) - c.commandParamsSchemas["Debugger.enable"] = abxjsonschema.SchemaFor[DebuggerEnableParams]() - c.commandResultSchemas["Debugger.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerEnableResult]()) - c.commandParamsSchemas["Debugger.evaluateOnCallFrame"] = abxjsonschema.SchemaFor[DebuggerEvaluateOnCallFrameParams]() - c.commandResultSchemas["Debugger.evaluateOnCallFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerEvaluateOnCallFrameResult]()) - c.commandParamsSchemas["Debugger.getPossibleBreakpoints"] = abxjsonschema.SchemaFor[DebuggerGetPossibleBreakpointsParams]() - c.commandResultSchemas["Debugger.getPossibleBreakpoints"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetPossibleBreakpointsResult]()) - c.commandParamsSchemas["Debugger.getScriptSource"] = abxjsonschema.SchemaFor[DebuggerGetScriptSourceParams]() - c.commandResultSchemas["Debugger.getScriptSource"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetScriptSourceResult]()) - c.commandParamsSchemas["Debugger.disassembleWasmModule"] = abxjsonschema.SchemaFor[DebuggerDisassembleWasmModuleParams]() - c.commandResultSchemas["Debugger.disassembleWasmModule"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerDisassembleWasmModuleResult]()) - c.commandParamsSchemas["Debugger.nextWasmDisassemblyChunk"] = abxjsonschema.SchemaFor[DebuggerNextWasmDisassemblyChunkParams]() - c.commandResultSchemas["Debugger.nextWasmDisassemblyChunk"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerNextWasmDisassemblyChunkResult]()) - c.commandParamsSchemas["Debugger.getWasmBytecode"] = abxjsonschema.SchemaFor[DebuggerGetWasmBytecodeParams]() - c.commandResultSchemas["Debugger.getWasmBytecode"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetWasmBytecodeResult]()) - c.commandParamsSchemas["Debugger.getStackTrace"] = abxjsonschema.SchemaFor[DebuggerGetStackTraceParams]() - c.commandResultSchemas["Debugger.getStackTrace"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetStackTraceResult]()) - c.commandParamsSchemas["Debugger.pause"] = abxjsonschema.SchemaFor[DebuggerPauseParams]() - c.commandResultSchemas["Debugger.pause"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPauseResult]()) - c.commandParamsSchemas["Debugger.pauseOnAsyncCall"] = abxjsonschema.SchemaFor[DebuggerPauseOnAsyncCallParams]() - c.commandResultSchemas["Debugger.pauseOnAsyncCall"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPauseOnAsyncCallResult]()) - c.commandParamsSchemas["Debugger.removeBreakpoint"] = abxjsonschema.SchemaFor[DebuggerRemoveBreakpointParams]() - c.commandResultSchemas["Debugger.removeBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerRemoveBreakpointResult]()) - c.commandParamsSchemas["Debugger.restartFrame"] = abxjsonschema.SchemaFor[DebuggerRestartFrameParams]() - c.commandResultSchemas["Debugger.restartFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerRestartFrameResult]()) - c.commandParamsSchemas["Debugger.resume"] = abxjsonschema.SchemaFor[DebuggerResumeParams]() - c.commandResultSchemas["Debugger.resume"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerResumeResult]()) - c.commandParamsSchemas["Debugger.searchInContent"] = abxjsonschema.SchemaFor[DebuggerSearchInContentParams]() - c.commandResultSchemas["Debugger.searchInContent"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSearchInContentResult]()) - c.commandParamsSchemas["Debugger.setAsyncCallStackDepth"] = abxjsonschema.SchemaFor[DebuggerSetAsyncCallStackDepthParams]() - c.commandResultSchemas["Debugger.setAsyncCallStackDepth"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetAsyncCallStackDepthResult]()) - c.commandParamsSchemas["Debugger.setBlackboxExecutionContexts"] = abxjsonschema.SchemaFor[DebuggerSetBlackboxExecutionContextsParams]() - c.commandResultSchemas["Debugger.setBlackboxExecutionContexts"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxExecutionContextsResult]()) - c.commandParamsSchemas["Debugger.setBlackboxPatterns"] = abxjsonschema.SchemaFor[DebuggerSetBlackboxPatternsParams]() - c.commandResultSchemas["Debugger.setBlackboxPatterns"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxPatternsResult]()) - c.commandParamsSchemas["Debugger.setBlackboxedRanges"] = abxjsonschema.SchemaFor[DebuggerSetBlackboxedRangesParams]() - c.commandResultSchemas["Debugger.setBlackboxedRanges"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxedRangesResult]()) - c.commandParamsSchemas["Debugger.setBreakpoint"] = abxjsonschema.SchemaFor[DebuggerSetBreakpointParams]() - c.commandResultSchemas["Debugger.setBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointResult]()) - c.commandParamsSchemas["Debugger.setInstrumentationBreakpoint"] = abxjsonschema.SchemaFor[DebuggerSetInstrumentationBreakpointParams]() - c.commandResultSchemas["Debugger.setInstrumentationBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetInstrumentationBreakpointResult]()) - c.commandParamsSchemas["Debugger.setBreakpointByUrl"] = abxjsonschema.SchemaFor[DebuggerSetBreakpointByURLParams]() - c.commandResultSchemas["Debugger.setBreakpointByUrl"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointByURLResult]()) - c.commandParamsSchemas["Debugger.setBreakpointOnFunctionCall"] = abxjsonschema.SchemaFor[DebuggerSetBreakpointOnFunctionCallParams]() - c.commandResultSchemas["Debugger.setBreakpointOnFunctionCall"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointOnFunctionCallResult]()) - c.commandParamsSchemas["Debugger.setBreakpointsActive"] = abxjsonschema.SchemaFor[DebuggerSetBreakpointsActiveParams]() - c.commandResultSchemas["Debugger.setBreakpointsActive"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointsActiveResult]()) - c.commandParamsSchemas["Debugger.setPauseOnExceptions"] = abxjsonschema.SchemaFor[DebuggerSetPauseOnExceptionsParams]() - c.commandResultSchemas["Debugger.setPauseOnExceptions"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetPauseOnExceptionsResult]()) - c.commandParamsSchemas["Debugger.setReturnValue"] = abxjsonschema.SchemaFor[DebuggerSetReturnValueParams]() - c.commandResultSchemas["Debugger.setReturnValue"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetReturnValueResult]()) - c.commandParamsSchemas["Debugger.setScriptSource"] = abxjsonschema.SchemaFor[DebuggerSetScriptSourceParams]() - c.commandResultSchemas["Debugger.setScriptSource"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetScriptSourceResult]()) - c.commandParamsSchemas["Debugger.setSkipAllPauses"] = abxjsonschema.SchemaFor[DebuggerSetSkipAllPausesParams]() - c.commandResultSchemas["Debugger.setSkipAllPauses"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetSkipAllPausesResult]()) - c.commandParamsSchemas["Debugger.setVariableValue"] = abxjsonschema.SchemaFor[DebuggerSetVariableValueParams]() - c.commandResultSchemas["Debugger.setVariableValue"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetVariableValueResult]()) - c.commandParamsSchemas["Debugger.stepInto"] = abxjsonschema.SchemaFor[DebuggerStepIntoParams]() - c.commandResultSchemas["Debugger.stepInto"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepIntoResult]()) - c.commandParamsSchemas["Debugger.stepOut"] = abxjsonschema.SchemaFor[DebuggerStepOutParams]() - c.commandResultSchemas["Debugger.stepOut"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepOutResult]()) - c.commandParamsSchemas["Debugger.stepOver"] = abxjsonschema.SchemaFor[DebuggerStepOverParams]() - c.commandResultSchemas["Debugger.stepOver"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepOverResult]()) - c.commandParamsSchemas["DeviceAccess.enable"] = abxjsonschema.SchemaFor[DeviceAccessEnableParams]() - c.commandResultSchemas["DeviceAccess.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessEnableResult]()) - c.commandParamsSchemas["DeviceAccess.disable"] = abxjsonschema.SchemaFor[DeviceAccessDisableParams]() - c.commandResultSchemas["DeviceAccess.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessDisableResult]()) - c.commandParamsSchemas["DeviceAccess.selectPrompt"] = abxjsonschema.SchemaFor[DeviceAccessSelectPromptParams]() - c.commandResultSchemas["DeviceAccess.selectPrompt"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessSelectPromptResult]()) - c.commandParamsSchemas["DeviceAccess.cancelPrompt"] = abxjsonschema.SchemaFor[DeviceAccessCancelPromptParams]() - c.commandResultSchemas["DeviceAccess.cancelPrompt"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessCancelPromptResult]()) - c.commandParamsSchemas["DeviceOrientation.clearDeviceOrientationOverride"] = abxjsonschema.SchemaFor[DeviceOrientationClearDeviceOrientationOverrideParams]() - c.commandResultSchemas["DeviceOrientation.clearDeviceOrientationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceOrientationClearDeviceOrientationOverrideResult]()) - c.commandParamsSchemas["DeviceOrientation.setDeviceOrientationOverride"] = abxjsonschema.SchemaFor[DeviceOrientationSetDeviceOrientationOverrideParams]() - c.commandResultSchemas["DeviceOrientation.setDeviceOrientationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceOrientationSetDeviceOrientationOverrideResult]()) - c.commandParamsSchemas["Emulation.canEmulate"] = abxjsonschema.SchemaFor[EmulationCanEmulateParams]() - c.commandResultSchemas["Emulation.canEmulate"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationCanEmulateResult]()) - c.commandParamsSchemas["Emulation.clearDeviceMetricsOverride"] = abxjsonschema.SchemaFor[EmulationClearDeviceMetricsOverrideParams]() - c.commandResultSchemas["Emulation.clearDeviceMetricsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDeviceMetricsOverrideResult]()) - c.commandParamsSchemas["Emulation.clearGeolocationOverride"] = abxjsonschema.SchemaFor[EmulationClearGeolocationOverrideParams]() - c.commandResultSchemas["Emulation.clearGeolocationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearGeolocationOverrideResult]()) - c.commandParamsSchemas["Emulation.resetPageScaleFactor"] = abxjsonschema.SchemaFor[EmulationResetPageScaleFactorParams]() - c.commandResultSchemas["Emulation.resetPageScaleFactor"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationResetPageScaleFactorResult]()) - c.commandParamsSchemas["Emulation.setFocusEmulationEnabled"] = abxjsonschema.SchemaFor[EmulationSetFocusEmulationEnabledParams]() - c.commandResultSchemas["Emulation.setFocusEmulationEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetFocusEmulationEnabledResult]()) - c.commandParamsSchemas["Emulation.setAutoDarkModeOverride"] = abxjsonschema.SchemaFor[EmulationSetAutoDarkModeOverrideParams]() - c.commandResultSchemas["Emulation.setAutoDarkModeOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetAutoDarkModeOverrideResult]()) - c.commandParamsSchemas["Emulation.setCPUThrottlingRate"] = abxjsonschema.SchemaFor[EmulationSetCPUThrottlingRateParams]() - c.commandResultSchemas["Emulation.setCPUThrottlingRate"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetCPUThrottlingRateResult]()) - c.commandParamsSchemas["Emulation.setDefaultBackgroundColorOverride"] = abxjsonschema.SchemaFor[EmulationSetDefaultBackgroundColorOverrideParams]() - c.commandResultSchemas["Emulation.setDefaultBackgroundColorOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDefaultBackgroundColorOverrideResult]()) - c.commandParamsSchemas["Emulation.setSafeAreaInsetsOverride"] = abxjsonschema.SchemaFor[EmulationSetSafeAreaInsetsOverrideParams]() - c.commandResultSchemas["Emulation.setSafeAreaInsetsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSafeAreaInsetsOverrideResult]()) - c.commandParamsSchemas["Emulation.setDeviceMetricsOverride"] = abxjsonschema.SchemaFor[EmulationSetDeviceMetricsOverrideParams]() - c.commandResultSchemas["Emulation.setDeviceMetricsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDeviceMetricsOverrideResult]()) - c.commandParamsSchemas["Emulation.setDevicePostureOverride"] = abxjsonschema.SchemaFor[EmulationSetDevicePostureOverrideParams]() - c.commandResultSchemas["Emulation.setDevicePostureOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDevicePostureOverrideResult]()) - c.commandParamsSchemas["Emulation.clearDevicePostureOverride"] = abxjsonschema.SchemaFor[EmulationClearDevicePostureOverrideParams]() - c.commandResultSchemas["Emulation.clearDevicePostureOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDevicePostureOverrideResult]()) - c.commandParamsSchemas["Emulation.setDisplayFeaturesOverride"] = abxjsonschema.SchemaFor[EmulationSetDisplayFeaturesOverrideParams]() - c.commandResultSchemas["Emulation.setDisplayFeaturesOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDisplayFeaturesOverrideResult]()) - c.commandParamsSchemas["Emulation.clearDisplayFeaturesOverride"] = abxjsonschema.SchemaFor[EmulationClearDisplayFeaturesOverrideParams]() - c.commandResultSchemas["Emulation.clearDisplayFeaturesOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDisplayFeaturesOverrideResult]()) - c.commandParamsSchemas["Emulation.setScrollbarsHidden"] = abxjsonschema.SchemaFor[EmulationSetScrollbarsHiddenParams]() - c.commandResultSchemas["Emulation.setScrollbarsHidden"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetScrollbarsHiddenResult]()) - c.commandParamsSchemas["Emulation.setDocumentCookieDisabled"] = abxjsonschema.SchemaFor[EmulationSetDocumentCookieDisabledParams]() - c.commandResultSchemas["Emulation.setDocumentCookieDisabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDocumentCookieDisabledResult]()) - c.commandParamsSchemas["Emulation.setEmitTouchEventsForMouse"] = abxjsonschema.SchemaFor[EmulationSetEmitTouchEventsForMouseParams]() - c.commandResultSchemas["Emulation.setEmitTouchEventsForMouse"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmitTouchEventsForMouseResult]()) - c.commandParamsSchemas["Emulation.setEmulatedMedia"] = abxjsonschema.SchemaFor[EmulationSetEmulatedMediaParams]() - c.commandResultSchemas["Emulation.setEmulatedMedia"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedMediaResult]()) - c.commandParamsSchemas["Emulation.setEmulatedVisionDeficiency"] = abxjsonschema.SchemaFor[EmulationSetEmulatedVisionDeficiencyParams]() - c.commandResultSchemas["Emulation.setEmulatedVisionDeficiency"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedVisionDeficiencyResult]()) - c.commandParamsSchemas["Emulation.setEmulatedOSTextScale"] = abxjsonschema.SchemaFor[EmulationSetEmulatedOSTextScaleParams]() - c.commandResultSchemas["Emulation.setEmulatedOSTextScale"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedOSTextScaleResult]()) - c.commandParamsSchemas["Emulation.setGeolocationOverride"] = abxjsonschema.SchemaFor[EmulationSetGeolocationOverrideParams]() - c.commandResultSchemas["Emulation.setGeolocationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetGeolocationOverrideResult]()) - c.commandParamsSchemas["Emulation.getOverriddenSensorInformation"] = abxjsonschema.SchemaFor[EmulationGetOverriddenSensorInformationParams]() - c.commandResultSchemas["Emulation.getOverriddenSensorInformation"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationGetOverriddenSensorInformationResult]()) - c.commandParamsSchemas["Emulation.setSensorOverrideEnabled"] = abxjsonschema.SchemaFor[EmulationSetSensorOverrideEnabledParams]() - c.commandResultSchemas["Emulation.setSensorOverrideEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSensorOverrideEnabledResult]()) - c.commandParamsSchemas["Emulation.setSensorOverrideReadings"] = abxjsonschema.SchemaFor[EmulationSetSensorOverrideReadingsParams]() - c.commandResultSchemas["Emulation.setSensorOverrideReadings"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSensorOverrideReadingsResult]()) - c.commandParamsSchemas["Emulation.setPressureSourceOverrideEnabled"] = abxjsonschema.SchemaFor[EmulationSetPressureSourceOverrideEnabledParams]() - c.commandResultSchemas["Emulation.setPressureSourceOverrideEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureSourceOverrideEnabledResult]()) - c.commandParamsSchemas["Emulation.setPressureStateOverride"] = abxjsonschema.SchemaFor[EmulationSetPressureStateOverrideParams]() - c.commandResultSchemas["Emulation.setPressureStateOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureStateOverrideResult]()) - c.commandParamsSchemas["Emulation.setPressureDataOverride"] = abxjsonschema.SchemaFor[EmulationSetPressureDataOverrideParams]() - c.commandResultSchemas["Emulation.setPressureDataOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureDataOverrideResult]()) - c.commandParamsSchemas["Emulation.setIdleOverride"] = abxjsonschema.SchemaFor[EmulationSetIdleOverrideParams]() - c.commandResultSchemas["Emulation.setIdleOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetIdleOverrideResult]()) - c.commandParamsSchemas["Emulation.clearIdleOverride"] = abxjsonschema.SchemaFor[EmulationClearIdleOverrideParams]() - c.commandResultSchemas["Emulation.clearIdleOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearIdleOverrideResult]()) - c.commandParamsSchemas["Emulation.setNavigatorOverrides"] = abxjsonschema.SchemaFor[EmulationSetNavigatorOverridesParams]() - c.commandResultSchemas["Emulation.setNavigatorOverrides"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetNavigatorOverridesResult]()) - c.commandParamsSchemas["Emulation.setPageScaleFactor"] = abxjsonschema.SchemaFor[EmulationSetPageScaleFactorParams]() - c.commandResultSchemas["Emulation.setPageScaleFactor"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPageScaleFactorResult]()) - c.commandParamsSchemas["Emulation.setScriptExecutionDisabled"] = abxjsonschema.SchemaFor[EmulationSetScriptExecutionDisabledParams]() - c.commandResultSchemas["Emulation.setScriptExecutionDisabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetScriptExecutionDisabledResult]()) - c.commandParamsSchemas["Emulation.setTouchEmulationEnabled"] = abxjsonschema.SchemaFor[EmulationSetTouchEmulationEnabledParams]() - c.commandResultSchemas["Emulation.setTouchEmulationEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetTouchEmulationEnabledResult]()) - c.commandParamsSchemas["Emulation.setVirtualTimePolicy"] = abxjsonschema.SchemaFor[EmulationSetVirtualTimePolicyParams]() - c.commandResultSchemas["Emulation.setVirtualTimePolicy"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetVirtualTimePolicyResult]()) - c.commandParamsSchemas["Emulation.setLocaleOverride"] = abxjsonschema.SchemaFor[EmulationSetLocaleOverrideParams]() - c.commandResultSchemas["Emulation.setLocaleOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetLocaleOverrideResult]()) - c.commandParamsSchemas["Emulation.setTimezoneOverride"] = abxjsonschema.SchemaFor[EmulationSetTimezoneOverrideParams]() - c.commandResultSchemas["Emulation.setTimezoneOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetTimezoneOverrideResult]()) - c.commandParamsSchemas["Emulation.setVisibleSize"] = abxjsonschema.SchemaFor[EmulationSetVisibleSizeParams]() - c.commandResultSchemas["Emulation.setVisibleSize"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetVisibleSizeResult]()) - c.commandParamsSchemas["Emulation.setDisabledImageTypes"] = abxjsonschema.SchemaFor[EmulationSetDisabledImageTypesParams]() - c.commandResultSchemas["Emulation.setDisabledImageTypes"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDisabledImageTypesResult]()) - c.commandParamsSchemas["Emulation.setDataSaverOverride"] = abxjsonschema.SchemaFor[EmulationSetDataSaverOverrideParams]() - c.commandResultSchemas["Emulation.setDataSaverOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDataSaverOverrideResult]()) - c.commandParamsSchemas["Emulation.setHardwareConcurrencyOverride"] = abxjsonschema.SchemaFor[EmulationSetHardwareConcurrencyOverrideParams]() - c.commandResultSchemas["Emulation.setHardwareConcurrencyOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetHardwareConcurrencyOverrideResult]()) - c.commandParamsSchemas["Emulation.setUserAgentOverride"] = abxjsonschema.SchemaFor[EmulationSetUserAgentOverrideParams]() - c.commandResultSchemas["Emulation.setUserAgentOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetUserAgentOverrideResult]()) - c.commandParamsSchemas["Emulation.setAutomationOverride"] = abxjsonschema.SchemaFor[EmulationSetAutomationOverrideParams]() - c.commandResultSchemas["Emulation.setAutomationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetAutomationOverrideResult]()) - c.commandParamsSchemas["Emulation.setSmallViewportHeightDifferenceOverride"] = abxjsonschema.SchemaFor[EmulationSetSmallViewportHeightDifferenceOverrideParams]() - c.commandResultSchemas["Emulation.setSmallViewportHeightDifferenceOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSmallViewportHeightDifferenceOverrideResult]()) - c.commandParamsSchemas["Emulation.getScreenInfos"] = abxjsonschema.SchemaFor[EmulationGetScreenInfosParams]() - c.commandResultSchemas["Emulation.getScreenInfos"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationGetScreenInfosResult]()) - c.commandParamsSchemas["Emulation.addScreen"] = abxjsonschema.SchemaFor[EmulationAddScreenParams]() - c.commandResultSchemas["Emulation.addScreen"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationAddScreenResult]()) - c.commandParamsSchemas["Emulation.updateScreen"] = abxjsonschema.SchemaFor[EmulationUpdateScreenParams]() - c.commandResultSchemas["Emulation.updateScreen"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationUpdateScreenResult]()) - c.commandParamsSchemas["Emulation.removeScreen"] = abxjsonschema.SchemaFor[EmulationRemoveScreenParams]() - c.commandResultSchemas["Emulation.removeScreen"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationRemoveScreenResult]()) - c.commandParamsSchemas["Emulation.setPrimaryScreen"] = abxjsonschema.SchemaFor[EmulationSetPrimaryScreenParams]() - c.commandResultSchemas["Emulation.setPrimaryScreen"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPrimaryScreenResult]()) - c.commandParamsSchemas["EventBreakpoints.setInstrumentationBreakpoint"] = abxjsonschema.SchemaFor[EventBreakpointsSetInstrumentationBreakpointParams]() - c.commandResultSchemas["EventBreakpoints.setInstrumentationBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsSetInstrumentationBreakpointResult]()) - c.commandParamsSchemas["EventBreakpoints.removeInstrumentationBreakpoint"] = abxjsonschema.SchemaFor[EventBreakpointsRemoveInstrumentationBreakpointParams]() - c.commandResultSchemas["EventBreakpoints.removeInstrumentationBreakpoint"] = nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsRemoveInstrumentationBreakpointResult]()) - c.commandParamsSchemas["EventBreakpoints.disable"] = abxjsonschema.SchemaFor[EventBreakpointsDisableParams]() - c.commandResultSchemas["EventBreakpoints.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsDisableResult]()) - c.commandParamsSchemas["Extensions.triggerAction"] = abxjsonschema.SchemaFor[ExtensionsTriggerActionParams]() - c.commandResultSchemas["Extensions.triggerAction"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsTriggerActionResult]()) - c.commandParamsSchemas["Extensions.loadUnpacked"] = abxjsonschema.SchemaFor[ExtensionsLoadUnpackedParams]() - c.commandResultSchemas["Extensions.loadUnpacked"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsLoadUnpackedResult]()) - c.commandParamsSchemas["Extensions.getExtensions"] = abxjsonschema.SchemaFor[ExtensionsGetExtensionsParams]() - c.commandResultSchemas["Extensions.getExtensions"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsGetExtensionsResult]()) - c.commandParamsSchemas["Extensions.uninstall"] = abxjsonschema.SchemaFor[ExtensionsUninstallParams]() - c.commandResultSchemas["Extensions.uninstall"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsUninstallResult]()) - c.commandParamsSchemas["Extensions.getStorageItems"] = abxjsonschema.SchemaFor[ExtensionsGetStorageItemsParams]() - c.commandResultSchemas["Extensions.getStorageItems"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsGetStorageItemsResult]()) - c.commandParamsSchemas["Extensions.removeStorageItems"] = abxjsonschema.SchemaFor[ExtensionsRemoveStorageItemsParams]() - c.commandResultSchemas["Extensions.removeStorageItems"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsRemoveStorageItemsResult]()) - c.commandParamsSchemas["Extensions.clearStorageItems"] = abxjsonschema.SchemaFor[ExtensionsClearStorageItemsParams]() - c.commandResultSchemas["Extensions.clearStorageItems"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsClearStorageItemsResult]()) - c.commandParamsSchemas["Extensions.setStorageItems"] = abxjsonschema.SchemaFor[ExtensionsSetStorageItemsParams]() - c.commandResultSchemas["Extensions.setStorageItems"] = nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsSetStorageItemsResult]()) - c.commandParamsSchemas["FedCm.enable"] = abxjsonschema.SchemaFor[FedCmEnableParams]() - c.commandResultSchemas["FedCm.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmEnableResult]()) - c.commandParamsSchemas["FedCm.disable"] = abxjsonschema.SchemaFor[FedCmDisableParams]() - c.commandResultSchemas["FedCm.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDisableResult]()) - c.commandParamsSchemas["FedCm.selectAccount"] = abxjsonschema.SchemaFor[FedCmSelectAccountParams]() - c.commandResultSchemas["FedCm.selectAccount"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmSelectAccountResult]()) - c.commandParamsSchemas["FedCm.clickDialogButton"] = abxjsonschema.SchemaFor[FedCmClickDialogButtonParams]() - c.commandResultSchemas["FedCm.clickDialogButton"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmClickDialogButtonResult]()) - c.commandParamsSchemas["FedCm.openUrl"] = abxjsonschema.SchemaFor[FedCmOpenURLParams]() - c.commandResultSchemas["FedCm.openUrl"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmOpenURLResult]()) - c.commandParamsSchemas["FedCm.dismissDialog"] = abxjsonschema.SchemaFor[FedCmDismissDialogParams]() - c.commandResultSchemas["FedCm.dismissDialog"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDismissDialogResult]()) - c.commandParamsSchemas["FedCm.resetCooldown"] = abxjsonschema.SchemaFor[FedCmResetCooldownParams]() - c.commandResultSchemas["FedCm.resetCooldown"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmResetCooldownResult]()) - c.commandParamsSchemas["Fetch.disable"] = abxjsonschema.SchemaFor[FetchDisableParams]() - c.commandResultSchemas["Fetch.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchDisableResult]()) - c.commandParamsSchemas["Fetch.enable"] = abxjsonschema.SchemaFor[FetchEnableParams]() - c.commandResultSchemas["Fetch.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchEnableResult]()) - c.commandParamsSchemas["Fetch.failRequest"] = abxjsonschema.SchemaFor[FetchFailRequestParams]() - c.commandResultSchemas["Fetch.failRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchFailRequestResult]()) - c.commandParamsSchemas["Fetch.fulfillRequest"] = abxjsonschema.SchemaFor[FetchFulfillRequestParams]() - c.commandResultSchemas["Fetch.fulfillRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchFulfillRequestResult]()) - c.commandParamsSchemas["Fetch.continueRequest"] = abxjsonschema.SchemaFor[FetchContinueRequestParams]() - c.commandResultSchemas["Fetch.continueRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueRequestResult]()) - c.commandParamsSchemas["Fetch.continueWithAuth"] = abxjsonschema.SchemaFor[FetchContinueWithAuthParams]() - c.commandResultSchemas["Fetch.continueWithAuth"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueWithAuthResult]()) - c.commandParamsSchemas["Fetch.continueResponse"] = abxjsonschema.SchemaFor[FetchContinueResponseParams]() - c.commandResultSchemas["Fetch.continueResponse"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueResponseResult]()) - c.commandParamsSchemas["Fetch.getResponseBody"] = abxjsonschema.SchemaFor[FetchGetResponseBodyParams]() - c.commandResultSchemas["Fetch.getResponseBody"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchGetResponseBodyResult]()) - c.commandParamsSchemas["Fetch.takeResponseBodyAsStream"] = abxjsonschema.SchemaFor[FetchTakeResponseBodyAsStreamParams]() - c.commandResultSchemas["Fetch.takeResponseBodyAsStream"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchTakeResponseBodyAsStreamResult]()) - c.commandParamsSchemas["FileSystem.getDirectory"] = abxjsonschema.SchemaFor[FileSystemGetDirectoryParams]() - c.commandResultSchemas["FileSystem.getDirectory"] = nativeResultSchema(abxjsonschema.SchemaFor[FileSystemGetDirectoryResult]()) - c.commandParamsSchemas["HeadlessExperimental.beginFrame"] = abxjsonschema.SchemaFor[HeadlessExperimentalBeginFrameParams]() - c.commandResultSchemas["HeadlessExperimental.beginFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalBeginFrameResult]()) - c.commandParamsSchemas["HeadlessExperimental.disable"] = abxjsonschema.SchemaFor[HeadlessExperimentalDisableParams]() - c.commandResultSchemas["HeadlessExperimental.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalDisableResult]()) - c.commandParamsSchemas["HeadlessExperimental.enable"] = abxjsonschema.SchemaFor[HeadlessExperimentalEnableParams]() - c.commandResultSchemas["HeadlessExperimental.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalEnableResult]()) - c.commandParamsSchemas["HeapProfiler.addInspectedHeapObject"] = abxjsonschema.SchemaFor[HeapProfilerAddInspectedHeapObjectParams]() - c.commandResultSchemas["HeapProfiler.addInspectedHeapObject"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerAddInspectedHeapObjectResult]()) - c.commandParamsSchemas["HeapProfiler.collectGarbage"] = abxjsonschema.SchemaFor[HeapProfilerCollectGarbageParams]() - c.commandResultSchemas["HeapProfiler.collectGarbage"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerCollectGarbageResult]()) - c.commandParamsSchemas["HeapProfiler.disable"] = abxjsonschema.SchemaFor[HeapProfilerDisableParams]() - c.commandResultSchemas["HeapProfiler.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerDisableResult]()) - c.commandParamsSchemas["HeapProfiler.enable"] = abxjsonschema.SchemaFor[HeapProfilerEnableParams]() - c.commandResultSchemas["HeapProfiler.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerEnableResult]()) - c.commandParamsSchemas["HeapProfiler.getHeapObjectId"] = abxjsonschema.SchemaFor[HeapProfilerGetHeapObjectIDParams]() - c.commandResultSchemas["HeapProfiler.getHeapObjectId"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetHeapObjectIDResult]()) - c.commandParamsSchemas["HeapProfiler.getObjectByHeapObjectId"] = abxjsonschema.SchemaFor[HeapProfilerGetObjectByHeapObjectIDParams]() - c.commandResultSchemas["HeapProfiler.getObjectByHeapObjectId"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetObjectByHeapObjectIDResult]()) - c.commandParamsSchemas["HeapProfiler.getSamplingProfile"] = abxjsonschema.SchemaFor[HeapProfilerGetSamplingProfileParams]() - c.commandResultSchemas["HeapProfiler.getSamplingProfile"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetSamplingProfileResult]()) - c.commandParamsSchemas["HeapProfiler.startSampling"] = abxjsonschema.SchemaFor[HeapProfilerStartSamplingParams]() - c.commandResultSchemas["HeapProfiler.startSampling"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStartSamplingResult]()) - c.commandParamsSchemas["HeapProfiler.startTrackingHeapObjects"] = abxjsonschema.SchemaFor[HeapProfilerStartTrackingHeapObjectsParams]() - c.commandResultSchemas["HeapProfiler.startTrackingHeapObjects"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStartTrackingHeapObjectsResult]()) - c.commandParamsSchemas["HeapProfiler.stopSampling"] = abxjsonschema.SchemaFor[HeapProfilerStopSamplingParams]() - c.commandResultSchemas["HeapProfiler.stopSampling"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStopSamplingResult]()) - c.commandParamsSchemas["HeapProfiler.stopTrackingHeapObjects"] = abxjsonschema.SchemaFor[HeapProfilerStopTrackingHeapObjectsParams]() - c.commandResultSchemas["HeapProfiler.stopTrackingHeapObjects"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStopTrackingHeapObjectsResult]()) - c.commandParamsSchemas["HeapProfiler.takeHeapSnapshot"] = abxjsonschema.SchemaFor[HeapProfilerTakeHeapSnapshotParams]() - c.commandResultSchemas["HeapProfiler.takeHeapSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerTakeHeapSnapshotResult]()) - c.commandParamsSchemas["IO.close"] = abxjsonschema.SchemaFor[IOCloseParams]() - c.commandResultSchemas["IO.close"] = nativeResultSchema(abxjsonschema.SchemaFor[IOCloseResult]()) - c.commandParamsSchemas["IO.read"] = abxjsonschema.SchemaFor[IOReadParams]() - c.commandResultSchemas["IO.read"] = nativeResultSchema(abxjsonschema.SchemaFor[IOReadResult]()) - c.commandParamsSchemas["IO.resolveBlob"] = abxjsonschema.SchemaFor[IOResolveBlobParams]() - c.commandResultSchemas["IO.resolveBlob"] = nativeResultSchema(abxjsonschema.SchemaFor[IOResolveBlobResult]()) - c.commandParamsSchemas["IndexedDB.clearObjectStore"] = abxjsonschema.SchemaFor[IndexedDBClearObjectStoreParams]() - c.commandResultSchemas["IndexedDB.clearObjectStore"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBClearObjectStoreResult]()) - c.commandParamsSchemas["IndexedDB.deleteDatabase"] = abxjsonschema.SchemaFor[IndexedDBDeleteDatabaseParams]() - c.commandResultSchemas["IndexedDB.deleteDatabase"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDeleteDatabaseResult]()) - c.commandParamsSchemas["IndexedDB.deleteObjectStoreEntries"] = abxjsonschema.SchemaFor[IndexedDBDeleteObjectStoreEntriesParams]() - c.commandResultSchemas["IndexedDB.deleteObjectStoreEntries"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDeleteObjectStoreEntriesResult]()) - c.commandParamsSchemas["IndexedDB.disable"] = abxjsonschema.SchemaFor[IndexedDBDisableParams]() - c.commandResultSchemas["IndexedDB.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDisableResult]()) - c.commandParamsSchemas["IndexedDB.enable"] = abxjsonschema.SchemaFor[IndexedDBEnableParams]() - c.commandResultSchemas["IndexedDB.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBEnableResult]()) - c.commandParamsSchemas["IndexedDB.requestData"] = abxjsonschema.SchemaFor[IndexedDBRequestDataParams]() - c.commandResultSchemas["IndexedDB.requestData"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDataResult]()) - c.commandParamsSchemas["IndexedDB.getMetadata"] = abxjsonschema.SchemaFor[IndexedDBGetMetadataParams]() - c.commandResultSchemas["IndexedDB.getMetadata"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBGetMetadataResult]()) - c.commandParamsSchemas["IndexedDB.requestDatabase"] = abxjsonschema.SchemaFor[IndexedDBRequestDatabaseParams]() - c.commandResultSchemas["IndexedDB.requestDatabase"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDatabaseResult]()) - c.commandParamsSchemas["IndexedDB.requestDatabaseNames"] = abxjsonschema.SchemaFor[IndexedDBRequestDatabaseNamesParams]() - c.commandResultSchemas["IndexedDB.requestDatabaseNames"] = nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDatabaseNamesResult]()) - c.commandParamsSchemas["Input.dispatchDragEvent"] = abxjsonschema.SchemaFor[InputDispatchDragEventParams]() - c.commandResultSchemas["Input.dispatchDragEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchDragEventResult]()) - c.commandParamsSchemas["Input.dispatchKeyEvent"] = abxjsonschema.SchemaFor[InputDispatchKeyEventParams]() - c.commandResultSchemas["Input.dispatchKeyEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchKeyEventResult]()) - c.commandParamsSchemas["Input.insertText"] = abxjsonschema.SchemaFor[InputInsertTextParams]() - c.commandResultSchemas["Input.insertText"] = nativeResultSchema(abxjsonschema.SchemaFor[InputInsertTextResult]()) - c.commandParamsSchemas["Input.imeSetComposition"] = abxjsonschema.SchemaFor[InputImeSetCompositionParams]() - c.commandResultSchemas["Input.imeSetComposition"] = nativeResultSchema(abxjsonschema.SchemaFor[InputImeSetCompositionResult]()) - c.commandParamsSchemas["Input.dispatchMouseEvent"] = abxjsonschema.SchemaFor[InputDispatchMouseEventParams]() - c.commandResultSchemas["Input.dispatchMouseEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchMouseEventResult]()) - c.commandParamsSchemas["Input.dispatchTouchEvent"] = abxjsonschema.SchemaFor[InputDispatchTouchEventParams]() - c.commandResultSchemas["Input.dispatchTouchEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchTouchEventResult]()) - c.commandParamsSchemas["Input.cancelDragging"] = abxjsonschema.SchemaFor[InputCancelDraggingParams]() - c.commandResultSchemas["Input.cancelDragging"] = nativeResultSchema(abxjsonschema.SchemaFor[InputCancelDraggingResult]()) - c.commandParamsSchemas["Input.emulateTouchFromMouseEvent"] = abxjsonschema.SchemaFor[InputEmulateTouchFromMouseEventParams]() - c.commandResultSchemas["Input.emulateTouchFromMouseEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[InputEmulateTouchFromMouseEventResult]()) - c.commandParamsSchemas["Input.setIgnoreInputEvents"] = abxjsonschema.SchemaFor[InputSetIgnoreInputEventsParams]() - c.commandResultSchemas["Input.setIgnoreInputEvents"] = nativeResultSchema(abxjsonschema.SchemaFor[InputSetIgnoreInputEventsResult]()) - c.commandParamsSchemas["Input.setInterceptDrags"] = abxjsonschema.SchemaFor[InputSetInterceptDragsParams]() - c.commandResultSchemas["Input.setInterceptDrags"] = nativeResultSchema(abxjsonschema.SchemaFor[InputSetInterceptDragsResult]()) - c.commandParamsSchemas["Input.synthesizePinchGesture"] = abxjsonschema.SchemaFor[InputSynthesizePinchGestureParams]() - c.commandResultSchemas["Input.synthesizePinchGesture"] = nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizePinchGestureResult]()) - c.commandParamsSchemas["Input.synthesizeScrollGesture"] = abxjsonschema.SchemaFor[InputSynthesizeScrollGestureParams]() - c.commandResultSchemas["Input.synthesizeScrollGesture"] = nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizeScrollGestureResult]()) - c.commandParamsSchemas["Input.synthesizeTapGesture"] = abxjsonschema.SchemaFor[InputSynthesizeTapGestureParams]() - c.commandResultSchemas["Input.synthesizeTapGesture"] = nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizeTapGestureResult]()) - c.commandParamsSchemas["Inspector.disable"] = abxjsonschema.SchemaFor[InspectorDisableParams]() - c.commandResultSchemas["Inspector.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorDisableResult]()) - c.commandParamsSchemas["Inspector.enable"] = abxjsonschema.SchemaFor[InspectorEnableParams]() - c.commandResultSchemas["Inspector.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorEnableResult]()) - c.commandParamsSchemas["LayerTree.compositingReasons"] = abxjsonschema.SchemaFor[LayerTreeCompositingReasonsParams]() - c.commandResultSchemas["LayerTree.compositingReasons"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeCompositingReasonsResult]()) - c.commandParamsSchemas["LayerTree.disable"] = abxjsonschema.SchemaFor[LayerTreeDisableParams]() - c.commandResultSchemas["LayerTree.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeDisableResult]()) - c.commandParamsSchemas["LayerTree.enable"] = abxjsonschema.SchemaFor[LayerTreeEnableParams]() - c.commandResultSchemas["LayerTree.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeEnableResult]()) - c.commandParamsSchemas["LayerTree.loadSnapshot"] = abxjsonschema.SchemaFor[LayerTreeLoadSnapshotParams]() - c.commandResultSchemas["LayerTree.loadSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLoadSnapshotResult]()) - c.commandParamsSchemas["LayerTree.makeSnapshot"] = abxjsonschema.SchemaFor[LayerTreeMakeSnapshotParams]() - c.commandResultSchemas["LayerTree.makeSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeMakeSnapshotResult]()) - c.commandParamsSchemas["LayerTree.profileSnapshot"] = abxjsonschema.SchemaFor[LayerTreeProfileSnapshotParams]() - c.commandResultSchemas["LayerTree.profileSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeProfileSnapshotResult]()) - c.commandParamsSchemas["LayerTree.releaseSnapshot"] = abxjsonschema.SchemaFor[LayerTreeReleaseSnapshotParams]() - c.commandResultSchemas["LayerTree.releaseSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeReleaseSnapshotResult]()) - c.commandParamsSchemas["LayerTree.replaySnapshot"] = abxjsonschema.SchemaFor[LayerTreeReplaySnapshotParams]() - c.commandResultSchemas["LayerTree.replaySnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeReplaySnapshotResult]()) - c.commandParamsSchemas["LayerTree.snapshotCommandLog"] = abxjsonschema.SchemaFor[LayerTreeSnapshotCommandLogParams]() - c.commandResultSchemas["LayerTree.snapshotCommandLog"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeSnapshotCommandLogResult]()) - c.commandParamsSchemas["Log.clear"] = abxjsonschema.SchemaFor[LogClearParams]() - c.commandResultSchemas["Log.clear"] = nativeResultSchema(abxjsonschema.SchemaFor[LogClearResult]()) - c.commandParamsSchemas["Log.disable"] = abxjsonschema.SchemaFor[LogDisableParams]() - c.commandResultSchemas["Log.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[LogDisableResult]()) - c.commandParamsSchemas["Log.enable"] = abxjsonschema.SchemaFor[LogEnableParams]() - c.commandResultSchemas["Log.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[LogEnableResult]()) - c.commandParamsSchemas["Log.startViolationsReport"] = abxjsonschema.SchemaFor[LogStartViolationsReportParams]() - c.commandResultSchemas["Log.startViolationsReport"] = nativeResultSchema(abxjsonschema.SchemaFor[LogStartViolationsReportResult]()) - c.commandParamsSchemas["Log.stopViolationsReport"] = abxjsonschema.SchemaFor[LogStopViolationsReportParams]() - c.commandResultSchemas["Log.stopViolationsReport"] = nativeResultSchema(abxjsonschema.SchemaFor[LogStopViolationsReportResult]()) - c.commandParamsSchemas["Media.enable"] = abxjsonschema.SchemaFor[MediaEnableParams]() - c.commandResultSchemas["Media.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaEnableResult]()) - c.commandParamsSchemas["Media.disable"] = abxjsonschema.SchemaFor[MediaDisableParams]() - c.commandResultSchemas["Media.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaDisableResult]()) - c.commandParamsSchemas["Memory.getDOMCounters"] = abxjsonschema.SchemaFor[MemoryGetDOMCountersParams]() - c.commandResultSchemas["Memory.getDOMCounters"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetDOMCountersResult]()) - c.commandParamsSchemas["Memory.getDOMCountersForLeakDetection"] = abxjsonschema.SchemaFor[MemoryGetDOMCountersForLeakDetectionParams]() - c.commandResultSchemas["Memory.getDOMCountersForLeakDetection"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetDOMCountersForLeakDetectionResult]()) - c.commandParamsSchemas["Memory.prepareForLeakDetection"] = abxjsonschema.SchemaFor[MemoryPrepareForLeakDetectionParams]() - c.commandResultSchemas["Memory.prepareForLeakDetection"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryPrepareForLeakDetectionResult]()) - c.commandParamsSchemas["Memory.forciblyPurgeJavaScriptMemory"] = abxjsonschema.SchemaFor[MemoryForciblyPurgeJavaScriptMemoryParams]() - c.commandResultSchemas["Memory.forciblyPurgeJavaScriptMemory"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryForciblyPurgeJavaScriptMemoryResult]()) - c.commandParamsSchemas["Memory.setPressureNotificationsSuppressed"] = abxjsonschema.SchemaFor[MemorySetPressureNotificationsSuppressedParams]() - c.commandResultSchemas["Memory.setPressureNotificationsSuppressed"] = nativeResultSchema(abxjsonschema.SchemaFor[MemorySetPressureNotificationsSuppressedResult]()) - c.commandParamsSchemas["Memory.simulatePressureNotification"] = abxjsonschema.SchemaFor[MemorySimulatePressureNotificationParams]() - c.commandResultSchemas["Memory.simulatePressureNotification"] = nativeResultSchema(abxjsonschema.SchemaFor[MemorySimulatePressureNotificationResult]()) - c.commandParamsSchemas["Memory.startSampling"] = abxjsonschema.SchemaFor[MemoryStartSamplingParams]() - c.commandResultSchemas["Memory.startSampling"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryStartSamplingResult]()) - c.commandParamsSchemas["Memory.stopSampling"] = abxjsonschema.SchemaFor[MemoryStopSamplingParams]() - c.commandResultSchemas["Memory.stopSampling"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryStopSamplingResult]()) - c.commandParamsSchemas["Memory.getAllTimeSamplingProfile"] = abxjsonschema.SchemaFor[MemoryGetAllTimeSamplingProfileParams]() - c.commandResultSchemas["Memory.getAllTimeSamplingProfile"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetAllTimeSamplingProfileResult]()) - c.commandParamsSchemas["Memory.getBrowserSamplingProfile"] = abxjsonschema.SchemaFor[MemoryGetBrowserSamplingProfileParams]() - c.commandResultSchemas["Memory.getBrowserSamplingProfile"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetBrowserSamplingProfileResult]()) - c.commandParamsSchemas["Memory.getSamplingProfile"] = abxjsonschema.SchemaFor[MemoryGetSamplingProfileParams]() - c.commandResultSchemas["Memory.getSamplingProfile"] = nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetSamplingProfileResult]()) - c.commandParamsSchemas["Network.setAcceptedEncodings"] = abxjsonschema.SchemaFor[NetworkSetAcceptedEncodingsParams]() - c.commandResultSchemas["Network.setAcceptedEncodings"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetAcceptedEncodingsResult]()) - c.commandParamsSchemas["Network.clearAcceptedEncodingsOverride"] = abxjsonschema.SchemaFor[NetworkClearAcceptedEncodingsOverrideParams]() - c.commandResultSchemas["Network.clearAcceptedEncodingsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearAcceptedEncodingsOverrideResult]()) - c.commandParamsSchemas["Network.canClearBrowserCache"] = abxjsonschema.SchemaFor[NetworkCanClearBrowserCacheParams]() - c.commandResultSchemas["Network.canClearBrowserCache"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanClearBrowserCacheResult]()) - c.commandParamsSchemas["Network.canClearBrowserCookies"] = abxjsonschema.SchemaFor[NetworkCanClearBrowserCookiesParams]() - c.commandResultSchemas["Network.canClearBrowserCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanClearBrowserCookiesResult]()) - c.commandParamsSchemas["Network.canEmulateNetworkConditions"] = abxjsonschema.SchemaFor[NetworkCanEmulateNetworkConditionsParams]() - c.commandResultSchemas["Network.canEmulateNetworkConditions"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanEmulateNetworkConditionsResult]()) - c.commandParamsSchemas["Network.clearBrowserCache"] = abxjsonschema.SchemaFor[NetworkClearBrowserCacheParams]() - c.commandResultSchemas["Network.clearBrowserCache"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearBrowserCacheResult]()) - c.commandParamsSchemas["Network.clearBrowserCookies"] = abxjsonschema.SchemaFor[NetworkClearBrowserCookiesParams]() - c.commandResultSchemas["Network.clearBrowserCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearBrowserCookiesResult]()) - c.commandParamsSchemas["Network.continueInterceptedRequest"] = abxjsonschema.SchemaFor[NetworkContinueInterceptedRequestParams]() - c.commandResultSchemas["Network.continueInterceptedRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkContinueInterceptedRequestResult]()) - c.commandParamsSchemas["Network.deleteCookies"] = abxjsonschema.SchemaFor[NetworkDeleteCookiesParams]() - c.commandResultSchemas["Network.deleteCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeleteCookiesResult]()) - c.commandParamsSchemas["Network.disable"] = abxjsonschema.SchemaFor[NetworkDisableParams]() - c.commandResultSchemas["Network.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDisableResult]()) - c.commandParamsSchemas["Network.emulateNetworkConditions"] = abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsParams]() - c.commandResultSchemas["Network.emulateNetworkConditions"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsResult]()) - c.commandParamsSchemas["Network.emulateNetworkConditionsByRule"] = abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsByRuleParams]() - c.commandResultSchemas["Network.emulateNetworkConditionsByRule"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsByRuleResult]()) - c.commandParamsSchemas["Network.overrideNetworkState"] = abxjsonschema.SchemaFor[NetworkOverrideNetworkStateParams]() - c.commandResultSchemas["Network.overrideNetworkState"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkOverrideNetworkStateResult]()) - c.commandParamsSchemas["Network.enable"] = abxjsonschema.SchemaFor[NetworkEnableParams]() - c.commandResultSchemas["Network.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableResult]()) - c.commandParamsSchemas["Network.configureDurableMessages"] = abxjsonschema.SchemaFor[NetworkConfigureDurableMessagesParams]() - c.commandResultSchemas["Network.configureDurableMessages"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkConfigureDurableMessagesResult]()) - c.commandParamsSchemas["Network.getAllCookies"] = abxjsonschema.SchemaFor[NetworkGetAllCookiesParams]() - c.commandResultSchemas["Network.getAllCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetAllCookiesResult]()) - c.commandParamsSchemas["Network.getCertificate"] = abxjsonschema.SchemaFor[NetworkGetCertificateParams]() - c.commandResultSchemas["Network.getCertificate"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetCertificateResult]()) - c.commandParamsSchemas["Network.getCookies"] = abxjsonschema.SchemaFor[NetworkGetCookiesParams]() - c.commandResultSchemas["Network.getCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetCookiesResult]()) - c.commandParamsSchemas["Network.getResponseBody"] = abxjsonschema.SchemaFor[NetworkGetResponseBodyParams]() - c.commandResultSchemas["Network.getResponseBody"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetResponseBodyResult]()) - c.commandParamsSchemas["Network.getRequestPostData"] = abxjsonschema.SchemaFor[NetworkGetRequestPostDataParams]() - c.commandResultSchemas["Network.getRequestPostData"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetRequestPostDataResult]()) - c.commandParamsSchemas["Network.getResponseBodyForInterception"] = abxjsonschema.SchemaFor[NetworkGetResponseBodyForInterceptionParams]() - c.commandResultSchemas["Network.getResponseBodyForInterception"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetResponseBodyForInterceptionResult]()) - c.commandParamsSchemas["Network.takeResponseBodyForInterceptionAsStream"] = abxjsonschema.SchemaFor[NetworkTakeResponseBodyForInterceptionAsStreamParams]() - c.commandResultSchemas["Network.takeResponseBodyForInterceptionAsStream"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkTakeResponseBodyForInterceptionAsStreamResult]()) - c.commandParamsSchemas["Network.replayXHR"] = abxjsonschema.SchemaFor[NetworkReplayXHRParams]() - c.commandResultSchemas["Network.replayXHR"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReplayXHRResult]()) - c.commandParamsSchemas["Network.searchInResponseBody"] = abxjsonschema.SchemaFor[NetworkSearchInResponseBodyParams]() - c.commandResultSchemas["Network.searchInResponseBody"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSearchInResponseBodyResult]()) - c.commandParamsSchemas["Network.setBlockedURLs"] = abxjsonschema.SchemaFor[NetworkSetBlockedURLsParams]() - c.commandResultSchemas["Network.setBlockedURLs"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetBlockedURLsResult]()) - c.commandParamsSchemas["Network.setBypassServiceWorker"] = abxjsonschema.SchemaFor[NetworkSetBypassServiceWorkerParams]() - c.commandResultSchemas["Network.setBypassServiceWorker"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetBypassServiceWorkerResult]()) - c.commandParamsSchemas["Network.setCacheDisabled"] = abxjsonschema.SchemaFor[NetworkSetCacheDisabledParams]() - c.commandResultSchemas["Network.setCacheDisabled"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCacheDisabledResult]()) - c.commandParamsSchemas["Network.setCookie"] = abxjsonschema.SchemaFor[NetworkSetCookieParams]() - c.commandResultSchemas["Network.setCookie"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookieResult]()) - c.commandParamsSchemas["Network.setCookies"] = abxjsonschema.SchemaFor[NetworkSetCookiesParams]() - c.commandResultSchemas["Network.setCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookiesResult]()) - c.commandParamsSchemas["Network.setExtraHTTPHeaders"] = abxjsonschema.SchemaFor[NetworkSetExtraHTTPHeadersParams]() - c.commandResultSchemas["Network.setExtraHTTPHeaders"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetExtraHTTPHeadersResult]()) - c.commandParamsSchemas["Network.setAttachDebugStack"] = abxjsonschema.SchemaFor[NetworkSetAttachDebugStackParams]() - c.commandResultSchemas["Network.setAttachDebugStack"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetAttachDebugStackResult]()) - c.commandParamsSchemas["Network.setRequestInterception"] = abxjsonschema.SchemaFor[NetworkSetRequestInterceptionParams]() - c.commandResultSchemas["Network.setRequestInterception"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetRequestInterceptionResult]()) - c.commandParamsSchemas["Network.setUserAgentOverride"] = abxjsonschema.SchemaFor[NetworkSetUserAgentOverrideParams]() - c.commandResultSchemas["Network.setUserAgentOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetUserAgentOverrideResult]()) - c.commandParamsSchemas["Network.streamResourceContent"] = abxjsonschema.SchemaFor[NetworkStreamResourceContentParams]() - c.commandResultSchemas["Network.streamResourceContent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkStreamResourceContentResult]()) - c.commandParamsSchemas["Network.getSecurityIsolationStatus"] = abxjsonschema.SchemaFor[NetworkGetSecurityIsolationStatusParams]() - c.commandResultSchemas["Network.getSecurityIsolationStatus"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetSecurityIsolationStatusResult]()) - c.commandParamsSchemas["Network.enableReportingApi"] = abxjsonschema.SchemaFor[NetworkEnableReportingAPIParams]() - c.commandResultSchemas["Network.enableReportingApi"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableReportingAPIResult]()) - c.commandParamsSchemas["Network.enableDeviceBoundSessions"] = abxjsonschema.SchemaFor[NetworkEnableDeviceBoundSessionsParams]() - c.commandResultSchemas["Network.enableDeviceBoundSessions"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableDeviceBoundSessionsResult]()) - c.commandParamsSchemas["Network.fetchSchemefulSite"] = abxjsonschema.SchemaFor[NetworkFetchSchemefulSiteParams]() - c.commandResultSchemas["Network.fetchSchemefulSite"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkFetchSchemefulSiteResult]()) - c.commandParamsSchemas["Network.loadNetworkResource"] = abxjsonschema.SchemaFor[NetworkLoadNetworkResourceParams]() - c.commandResultSchemas["Network.loadNetworkResource"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadNetworkResourceResult]()) - c.commandParamsSchemas["Network.setCookieControls"] = abxjsonschema.SchemaFor[NetworkSetCookieControlsParams]() - c.commandResultSchemas["Network.setCookieControls"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookieControlsResult]()) - c.commandParamsSchemas["Overlay.disable"] = abxjsonschema.SchemaFor[OverlayDisableParams]() - c.commandResultSchemas["Overlay.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayDisableResult]()) - c.commandParamsSchemas["Overlay.enable"] = abxjsonschema.SchemaFor[OverlayEnableParams]() - c.commandResultSchemas["Overlay.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayEnableResult]()) - c.commandParamsSchemas["Overlay.getHighlightObjectForTest"] = abxjsonschema.SchemaFor[OverlayGetHighlightObjectForTestParams]() - c.commandResultSchemas["Overlay.getHighlightObjectForTest"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetHighlightObjectForTestResult]()) - c.commandParamsSchemas["Overlay.getGridHighlightObjectsForTest"] = abxjsonschema.SchemaFor[OverlayGetGridHighlightObjectsForTestParams]() - c.commandResultSchemas["Overlay.getGridHighlightObjectsForTest"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetGridHighlightObjectsForTestResult]()) - c.commandParamsSchemas["Overlay.getSourceOrderHighlightObjectForTest"] = abxjsonschema.SchemaFor[OverlayGetSourceOrderHighlightObjectForTestParams]() - c.commandResultSchemas["Overlay.getSourceOrderHighlightObjectForTest"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetSourceOrderHighlightObjectForTestResult]()) - c.commandParamsSchemas["Overlay.hideHighlight"] = abxjsonschema.SchemaFor[OverlayHideHighlightParams]() - c.commandResultSchemas["Overlay.hideHighlight"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHideHighlightResult]()) - c.commandParamsSchemas["Overlay.highlightFrame"] = abxjsonschema.SchemaFor[OverlayHighlightFrameParams]() - c.commandResultSchemas["Overlay.highlightFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightFrameResult]()) - c.commandParamsSchemas["Overlay.highlightNode"] = abxjsonschema.SchemaFor[OverlayHighlightNodeParams]() - c.commandResultSchemas["Overlay.highlightNode"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightNodeResult]()) - c.commandParamsSchemas["Overlay.highlightQuad"] = abxjsonschema.SchemaFor[OverlayHighlightQuadParams]() - c.commandResultSchemas["Overlay.highlightQuad"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightQuadResult]()) - c.commandParamsSchemas["Overlay.highlightRect"] = abxjsonschema.SchemaFor[OverlayHighlightRectParams]() - c.commandResultSchemas["Overlay.highlightRect"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightRectResult]()) - c.commandParamsSchemas["Overlay.highlightSourceOrder"] = abxjsonschema.SchemaFor[OverlayHighlightSourceOrderParams]() - c.commandResultSchemas["Overlay.highlightSourceOrder"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightSourceOrderResult]()) - c.commandParamsSchemas["Overlay.setInspectMode"] = abxjsonschema.SchemaFor[OverlaySetInspectModeParams]() - c.commandResultSchemas["Overlay.setInspectMode"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetInspectModeResult]()) - c.commandParamsSchemas["Overlay.setShowAdHighlights"] = abxjsonschema.SchemaFor[OverlaySetShowAdHighlightsParams]() - c.commandResultSchemas["Overlay.setShowAdHighlights"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowAdHighlightsResult]()) - c.commandParamsSchemas["Overlay.setPausedInDebuggerMessage"] = abxjsonschema.SchemaFor[OverlaySetPausedInDebuggerMessageParams]() - c.commandResultSchemas["Overlay.setPausedInDebuggerMessage"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetPausedInDebuggerMessageResult]()) - c.commandParamsSchemas["Overlay.setShowDebugBorders"] = abxjsonschema.SchemaFor[OverlaySetShowDebugBordersParams]() - c.commandResultSchemas["Overlay.setShowDebugBorders"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowDebugBordersResult]()) - c.commandParamsSchemas["Overlay.setShowFPSCounter"] = abxjsonschema.SchemaFor[OverlaySetShowFPSCounterParams]() - c.commandResultSchemas["Overlay.setShowFPSCounter"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowFPSCounterResult]()) - c.commandParamsSchemas["Overlay.setShowGridOverlays"] = abxjsonschema.SchemaFor[OverlaySetShowGridOverlaysParams]() - c.commandResultSchemas["Overlay.setShowGridOverlays"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowGridOverlaysResult]()) - c.commandParamsSchemas["Overlay.setShowFlexOverlays"] = abxjsonschema.SchemaFor[OverlaySetShowFlexOverlaysParams]() - c.commandResultSchemas["Overlay.setShowFlexOverlays"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowFlexOverlaysResult]()) - c.commandParamsSchemas["Overlay.setShowScrollSnapOverlays"] = abxjsonschema.SchemaFor[OverlaySetShowScrollSnapOverlaysParams]() - c.commandResultSchemas["Overlay.setShowScrollSnapOverlays"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowScrollSnapOverlaysResult]()) - c.commandParamsSchemas["Overlay.setShowContainerQueryOverlays"] = abxjsonschema.SchemaFor[OverlaySetShowContainerQueryOverlaysParams]() - c.commandResultSchemas["Overlay.setShowContainerQueryOverlays"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowContainerQueryOverlaysResult]()) - c.commandParamsSchemas["Overlay.setShowInspectedElementAnchor"] = abxjsonschema.SchemaFor[OverlaySetShowInspectedElementAnchorParams]() - c.commandResultSchemas["Overlay.setShowInspectedElementAnchor"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowInspectedElementAnchorResult]()) - c.commandParamsSchemas["Overlay.setShowPaintRects"] = abxjsonschema.SchemaFor[OverlaySetShowPaintRectsParams]() - c.commandResultSchemas["Overlay.setShowPaintRects"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowPaintRectsResult]()) - c.commandParamsSchemas["Overlay.setShowLayoutShiftRegions"] = abxjsonschema.SchemaFor[OverlaySetShowLayoutShiftRegionsParams]() - c.commandResultSchemas["Overlay.setShowLayoutShiftRegions"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowLayoutShiftRegionsResult]()) - c.commandParamsSchemas["Overlay.setShowScrollBottleneckRects"] = abxjsonschema.SchemaFor[OverlaySetShowScrollBottleneckRectsParams]() - c.commandResultSchemas["Overlay.setShowScrollBottleneckRects"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowScrollBottleneckRectsResult]()) - c.commandParamsSchemas["Overlay.setShowHitTestBorders"] = abxjsonschema.SchemaFor[OverlaySetShowHitTestBordersParams]() - c.commandResultSchemas["Overlay.setShowHitTestBorders"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowHitTestBordersResult]()) - c.commandParamsSchemas["Overlay.setShowWebVitals"] = abxjsonschema.SchemaFor[OverlaySetShowWebVitalsParams]() - c.commandResultSchemas["Overlay.setShowWebVitals"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowWebVitalsResult]()) - c.commandParamsSchemas["Overlay.setShowViewportSizeOnResize"] = abxjsonschema.SchemaFor[OverlaySetShowViewportSizeOnResizeParams]() - c.commandResultSchemas["Overlay.setShowViewportSizeOnResize"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowViewportSizeOnResizeResult]()) - c.commandParamsSchemas["Overlay.setShowHinge"] = abxjsonschema.SchemaFor[OverlaySetShowHingeParams]() - c.commandResultSchemas["Overlay.setShowHinge"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowHingeResult]()) - c.commandParamsSchemas["Overlay.setShowIsolatedElements"] = abxjsonschema.SchemaFor[OverlaySetShowIsolatedElementsParams]() - c.commandResultSchemas["Overlay.setShowIsolatedElements"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowIsolatedElementsResult]()) - c.commandParamsSchemas["Overlay.setShowWindowControlsOverlay"] = abxjsonschema.SchemaFor[OverlaySetShowWindowControlsOverlayParams]() - c.commandResultSchemas["Overlay.setShowWindowControlsOverlay"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowWindowControlsOverlayResult]()) - c.commandParamsSchemas["PWA.getOsAppState"] = abxjsonschema.SchemaFor[PWAGetOsAppStateParams]() - c.commandResultSchemas["PWA.getOsAppState"] = nativeResultSchema(abxjsonschema.SchemaFor[PWAGetOsAppStateResult]()) - c.commandParamsSchemas["PWA.install"] = abxjsonschema.SchemaFor[PWAInstallParams]() - c.commandResultSchemas["PWA.install"] = nativeResultSchema(abxjsonschema.SchemaFor[PWAInstallResult]()) - c.commandParamsSchemas["PWA.uninstall"] = abxjsonschema.SchemaFor[PWAUninstallParams]() - c.commandResultSchemas["PWA.uninstall"] = nativeResultSchema(abxjsonschema.SchemaFor[PWAUninstallResult]()) - c.commandParamsSchemas["PWA.launch"] = abxjsonschema.SchemaFor[PWALaunchParams]() - c.commandResultSchemas["PWA.launch"] = nativeResultSchema(abxjsonschema.SchemaFor[PWALaunchResult]()) - c.commandParamsSchemas["PWA.launchFilesInApp"] = abxjsonschema.SchemaFor[PWALaunchFilesInAppParams]() - c.commandResultSchemas["PWA.launchFilesInApp"] = nativeResultSchema(abxjsonschema.SchemaFor[PWALaunchFilesInAppResult]()) - c.commandParamsSchemas["PWA.openCurrentPageInApp"] = abxjsonschema.SchemaFor[PWAOpenCurrentPageInAppParams]() - c.commandResultSchemas["PWA.openCurrentPageInApp"] = nativeResultSchema(abxjsonschema.SchemaFor[PWAOpenCurrentPageInAppResult]()) - c.commandParamsSchemas["PWA.changeAppUserSettings"] = abxjsonschema.SchemaFor[PWAChangeAppUserSettingsParams]() - c.commandResultSchemas["PWA.changeAppUserSettings"] = nativeResultSchema(abxjsonschema.SchemaFor[PWAChangeAppUserSettingsResult]()) - c.commandParamsSchemas["Page.addScriptToEvaluateOnLoad"] = abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnLoadParams]() - c.commandResultSchemas["Page.addScriptToEvaluateOnLoad"] = nativeResultSchema(abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnLoadResult]()) - c.commandParamsSchemas["Page.addScriptToEvaluateOnNewDocument"] = abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnNewDocumentParams]() - c.commandResultSchemas["Page.addScriptToEvaluateOnNewDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnNewDocumentResult]()) - c.commandParamsSchemas["Page.bringToFront"] = abxjsonschema.SchemaFor[PageBringToFrontParams]() - c.commandResultSchemas["Page.bringToFront"] = nativeResultSchema(abxjsonschema.SchemaFor[PageBringToFrontResult]()) - c.commandParamsSchemas["Page.captureScreenshot"] = abxjsonschema.SchemaFor[PageCaptureScreenshotParams]() - c.commandResultSchemas["Page.captureScreenshot"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCaptureScreenshotResult]()) - c.commandParamsSchemas["Page.captureSnapshot"] = abxjsonschema.SchemaFor[PageCaptureSnapshotParams]() - c.commandResultSchemas["Page.captureSnapshot"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCaptureSnapshotResult]()) - c.commandParamsSchemas["Page.clearDeviceMetricsOverride"] = abxjsonschema.SchemaFor[PageClearDeviceMetricsOverrideParams]() - c.commandResultSchemas["Page.clearDeviceMetricsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageClearDeviceMetricsOverrideResult]()) - c.commandParamsSchemas["Page.clearDeviceOrientationOverride"] = abxjsonschema.SchemaFor[PageClearDeviceOrientationOverrideParams]() - c.commandResultSchemas["Page.clearDeviceOrientationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageClearDeviceOrientationOverrideResult]()) - c.commandParamsSchemas["Page.clearGeolocationOverride"] = abxjsonschema.SchemaFor[PageClearGeolocationOverrideParams]() - c.commandResultSchemas["Page.clearGeolocationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageClearGeolocationOverrideResult]()) - c.commandParamsSchemas["Page.createIsolatedWorld"] = abxjsonschema.SchemaFor[PageCreateIsolatedWorldParams]() - c.commandResultSchemas["Page.createIsolatedWorld"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCreateIsolatedWorldResult]()) - c.commandParamsSchemas["Page.deleteCookie"] = abxjsonschema.SchemaFor[PageDeleteCookieParams]() - c.commandResultSchemas["Page.deleteCookie"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDeleteCookieResult]()) - c.commandParamsSchemas["Page.disable"] = abxjsonschema.SchemaFor[PageDisableParams]() - c.commandResultSchemas["Page.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDisableResult]()) - c.commandParamsSchemas["Page.enable"] = abxjsonschema.SchemaFor[PageEnableParams]() - c.commandResultSchemas["Page.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[PageEnableResult]()) - c.commandParamsSchemas["Page.getAppManifest"] = abxjsonschema.SchemaFor[PageGetAppManifestParams]() - c.commandResultSchemas["Page.getAppManifest"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetAppManifestResult]()) - c.commandParamsSchemas["Page.getInstallabilityErrors"] = abxjsonschema.SchemaFor[PageGetInstallabilityErrorsParams]() - c.commandResultSchemas["Page.getInstallabilityErrors"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetInstallabilityErrorsResult]()) - c.commandParamsSchemas["Page.getManifestIcons"] = abxjsonschema.SchemaFor[PageGetManifestIconsParams]() - c.commandResultSchemas["Page.getManifestIcons"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetManifestIconsResult]()) - c.commandParamsSchemas["Page.getAppId"] = abxjsonschema.SchemaFor[PageGetAppIDParams]() - c.commandResultSchemas["Page.getAppId"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetAppIDResult]()) - c.commandParamsSchemas["Page.getAdScriptAncestry"] = abxjsonschema.SchemaFor[PageGetAdScriptAncestryParams]() - c.commandResultSchemas["Page.getAdScriptAncestry"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetAdScriptAncestryResult]()) - c.commandParamsSchemas["Page.getFrameTree"] = abxjsonschema.SchemaFor[PageGetFrameTreeParams]() - c.commandResultSchemas["Page.getFrameTree"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetFrameTreeResult]()) - c.commandParamsSchemas["Page.getLayoutMetrics"] = abxjsonschema.SchemaFor[PageGetLayoutMetricsParams]() - c.commandResultSchemas["Page.getLayoutMetrics"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetLayoutMetricsResult]()) - c.commandParamsSchemas["Page.getNavigationHistory"] = abxjsonschema.SchemaFor[PageGetNavigationHistoryParams]() - c.commandResultSchemas["Page.getNavigationHistory"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetNavigationHistoryResult]()) - c.commandParamsSchemas["Page.resetNavigationHistory"] = abxjsonschema.SchemaFor[PageResetNavigationHistoryParams]() - c.commandResultSchemas["Page.resetNavigationHistory"] = nativeResultSchema(abxjsonschema.SchemaFor[PageResetNavigationHistoryResult]()) - c.commandParamsSchemas["Page.getResourceContent"] = abxjsonschema.SchemaFor[PageGetResourceContentParams]() - c.commandResultSchemas["Page.getResourceContent"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetResourceContentResult]()) - c.commandParamsSchemas["Page.getResourceTree"] = abxjsonschema.SchemaFor[PageGetResourceTreeParams]() - c.commandResultSchemas["Page.getResourceTree"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetResourceTreeResult]()) - c.commandParamsSchemas["Page.handleJavaScriptDialog"] = abxjsonschema.SchemaFor[PageHandleJavaScriptDialogParams]() - c.commandResultSchemas["Page.handleJavaScriptDialog"] = nativeResultSchema(abxjsonschema.SchemaFor[PageHandleJavaScriptDialogResult]()) - c.commandParamsSchemas["Page.navigate"] = abxjsonschema.SchemaFor[PageNavigateParams]() - c.commandResultSchemas["Page.navigate"] = nativeResultSchema(abxjsonschema.SchemaFor[PageNavigateResult]()) - c.commandParamsSchemas["Page.navigateToHistoryEntry"] = abxjsonschema.SchemaFor[PageNavigateToHistoryEntryParams]() - c.commandResultSchemas["Page.navigateToHistoryEntry"] = nativeResultSchema(abxjsonschema.SchemaFor[PageNavigateToHistoryEntryResult]()) - c.commandParamsSchemas["Page.printToPDF"] = abxjsonschema.SchemaFor[PagePrintToPDFParams]() - c.commandResultSchemas["Page.printToPDF"] = nativeResultSchema(abxjsonschema.SchemaFor[PagePrintToPDFResult]()) - c.commandParamsSchemas["Page.reload"] = abxjsonschema.SchemaFor[PageReloadParams]() - c.commandResultSchemas["Page.reload"] = nativeResultSchema(abxjsonschema.SchemaFor[PageReloadResult]()) - c.commandParamsSchemas["Page.removeScriptToEvaluateOnLoad"] = abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnLoadParams]() - c.commandResultSchemas["Page.removeScriptToEvaluateOnLoad"] = nativeResultSchema(abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnLoadResult]()) - c.commandParamsSchemas["Page.removeScriptToEvaluateOnNewDocument"] = abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnNewDocumentParams]() - c.commandResultSchemas["Page.removeScriptToEvaluateOnNewDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnNewDocumentResult]()) - c.commandParamsSchemas["Page.screencastFrameAck"] = abxjsonschema.SchemaFor[PageScreencastFrameAckParams]() - c.commandResultSchemas["Page.screencastFrameAck"] = nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastFrameAckResult]()) - c.commandParamsSchemas["Page.searchInResource"] = abxjsonschema.SchemaFor[PageSearchInResourceParams]() - c.commandResultSchemas["Page.searchInResource"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSearchInResourceResult]()) - c.commandParamsSchemas["Page.setAdBlockingEnabled"] = abxjsonschema.SchemaFor[PageSetAdBlockingEnabledParams]() - c.commandResultSchemas["Page.setAdBlockingEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetAdBlockingEnabledResult]()) - c.commandParamsSchemas["Page.setBypassCSP"] = abxjsonschema.SchemaFor[PageSetBypassCSPParams]() - c.commandResultSchemas["Page.setBypassCSP"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetBypassCSPResult]()) - c.commandParamsSchemas["Page.getPermissionsPolicyState"] = abxjsonschema.SchemaFor[PageGetPermissionsPolicyStateParams]() - c.commandResultSchemas["Page.getPermissionsPolicyState"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetPermissionsPolicyStateResult]()) - c.commandParamsSchemas["Page.getOriginTrials"] = abxjsonschema.SchemaFor[PageGetOriginTrialsParams]() - c.commandResultSchemas["Page.getOriginTrials"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetOriginTrialsResult]()) - c.commandParamsSchemas["Page.setDeviceMetricsOverride"] = abxjsonschema.SchemaFor[PageSetDeviceMetricsOverrideParams]() - c.commandResultSchemas["Page.setDeviceMetricsOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetDeviceMetricsOverrideResult]()) - c.commandParamsSchemas["Page.setDeviceOrientationOverride"] = abxjsonschema.SchemaFor[PageSetDeviceOrientationOverrideParams]() - c.commandResultSchemas["Page.setDeviceOrientationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetDeviceOrientationOverrideResult]()) - c.commandParamsSchemas["Page.setFontFamilies"] = abxjsonschema.SchemaFor[PageSetFontFamiliesParams]() - c.commandResultSchemas["Page.setFontFamilies"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetFontFamiliesResult]()) - c.commandParamsSchemas["Page.setFontSizes"] = abxjsonschema.SchemaFor[PageSetFontSizesParams]() - c.commandResultSchemas["Page.setFontSizes"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetFontSizesResult]()) - c.commandParamsSchemas["Page.setDocumentContent"] = abxjsonschema.SchemaFor[PageSetDocumentContentParams]() - c.commandResultSchemas["Page.setDocumentContent"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetDocumentContentResult]()) - c.commandParamsSchemas["Page.setDownloadBehavior"] = abxjsonschema.SchemaFor[PageSetDownloadBehaviorParams]() - c.commandResultSchemas["Page.setDownloadBehavior"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetDownloadBehaviorResult]()) - c.commandParamsSchemas["Page.setGeolocationOverride"] = abxjsonschema.SchemaFor[PageSetGeolocationOverrideParams]() - c.commandResultSchemas["Page.setGeolocationOverride"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetGeolocationOverrideResult]()) - c.commandParamsSchemas["Page.setLifecycleEventsEnabled"] = abxjsonschema.SchemaFor[PageSetLifecycleEventsEnabledParams]() - c.commandResultSchemas["Page.setLifecycleEventsEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetLifecycleEventsEnabledResult]()) - c.commandParamsSchemas["Page.setTouchEmulationEnabled"] = abxjsonschema.SchemaFor[PageSetTouchEmulationEnabledParams]() - c.commandResultSchemas["Page.setTouchEmulationEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetTouchEmulationEnabledResult]()) - c.commandParamsSchemas["Page.startScreencast"] = abxjsonschema.SchemaFor[PageStartScreencastParams]() - c.commandResultSchemas["Page.startScreencast"] = nativeResultSchema(abxjsonschema.SchemaFor[PageStartScreencastResult]()) - c.commandParamsSchemas["Page.stopLoading"] = abxjsonschema.SchemaFor[PageStopLoadingParams]() - c.commandResultSchemas["Page.stopLoading"] = nativeResultSchema(abxjsonschema.SchemaFor[PageStopLoadingResult]()) - c.commandParamsSchemas["Page.crash"] = abxjsonschema.SchemaFor[PageCrashParams]() - c.commandResultSchemas["Page.crash"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCrashResult]()) - c.commandParamsSchemas["Page.close"] = abxjsonschema.SchemaFor[PageCloseParams]() - c.commandResultSchemas["Page.close"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCloseResult]()) - c.commandParamsSchemas["Page.setWebLifecycleState"] = abxjsonschema.SchemaFor[PageSetWebLifecycleStateParams]() - c.commandResultSchemas["Page.setWebLifecycleState"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetWebLifecycleStateResult]()) - c.commandParamsSchemas["Page.stopScreencast"] = abxjsonschema.SchemaFor[PageStopScreencastParams]() - c.commandResultSchemas["Page.stopScreencast"] = nativeResultSchema(abxjsonschema.SchemaFor[PageStopScreencastResult]()) - c.commandParamsSchemas["Page.produceCompilationCache"] = abxjsonschema.SchemaFor[PageProduceCompilationCacheParams]() - c.commandResultSchemas["Page.produceCompilationCache"] = nativeResultSchema(abxjsonschema.SchemaFor[PageProduceCompilationCacheResult]()) - c.commandParamsSchemas["Page.addCompilationCache"] = abxjsonschema.SchemaFor[PageAddCompilationCacheParams]() - c.commandResultSchemas["Page.addCompilationCache"] = nativeResultSchema(abxjsonschema.SchemaFor[PageAddCompilationCacheResult]()) - c.commandParamsSchemas["Page.clearCompilationCache"] = abxjsonschema.SchemaFor[PageClearCompilationCacheParams]() - c.commandResultSchemas["Page.clearCompilationCache"] = nativeResultSchema(abxjsonschema.SchemaFor[PageClearCompilationCacheResult]()) - c.commandParamsSchemas["Page.setSPCTransactionMode"] = abxjsonschema.SchemaFor[PageSetSPCTransactionModeParams]() - c.commandResultSchemas["Page.setSPCTransactionMode"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetSPCTransactionModeResult]()) - c.commandParamsSchemas["Page.setRPHRegistrationMode"] = abxjsonschema.SchemaFor[PageSetRPHRegistrationModeParams]() - c.commandResultSchemas["Page.setRPHRegistrationMode"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetRPHRegistrationModeResult]()) - c.commandParamsSchemas["Page.generateTestReport"] = abxjsonschema.SchemaFor[PageGenerateTestReportParams]() - c.commandResultSchemas["Page.generateTestReport"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGenerateTestReportResult]()) - c.commandParamsSchemas["Page.waitForDebugger"] = abxjsonschema.SchemaFor[PageWaitForDebuggerParams]() - c.commandResultSchemas["Page.waitForDebugger"] = nativeResultSchema(abxjsonschema.SchemaFor[PageWaitForDebuggerResult]()) - c.commandParamsSchemas["Page.setInterceptFileChooserDialog"] = abxjsonschema.SchemaFor[PageSetInterceptFileChooserDialogParams]() - c.commandResultSchemas["Page.setInterceptFileChooserDialog"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetInterceptFileChooserDialogResult]()) - c.commandParamsSchemas["Page.setPrerenderingAllowed"] = abxjsonschema.SchemaFor[PageSetPrerenderingAllowedParams]() - c.commandResultSchemas["Page.setPrerenderingAllowed"] = nativeResultSchema(abxjsonschema.SchemaFor[PageSetPrerenderingAllowedResult]()) - c.commandParamsSchemas["Page.getAnnotatedPageContent"] = abxjsonschema.SchemaFor[PageGetAnnotatedPageContentParams]() - c.commandResultSchemas["Page.getAnnotatedPageContent"] = nativeResultSchema(abxjsonschema.SchemaFor[PageGetAnnotatedPageContentResult]()) - c.commandParamsSchemas["Performance.disable"] = abxjsonschema.SchemaFor[PerformanceDisableParams]() - c.commandResultSchemas["Performance.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceDisableResult]()) - c.commandParamsSchemas["Performance.enable"] = abxjsonschema.SchemaFor[PerformanceEnableParams]() - c.commandResultSchemas["Performance.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceEnableResult]()) - c.commandParamsSchemas["Performance.setTimeDomain"] = abxjsonschema.SchemaFor[PerformanceSetTimeDomainParams]() - c.commandResultSchemas["Performance.setTimeDomain"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceSetTimeDomainResult]()) - c.commandParamsSchemas["Performance.getMetrics"] = abxjsonschema.SchemaFor[PerformanceGetMetricsParams]() - c.commandResultSchemas["Performance.getMetrics"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceGetMetricsResult]()) - c.commandParamsSchemas["PerformanceTimeline.enable"] = abxjsonschema.SchemaFor[PerformanceTimelineEnableParams]() - c.commandResultSchemas["PerformanceTimeline.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceTimelineEnableResult]()) - c.commandParamsSchemas["Preload.enable"] = abxjsonschema.SchemaFor[PreloadEnableParams]() - c.commandResultSchemas["Preload.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadEnableResult]()) - c.commandParamsSchemas["Preload.disable"] = abxjsonschema.SchemaFor[PreloadDisableParams]() - c.commandResultSchemas["Preload.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadDisableResult]()) - c.commandParamsSchemas["Profiler.disable"] = abxjsonschema.SchemaFor[ProfilerDisableParams]() - c.commandResultSchemas["Profiler.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerDisableResult]()) - c.commandParamsSchemas["Profiler.enable"] = abxjsonschema.SchemaFor[ProfilerEnableParams]() - c.commandResultSchemas["Profiler.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerEnableResult]()) - c.commandParamsSchemas["Profiler.getBestEffortCoverage"] = abxjsonschema.SchemaFor[ProfilerGetBestEffortCoverageParams]() - c.commandResultSchemas["Profiler.getBestEffortCoverage"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerGetBestEffortCoverageResult]()) - c.commandParamsSchemas["Profiler.setSamplingInterval"] = abxjsonschema.SchemaFor[ProfilerSetSamplingIntervalParams]() - c.commandResultSchemas["Profiler.setSamplingInterval"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerSetSamplingIntervalResult]()) - c.commandParamsSchemas["Profiler.start"] = abxjsonschema.SchemaFor[ProfilerStartParams]() - c.commandResultSchemas["Profiler.start"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStartResult]()) - c.commandParamsSchemas["Profiler.startPreciseCoverage"] = abxjsonschema.SchemaFor[ProfilerStartPreciseCoverageParams]() - c.commandResultSchemas["Profiler.startPreciseCoverage"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStartPreciseCoverageResult]()) - c.commandParamsSchemas["Profiler.stop"] = abxjsonschema.SchemaFor[ProfilerStopParams]() - c.commandResultSchemas["Profiler.stop"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStopResult]()) - c.commandParamsSchemas["Profiler.stopPreciseCoverage"] = abxjsonschema.SchemaFor[ProfilerStopPreciseCoverageParams]() - c.commandResultSchemas["Profiler.stopPreciseCoverage"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStopPreciseCoverageResult]()) - c.commandParamsSchemas["Profiler.takePreciseCoverage"] = abxjsonschema.SchemaFor[ProfilerTakePreciseCoverageParams]() - c.commandResultSchemas["Profiler.takePreciseCoverage"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerTakePreciseCoverageResult]()) - c.commandParamsSchemas["Runtime.awaitPromise"] = abxjsonschema.SchemaFor[RuntimeAwaitPromiseParams]() - c.commandResultSchemas["Runtime.awaitPromise"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeAwaitPromiseResult]()) - c.commandParamsSchemas["Runtime.callFunctionOn"] = abxjsonschema.SchemaFor[RuntimeCallFunctionOnParams]() - c.commandResultSchemas["Runtime.callFunctionOn"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeCallFunctionOnResult]()) - c.commandParamsSchemas["Runtime.compileScript"] = abxjsonschema.SchemaFor[RuntimeCompileScriptParams]() - c.commandResultSchemas["Runtime.compileScript"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeCompileScriptResult]()) - c.commandParamsSchemas["Runtime.disable"] = abxjsonschema.SchemaFor[RuntimeDisableParams]() - c.commandResultSchemas["Runtime.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeDisableResult]()) - c.commandParamsSchemas["Runtime.discardConsoleEntries"] = abxjsonschema.SchemaFor[RuntimeDiscardConsoleEntriesParams]() - c.commandResultSchemas["Runtime.discardConsoleEntries"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeDiscardConsoleEntriesResult]()) - c.commandParamsSchemas["Runtime.enable"] = abxjsonschema.SchemaFor[RuntimeEnableParams]() - c.commandResultSchemas["Runtime.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeEnableResult]()) - c.commandParamsSchemas["Runtime.evaluate"] = abxjsonschema.SchemaFor[RuntimeEvaluateParams]() - c.commandResultSchemas["Runtime.evaluate"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeEvaluateResult]()) - c.commandParamsSchemas["Runtime.getIsolateId"] = abxjsonschema.SchemaFor[RuntimeGetIsolateIDParams]() - c.commandResultSchemas["Runtime.getIsolateId"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetIsolateIDResult]()) - c.commandParamsSchemas["Runtime.getHeapUsage"] = abxjsonschema.SchemaFor[RuntimeGetHeapUsageParams]() - c.commandResultSchemas["Runtime.getHeapUsage"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetHeapUsageResult]()) - c.commandParamsSchemas["Runtime.getProperties"] = abxjsonschema.SchemaFor[RuntimeGetPropertiesParams]() - c.commandResultSchemas["Runtime.getProperties"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetPropertiesResult]()) - c.commandParamsSchemas["Runtime.globalLexicalScopeNames"] = abxjsonschema.SchemaFor[RuntimeGlobalLexicalScopeNamesParams]() - c.commandResultSchemas["Runtime.globalLexicalScopeNames"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGlobalLexicalScopeNamesResult]()) - c.commandParamsSchemas["Runtime.queryObjects"] = abxjsonschema.SchemaFor[RuntimeQueryObjectsParams]() - c.commandResultSchemas["Runtime.queryObjects"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeQueryObjectsResult]()) - c.commandParamsSchemas["Runtime.releaseObject"] = abxjsonschema.SchemaFor[RuntimeReleaseObjectParams]() - c.commandResultSchemas["Runtime.releaseObject"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeReleaseObjectResult]()) - c.commandParamsSchemas["Runtime.releaseObjectGroup"] = abxjsonschema.SchemaFor[RuntimeReleaseObjectGroupParams]() - c.commandResultSchemas["Runtime.releaseObjectGroup"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeReleaseObjectGroupResult]()) - c.commandParamsSchemas["Runtime.runIfWaitingForDebugger"] = abxjsonschema.SchemaFor[RuntimeRunIfWaitingForDebuggerParams]() - c.commandResultSchemas["Runtime.runIfWaitingForDebugger"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRunIfWaitingForDebuggerResult]()) - c.commandParamsSchemas["Runtime.runScript"] = abxjsonschema.SchemaFor[RuntimeRunScriptParams]() - c.commandResultSchemas["Runtime.runScript"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRunScriptResult]()) - c.commandParamsSchemas["Runtime.setAsyncCallStackDepth"] = abxjsonschema.SchemaFor[RuntimeSetAsyncCallStackDepthParams]() - c.commandResultSchemas["Runtime.setAsyncCallStackDepth"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetAsyncCallStackDepthResult]()) - c.commandParamsSchemas["Runtime.setCustomObjectFormatterEnabled"] = abxjsonschema.SchemaFor[RuntimeSetCustomObjectFormatterEnabledParams]() - c.commandResultSchemas["Runtime.setCustomObjectFormatterEnabled"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetCustomObjectFormatterEnabledResult]()) - c.commandParamsSchemas["Runtime.setMaxCallStackSizeToCapture"] = abxjsonschema.SchemaFor[RuntimeSetMaxCallStackSizeToCaptureParams]() - c.commandResultSchemas["Runtime.setMaxCallStackSizeToCapture"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetMaxCallStackSizeToCaptureResult]()) - c.commandParamsSchemas["Runtime.terminateExecution"] = abxjsonschema.SchemaFor[RuntimeTerminateExecutionParams]() - c.commandResultSchemas["Runtime.terminateExecution"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeTerminateExecutionResult]()) - c.commandParamsSchemas["Runtime.addBinding"] = abxjsonschema.SchemaFor[RuntimeAddBindingParams]() - c.commandResultSchemas["Runtime.addBinding"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeAddBindingResult]()) - c.commandParamsSchemas["Runtime.removeBinding"] = abxjsonschema.SchemaFor[RuntimeRemoveBindingParams]() - c.commandResultSchemas["Runtime.removeBinding"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRemoveBindingResult]()) - c.commandParamsSchemas["Runtime.getExceptionDetails"] = abxjsonschema.SchemaFor[RuntimeGetExceptionDetailsParams]() - c.commandResultSchemas["Runtime.getExceptionDetails"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetExceptionDetailsResult]()) - c.commandParamsSchemas["Schema.getDomains"] = abxjsonschema.SchemaFor[SchemaGetDomainsParams]() - c.commandResultSchemas["Schema.getDomains"] = nativeResultSchema(abxjsonschema.SchemaFor[SchemaGetDomainsResult]()) - c.commandParamsSchemas["Security.disable"] = abxjsonschema.SchemaFor[SecurityDisableParams]() - c.commandResultSchemas["Security.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityDisableResult]()) - c.commandParamsSchemas["Security.enable"] = abxjsonschema.SchemaFor[SecurityEnableParams]() - c.commandResultSchemas["Security.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityEnableResult]()) - c.commandParamsSchemas["Security.setIgnoreCertificateErrors"] = abxjsonschema.SchemaFor[SecuritySetIgnoreCertificateErrorsParams]() - c.commandResultSchemas["Security.setIgnoreCertificateErrors"] = nativeResultSchema(abxjsonschema.SchemaFor[SecuritySetIgnoreCertificateErrorsResult]()) - c.commandParamsSchemas["Security.handleCertificateError"] = abxjsonschema.SchemaFor[SecurityHandleCertificateErrorParams]() - c.commandResultSchemas["Security.handleCertificateError"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityHandleCertificateErrorResult]()) - c.commandParamsSchemas["Security.setOverrideCertificateErrors"] = abxjsonschema.SchemaFor[SecuritySetOverrideCertificateErrorsParams]() - c.commandResultSchemas["Security.setOverrideCertificateErrors"] = nativeResultSchema(abxjsonschema.SchemaFor[SecuritySetOverrideCertificateErrorsResult]()) - c.commandParamsSchemas["ServiceWorker.deliverPushMessage"] = abxjsonschema.SchemaFor[ServiceWorkerDeliverPushMessageParams]() - c.commandResultSchemas["ServiceWorker.deliverPushMessage"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDeliverPushMessageResult]()) - c.commandParamsSchemas["ServiceWorker.disable"] = abxjsonschema.SchemaFor[ServiceWorkerDisableParams]() - c.commandResultSchemas["ServiceWorker.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDisableResult]()) - c.commandParamsSchemas["ServiceWorker.dispatchSyncEvent"] = abxjsonschema.SchemaFor[ServiceWorkerDispatchSyncEventParams]() - c.commandResultSchemas["ServiceWorker.dispatchSyncEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDispatchSyncEventResult]()) - c.commandParamsSchemas["ServiceWorker.dispatchPeriodicSyncEvent"] = abxjsonschema.SchemaFor[ServiceWorkerDispatchPeriodicSyncEventParams]() - c.commandResultSchemas["ServiceWorker.dispatchPeriodicSyncEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDispatchPeriodicSyncEventResult]()) - c.commandParamsSchemas["ServiceWorker.enable"] = abxjsonschema.SchemaFor[ServiceWorkerEnableParams]() - c.commandResultSchemas["ServiceWorker.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerEnableResult]()) - c.commandParamsSchemas["ServiceWorker.setForceUpdateOnPageLoad"] = abxjsonschema.SchemaFor[ServiceWorkerSetForceUpdateOnPageLoadParams]() - c.commandResultSchemas["ServiceWorker.setForceUpdateOnPageLoad"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerSetForceUpdateOnPageLoadResult]()) - c.commandParamsSchemas["ServiceWorker.skipWaiting"] = abxjsonschema.SchemaFor[ServiceWorkerSkipWaitingParams]() - c.commandResultSchemas["ServiceWorker.skipWaiting"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerSkipWaitingResult]()) - c.commandParamsSchemas["ServiceWorker.startWorker"] = abxjsonschema.SchemaFor[ServiceWorkerStartWorkerParams]() - c.commandResultSchemas["ServiceWorker.startWorker"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStartWorkerResult]()) - c.commandParamsSchemas["ServiceWorker.stopAllWorkers"] = abxjsonschema.SchemaFor[ServiceWorkerStopAllWorkersParams]() - c.commandResultSchemas["ServiceWorker.stopAllWorkers"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStopAllWorkersResult]()) - c.commandParamsSchemas["ServiceWorker.stopWorker"] = abxjsonschema.SchemaFor[ServiceWorkerStopWorkerParams]() - c.commandResultSchemas["ServiceWorker.stopWorker"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStopWorkerResult]()) - c.commandParamsSchemas["ServiceWorker.unregister"] = abxjsonschema.SchemaFor[ServiceWorkerUnregisterParams]() - c.commandResultSchemas["ServiceWorker.unregister"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerUnregisterResult]()) - c.commandParamsSchemas["ServiceWorker.updateRegistration"] = abxjsonschema.SchemaFor[ServiceWorkerUpdateRegistrationParams]() - c.commandResultSchemas["ServiceWorker.updateRegistration"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerUpdateRegistrationResult]()) - c.commandParamsSchemas["SmartCardEmulation.enable"] = abxjsonschema.SchemaFor[SmartCardEmulationEnableParams]() - c.commandResultSchemas["SmartCardEmulation.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEnableResult]()) - c.commandParamsSchemas["SmartCardEmulation.disable"] = abxjsonschema.SchemaFor[SmartCardEmulationDisableParams]() - c.commandResultSchemas["SmartCardEmulation.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationDisableResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportEstablishContextResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportEstablishContextResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportEstablishContextResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportEstablishContextResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportReleaseContextResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportReleaseContextResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportReleaseContextResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportReleaseContextResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportListReadersResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportListReadersResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportListReadersResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportListReadersResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportGetStatusChangeResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportGetStatusChangeResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportGetStatusChangeResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportGetStatusChangeResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportBeginTransactionResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportBeginTransactionResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportBeginTransactionResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportBeginTransactionResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportPlainResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportPlainResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportPlainResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportPlainResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportConnectResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportConnectResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportConnectResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportConnectResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportDataResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportDataResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportDataResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportDataResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportStatusResult"] = abxjsonschema.SchemaFor[SmartCardEmulationReportStatusResultParams]() - c.commandResultSchemas["SmartCardEmulation.reportStatusResult"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportStatusResultResult]()) - c.commandParamsSchemas["SmartCardEmulation.reportError"] = abxjsonschema.SchemaFor[SmartCardEmulationReportErrorParams]() - c.commandResultSchemas["SmartCardEmulation.reportError"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportErrorResult]()) - c.commandParamsSchemas["Storage.getStorageKeyForFrame"] = abxjsonschema.SchemaFor[StorageGetStorageKeyForFrameParams]() - c.commandResultSchemas["Storage.getStorageKeyForFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetStorageKeyForFrameResult]()) - c.commandParamsSchemas["Storage.getStorageKey"] = abxjsonschema.SchemaFor[StorageGetStorageKeyParams]() - c.commandResultSchemas["Storage.getStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetStorageKeyResult]()) - c.commandParamsSchemas["Storage.clearDataForOrigin"] = abxjsonschema.SchemaFor[StorageClearDataForOriginParams]() - c.commandResultSchemas["Storage.clearDataForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageClearDataForOriginResult]()) - c.commandParamsSchemas["Storage.clearDataForStorageKey"] = abxjsonschema.SchemaFor[StorageClearDataForStorageKeyParams]() - c.commandResultSchemas["Storage.clearDataForStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageClearDataForStorageKeyResult]()) - c.commandParamsSchemas["Storage.getCookies"] = abxjsonschema.SchemaFor[StorageGetCookiesParams]() - c.commandResultSchemas["Storage.getCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetCookiesResult]()) - c.commandParamsSchemas["Storage.setCookies"] = abxjsonschema.SchemaFor[StorageSetCookiesParams]() - c.commandResultSchemas["Storage.setCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetCookiesResult]()) - c.commandParamsSchemas["Storage.clearCookies"] = abxjsonschema.SchemaFor[StorageClearCookiesParams]() - c.commandResultSchemas["Storage.clearCookies"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageClearCookiesResult]()) - c.commandParamsSchemas["Storage.getUsageAndQuota"] = abxjsonschema.SchemaFor[StorageGetUsageAndQuotaParams]() - c.commandResultSchemas["Storage.getUsageAndQuota"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetUsageAndQuotaResult]()) - c.commandParamsSchemas["Storage.overrideQuotaForOrigin"] = abxjsonschema.SchemaFor[StorageOverrideQuotaForOriginParams]() - c.commandResultSchemas["Storage.overrideQuotaForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageOverrideQuotaForOriginResult]()) - c.commandParamsSchemas["Storage.trackCacheStorageForOrigin"] = abxjsonschema.SchemaFor[StorageTrackCacheStorageForOriginParams]() - c.commandResultSchemas["Storage.trackCacheStorageForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackCacheStorageForOriginResult]()) - c.commandParamsSchemas["Storage.trackCacheStorageForStorageKey"] = abxjsonschema.SchemaFor[StorageTrackCacheStorageForStorageKeyParams]() - c.commandResultSchemas["Storage.trackCacheStorageForStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackCacheStorageForStorageKeyResult]()) - c.commandParamsSchemas["Storage.trackIndexedDBForOrigin"] = abxjsonschema.SchemaFor[StorageTrackIndexedDBForOriginParams]() - c.commandResultSchemas["Storage.trackIndexedDBForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackIndexedDBForOriginResult]()) - c.commandParamsSchemas["Storage.trackIndexedDBForStorageKey"] = abxjsonschema.SchemaFor[StorageTrackIndexedDBForStorageKeyParams]() - c.commandResultSchemas["Storage.trackIndexedDBForStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackIndexedDBForStorageKeyResult]()) - c.commandParamsSchemas["Storage.untrackCacheStorageForOrigin"] = abxjsonschema.SchemaFor[StorageUntrackCacheStorageForOriginParams]() - c.commandResultSchemas["Storage.untrackCacheStorageForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackCacheStorageForOriginResult]()) - c.commandParamsSchemas["Storage.untrackCacheStorageForStorageKey"] = abxjsonschema.SchemaFor[StorageUntrackCacheStorageForStorageKeyParams]() - c.commandResultSchemas["Storage.untrackCacheStorageForStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackCacheStorageForStorageKeyResult]()) - c.commandParamsSchemas["Storage.untrackIndexedDBForOrigin"] = abxjsonschema.SchemaFor[StorageUntrackIndexedDBForOriginParams]() - c.commandResultSchemas["Storage.untrackIndexedDBForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackIndexedDBForOriginResult]()) - c.commandParamsSchemas["Storage.untrackIndexedDBForStorageKey"] = abxjsonschema.SchemaFor[StorageUntrackIndexedDBForStorageKeyParams]() - c.commandResultSchemas["Storage.untrackIndexedDBForStorageKey"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackIndexedDBForStorageKeyResult]()) - c.commandParamsSchemas["Storage.getTrustTokens"] = abxjsonschema.SchemaFor[StorageGetTrustTokensParams]() - c.commandResultSchemas["Storage.getTrustTokens"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetTrustTokensResult]()) - c.commandParamsSchemas["Storage.clearTrustTokens"] = abxjsonschema.SchemaFor[StorageClearTrustTokensParams]() - c.commandResultSchemas["Storage.clearTrustTokens"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageClearTrustTokensResult]()) - c.commandParamsSchemas["Storage.getInterestGroupDetails"] = abxjsonschema.SchemaFor[StorageGetInterestGroupDetailsParams]() - c.commandResultSchemas["Storage.getInterestGroupDetails"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetInterestGroupDetailsResult]()) - c.commandParamsSchemas["Storage.setInterestGroupTracking"] = abxjsonschema.SchemaFor[StorageSetInterestGroupTrackingParams]() - c.commandResultSchemas["Storage.setInterestGroupTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetInterestGroupTrackingResult]()) - c.commandParamsSchemas["Storage.setInterestGroupAuctionTracking"] = abxjsonschema.SchemaFor[StorageSetInterestGroupAuctionTrackingParams]() - c.commandResultSchemas["Storage.setInterestGroupAuctionTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetInterestGroupAuctionTrackingResult]()) - c.commandParamsSchemas["Storage.getSharedStorageMetadata"] = abxjsonschema.SchemaFor[StorageGetSharedStorageMetadataParams]() - c.commandResultSchemas["Storage.getSharedStorageMetadata"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetSharedStorageMetadataResult]()) - c.commandParamsSchemas["Storage.getSharedStorageEntries"] = abxjsonschema.SchemaFor[StorageGetSharedStorageEntriesParams]() - c.commandResultSchemas["Storage.getSharedStorageEntries"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetSharedStorageEntriesResult]()) - c.commandParamsSchemas["Storage.setSharedStorageEntry"] = abxjsonschema.SchemaFor[StorageSetSharedStorageEntryParams]() - c.commandResultSchemas["Storage.setSharedStorageEntry"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetSharedStorageEntryResult]()) - c.commandParamsSchemas["Storage.deleteSharedStorageEntry"] = abxjsonschema.SchemaFor[StorageDeleteSharedStorageEntryParams]() - c.commandResultSchemas["Storage.deleteSharedStorageEntry"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageDeleteSharedStorageEntryResult]()) - c.commandParamsSchemas["Storage.clearSharedStorageEntries"] = abxjsonschema.SchemaFor[StorageClearSharedStorageEntriesParams]() - c.commandResultSchemas["Storage.clearSharedStorageEntries"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageClearSharedStorageEntriesResult]()) - c.commandParamsSchemas["Storage.resetSharedStorageBudget"] = abxjsonschema.SchemaFor[StorageResetSharedStorageBudgetParams]() - c.commandResultSchemas["Storage.resetSharedStorageBudget"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageResetSharedStorageBudgetResult]()) - c.commandParamsSchemas["Storage.setSharedStorageTracking"] = abxjsonschema.SchemaFor[StorageSetSharedStorageTrackingParams]() - c.commandResultSchemas["Storage.setSharedStorageTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetSharedStorageTrackingResult]()) - c.commandParamsSchemas["Storage.setStorageBucketTracking"] = abxjsonschema.SchemaFor[StorageSetStorageBucketTrackingParams]() - c.commandResultSchemas["Storage.setStorageBucketTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetStorageBucketTrackingResult]()) - c.commandParamsSchemas["Storage.deleteStorageBucket"] = abxjsonschema.SchemaFor[StorageDeleteStorageBucketParams]() - c.commandResultSchemas["Storage.deleteStorageBucket"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageDeleteStorageBucketResult]()) - c.commandParamsSchemas["Storage.runBounceTrackingMitigations"] = abxjsonschema.SchemaFor[StorageRunBounceTrackingMitigationsParams]() - c.commandResultSchemas["Storage.runBounceTrackingMitigations"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageRunBounceTrackingMitigationsResult]()) - c.commandParamsSchemas["Storage.setAttributionReportingLocalTestingMode"] = abxjsonschema.SchemaFor[StorageSetAttributionReportingLocalTestingModeParams]() - c.commandResultSchemas["Storage.setAttributionReportingLocalTestingMode"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetAttributionReportingLocalTestingModeResult]()) - c.commandParamsSchemas["Storage.setAttributionReportingTracking"] = abxjsonschema.SchemaFor[StorageSetAttributionReportingTrackingParams]() - c.commandResultSchemas["Storage.setAttributionReportingTracking"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetAttributionReportingTrackingResult]()) - c.commandParamsSchemas["Storage.sendPendingAttributionReports"] = abxjsonschema.SchemaFor[StorageSendPendingAttributionReportsParams]() - c.commandResultSchemas["Storage.sendPendingAttributionReports"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSendPendingAttributionReportsResult]()) - c.commandParamsSchemas["Storage.getRelatedWebsiteSets"] = abxjsonschema.SchemaFor[StorageGetRelatedWebsiteSetsParams]() - c.commandResultSchemas["Storage.getRelatedWebsiteSets"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetRelatedWebsiteSetsResult]()) - c.commandParamsSchemas["Storage.getAffectedUrlsForThirdPartyCookieMetadata"] = abxjsonschema.SchemaFor[StorageGetAffectedUrlsForThirdPartyCookieMetadataParams]() - c.commandResultSchemas["Storage.getAffectedUrlsForThirdPartyCookieMetadata"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageGetAffectedUrlsForThirdPartyCookieMetadataResult]()) - c.commandParamsSchemas["Storage.setProtectedAudienceKAnonymity"] = abxjsonschema.SchemaFor[StorageSetProtectedAudienceKAnonymityParams]() - c.commandResultSchemas["Storage.setProtectedAudienceKAnonymity"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSetProtectedAudienceKAnonymityResult]()) - c.commandParamsSchemas["SystemInfo.getInfo"] = abxjsonschema.SchemaFor[SystemInfoGetInfoParams]() - c.commandResultSchemas["SystemInfo.getInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetInfoResult]()) - c.commandParamsSchemas["SystemInfo.getFeatureState"] = abxjsonschema.SchemaFor[SystemInfoGetFeatureStateParams]() - c.commandResultSchemas["SystemInfo.getFeatureState"] = nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetFeatureStateResult]()) - c.commandParamsSchemas["SystemInfo.getProcessInfo"] = abxjsonschema.SchemaFor[SystemInfoGetProcessInfoParams]() - c.commandResultSchemas["SystemInfo.getProcessInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetProcessInfoResult]()) - c.commandParamsSchemas["Target.activateTarget"] = abxjsonschema.SchemaFor[TargetActivateTargetParams]() - c.commandResultSchemas["Target.activateTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetActivateTargetResult]()) - c.commandParamsSchemas["Target.attachToTarget"] = abxjsonschema.SchemaFor[TargetAttachToTargetParams]() - c.commandResultSchemas["Target.attachToTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachToTargetResult]()) - c.commandParamsSchemas["Target.attachToBrowserTarget"] = abxjsonschema.SchemaFor[TargetAttachToBrowserTargetParams]() - c.commandResultSchemas["Target.attachToBrowserTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachToBrowserTargetResult]()) - c.commandParamsSchemas["Target.closeTarget"] = abxjsonschema.SchemaFor[TargetCloseTargetParams]() - c.commandResultSchemas["Target.closeTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetCloseTargetResult]()) - c.commandParamsSchemas["Target.exposeDevToolsProtocol"] = abxjsonschema.SchemaFor[TargetExposeDevToolsProtocolParams]() - c.commandResultSchemas["Target.exposeDevToolsProtocol"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetExposeDevToolsProtocolResult]()) - c.commandParamsSchemas["Target.createBrowserContext"] = abxjsonschema.SchemaFor[TargetCreateBrowserContextParams]() - c.commandResultSchemas["Target.createBrowserContext"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetCreateBrowserContextResult]()) - c.commandParamsSchemas["Target.getBrowserContexts"] = abxjsonschema.SchemaFor[TargetGetBrowserContextsParams]() - c.commandResultSchemas["Target.getBrowserContexts"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetGetBrowserContextsResult]()) - c.commandParamsSchemas["Target.createTarget"] = abxjsonschema.SchemaFor[TargetCreateTargetParams]() - c.commandResultSchemas["Target.createTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetCreateTargetResult]()) - c.commandParamsSchemas["Target.detachFromTarget"] = abxjsonschema.SchemaFor[TargetDetachFromTargetParams]() - c.commandResultSchemas["Target.detachFromTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetDetachFromTargetResult]()) - c.commandParamsSchemas["Target.disposeBrowserContext"] = abxjsonschema.SchemaFor[TargetDisposeBrowserContextParams]() - c.commandResultSchemas["Target.disposeBrowserContext"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetDisposeBrowserContextResult]()) - c.commandParamsSchemas["Target.getTargetInfo"] = abxjsonschema.SchemaFor[TargetGetTargetInfoParams]() - c.commandResultSchemas["Target.getTargetInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetGetTargetInfoResult]()) - c.commandParamsSchemas["Target.getTargets"] = abxjsonschema.SchemaFor[TargetGetTargetsParams]() - c.commandResultSchemas["Target.getTargets"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetGetTargetsResult]()) - c.commandParamsSchemas["Target.sendMessageToTarget"] = abxjsonschema.SchemaFor[TargetSendMessageToTargetParams]() - c.commandResultSchemas["Target.sendMessageToTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetSendMessageToTargetResult]()) - c.commandParamsSchemas["Target.setAutoAttach"] = abxjsonschema.SchemaFor[TargetSetAutoAttachParams]() - c.commandResultSchemas["Target.setAutoAttach"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetSetAutoAttachResult]()) - c.commandParamsSchemas["Target.autoAttachRelated"] = abxjsonschema.SchemaFor[TargetAutoAttachRelatedParams]() - c.commandResultSchemas["Target.autoAttachRelated"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetAutoAttachRelatedResult]()) - c.commandParamsSchemas["Target.setDiscoverTargets"] = abxjsonschema.SchemaFor[TargetSetDiscoverTargetsParams]() - c.commandResultSchemas["Target.setDiscoverTargets"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetSetDiscoverTargetsResult]()) - c.commandParamsSchemas["Target.setRemoteLocations"] = abxjsonschema.SchemaFor[TargetSetRemoteLocationsParams]() - c.commandResultSchemas["Target.setRemoteLocations"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetSetRemoteLocationsResult]()) - c.commandParamsSchemas["Target.getDevToolsTarget"] = abxjsonschema.SchemaFor[TargetGetDevToolsTargetParams]() - c.commandResultSchemas["Target.getDevToolsTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetGetDevToolsTargetResult]()) - c.commandParamsSchemas["Target.openDevTools"] = abxjsonschema.SchemaFor[TargetOpenDevToolsParams]() - c.commandResultSchemas["Target.openDevTools"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetOpenDevToolsResult]()) - c.commandParamsSchemas["Tethering.bind"] = abxjsonschema.SchemaFor[TetheringBindParams]() - c.commandResultSchemas["Tethering.bind"] = nativeResultSchema(abxjsonschema.SchemaFor[TetheringBindResult]()) - c.commandParamsSchemas["Tethering.unbind"] = abxjsonschema.SchemaFor[TetheringUnbindParams]() - c.commandResultSchemas["Tethering.unbind"] = nativeResultSchema(abxjsonschema.SchemaFor[TetheringUnbindResult]()) - c.commandParamsSchemas["Tracing.end"] = abxjsonschema.SchemaFor[TracingEndParams]() - c.commandResultSchemas["Tracing.end"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingEndResult]()) - c.commandParamsSchemas["Tracing.getCategories"] = abxjsonschema.SchemaFor[TracingGetCategoriesParams]() - c.commandResultSchemas["Tracing.getCategories"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingGetCategoriesResult]()) - c.commandParamsSchemas["Tracing.getTrackEventDescriptor"] = abxjsonschema.SchemaFor[TracingGetTrackEventDescriptorParams]() - c.commandResultSchemas["Tracing.getTrackEventDescriptor"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingGetTrackEventDescriptorResult]()) - c.commandParamsSchemas["Tracing.recordClockSyncMarker"] = abxjsonschema.SchemaFor[TracingRecordClockSyncMarkerParams]() - c.commandResultSchemas["Tracing.recordClockSyncMarker"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingRecordClockSyncMarkerResult]()) - c.commandParamsSchemas["Tracing.requestMemoryDump"] = abxjsonschema.SchemaFor[TracingRequestMemoryDumpParams]() - c.commandResultSchemas["Tracing.requestMemoryDump"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingRequestMemoryDumpResult]()) - c.commandParamsSchemas["Tracing.start"] = abxjsonschema.SchemaFor[TracingStartParams]() - c.commandResultSchemas["Tracing.start"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingStartResult]()) - c.commandParamsSchemas["WebAudio.enable"] = abxjsonschema.SchemaFor[WebAudioEnableParams]() - c.commandResultSchemas["WebAudio.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioEnableResult]()) - c.commandParamsSchemas["WebAudio.disable"] = abxjsonschema.SchemaFor[WebAudioDisableParams]() - c.commandResultSchemas["WebAudio.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioDisableResult]()) - c.commandParamsSchemas["WebAudio.getRealtimeData"] = abxjsonschema.SchemaFor[WebAudioGetRealtimeDataParams]() - c.commandResultSchemas["WebAudio.getRealtimeData"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioGetRealtimeDataResult]()) - c.commandParamsSchemas["WebAuthn.enable"] = abxjsonschema.SchemaFor[WebAuthnEnableParams]() - c.commandResultSchemas["WebAuthn.enable"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnEnableResult]()) - c.commandParamsSchemas["WebAuthn.disable"] = abxjsonschema.SchemaFor[WebAuthnDisableParams]() - c.commandResultSchemas["WebAuthn.disable"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnDisableResult]()) - c.commandParamsSchemas["WebAuthn.addVirtualAuthenticator"] = abxjsonschema.SchemaFor[WebAuthnAddVirtualAuthenticatorParams]() - c.commandResultSchemas["WebAuthn.addVirtualAuthenticator"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnAddVirtualAuthenticatorResult]()) - c.commandParamsSchemas["WebAuthn.setResponseOverrideBits"] = abxjsonschema.SchemaFor[WebAuthnSetResponseOverrideBitsParams]() - c.commandResultSchemas["WebAuthn.setResponseOverrideBits"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetResponseOverrideBitsResult]()) - c.commandParamsSchemas["WebAuthn.removeVirtualAuthenticator"] = abxjsonschema.SchemaFor[WebAuthnRemoveVirtualAuthenticatorParams]() - c.commandResultSchemas["WebAuthn.removeVirtualAuthenticator"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnRemoveVirtualAuthenticatorResult]()) - c.commandParamsSchemas["WebAuthn.addCredential"] = abxjsonschema.SchemaFor[WebAuthnAddCredentialParams]() - c.commandResultSchemas["WebAuthn.addCredential"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnAddCredentialResult]()) - c.commandParamsSchemas["WebAuthn.getCredential"] = abxjsonschema.SchemaFor[WebAuthnGetCredentialParams]() - c.commandResultSchemas["WebAuthn.getCredential"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnGetCredentialResult]()) - c.commandParamsSchemas["WebAuthn.getCredentials"] = abxjsonschema.SchemaFor[WebAuthnGetCredentialsParams]() - c.commandResultSchemas["WebAuthn.getCredentials"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnGetCredentialsResult]()) - c.commandParamsSchemas["WebAuthn.removeCredential"] = abxjsonschema.SchemaFor[WebAuthnRemoveCredentialParams]() - c.commandResultSchemas["WebAuthn.removeCredential"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnRemoveCredentialResult]()) - c.commandParamsSchemas["WebAuthn.clearCredentials"] = abxjsonschema.SchemaFor[WebAuthnClearCredentialsParams]() - c.commandResultSchemas["WebAuthn.clearCredentials"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnClearCredentialsResult]()) - c.commandParamsSchemas["WebAuthn.setUserVerified"] = abxjsonschema.SchemaFor[WebAuthnSetUserVerifiedParams]() - c.commandResultSchemas["WebAuthn.setUserVerified"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetUserVerifiedResult]()) - c.commandParamsSchemas["WebAuthn.setAutomaticPresenceSimulation"] = abxjsonschema.SchemaFor[WebAuthnSetAutomaticPresenceSimulationParams]() - c.commandResultSchemas["WebAuthn.setAutomaticPresenceSimulation"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetAutomaticPresenceSimulationResult]()) - c.commandParamsSchemas["WebAuthn.setCredentialProperties"] = abxjsonschema.SchemaFor[WebAuthnSetCredentialPropertiesParams]() - c.commandResultSchemas["WebAuthn.setCredentialProperties"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetCredentialPropertiesResult]()) - c.eventSchemas["Accessibility.loadComplete"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityLoadCompleteEvent]()) - c.eventSchemas["Accessibility.nodesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityNodesUpdatedEvent]()) - c.eventSchemas["Animation.animationCanceled"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationCanceledEvent]()) - c.eventSchemas["Animation.animationCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationCreatedEvent]()) - c.eventSchemas["Animation.animationStarted"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationStartedEvent]()) - c.eventSchemas["Animation.animationUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationUpdatedEvent]()) - c.eventSchemas["Audits.issueAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsIssueAddedEvent]()) - c.eventSchemas["Autofill.addressFormFilled"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillAddressFormFilledEvent]()) - c.eventSchemas["BackgroundService.recordingStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceRecordingStateChangedEvent]()) - c.eventSchemas["BackgroundService.backgroundServiceEventReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceBackgroundServiceEventReceivedEvent]()) - c.eventSchemas["BluetoothEmulation.gattOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationGattOperationReceivedEvent]()) - c.eventSchemas["BluetoothEmulation.characteristicOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationCharacteristicOperationReceivedEvent]()) - c.eventSchemas["BluetoothEmulation.descriptorOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationDescriptorOperationReceivedEvent]()) - c.eventSchemas["Browser.downloadWillBegin"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserDownloadWillBeginEvent]()) - c.eventSchemas["Browser.downloadProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserDownloadProgressEvent]()) - c.eventSchemas["CSS.fontsUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSFontsUpdatedEvent]()) - c.eventSchemas["CSS.mediaQueryResultChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSMediaQueryResultChangedEvent]()) - c.eventSchemas["CSS.styleSheetAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetAddedEvent]()) - c.eventSchemas["CSS.styleSheetChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetChangedEvent]()) - c.eventSchemas["CSS.styleSheetRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetRemovedEvent]()) - c.eventSchemas["CSS.computedStyleUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSComputedStyleUpdatedEvent]()) - c.eventSchemas["Cast.sinksUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CastSinksUpdatedEvent]()) - c.eventSchemas["Cast.issueUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CastIssueUpdatedEvent]()) - c.eventSchemas["Console.messageAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[ConsoleMessageAddedEvent]()) - c.eventSchemas["DOM.attributeModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAttributeModifiedEvent]()) - c.eventSchemas["DOM.adoptedStyleSheetsModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAdoptedStyleSheetsModifiedEvent]()) - c.eventSchemas["DOM.attributeRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAttributeRemovedEvent]()) - c.eventSchemas["DOM.characterDataModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMCharacterDataModifiedEvent]()) - c.eventSchemas["DOM.childNodeCountUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeCountUpdatedEvent]()) - c.eventSchemas["DOM.childNodeInserted"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeInsertedEvent]()) - c.eventSchemas["DOM.childNodeRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeRemovedEvent]()) - c.eventSchemas["DOM.distributedNodesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDistributedNodesUpdatedEvent]()) - c.eventSchemas["DOM.documentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDocumentUpdatedEvent]()) - c.eventSchemas["DOM.inlineStyleInvalidated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMInlineStyleInvalidatedEvent]()) - c.eventSchemas["DOM.pseudoElementAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPseudoElementAddedEvent]()) - c.eventSchemas["DOM.topLayerElementsUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMTopLayerElementsUpdatedEvent]()) - c.eventSchemas["DOM.scrollableFlagUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMScrollableFlagUpdatedEvent]()) - c.eventSchemas["DOM.adRelatedStateUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAdRelatedStateUpdatedEvent]()) - c.eventSchemas["DOM.affectedByStartingStylesFlagUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAffectedByStartingStylesFlagUpdatedEvent]()) - c.eventSchemas["DOM.pseudoElementRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPseudoElementRemovedEvent]()) - c.eventSchemas["DOM.setChildNodes"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetChildNodesEvent]()) - c.eventSchemas["DOM.shadowRootPopped"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMShadowRootPoppedEvent]()) - c.eventSchemas["DOM.shadowRootPushed"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMShadowRootPushedEvent]()) - c.eventSchemas["DOMStorage.domStorageItemAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemAddedEvent]()) - c.eventSchemas["DOMStorage.domStorageItemRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemRemovedEvent]()) - c.eventSchemas["DOMStorage.domStorageItemUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemUpdatedEvent]()) - c.eventSchemas["DOMStorage.domStorageItemsCleared"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemsClearedEvent]()) - c.eventSchemas["Debugger.breakpointResolved"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerBreakpointResolvedEvent]()) - c.eventSchemas["Debugger.paused"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPausedEvent]()) - c.eventSchemas["Debugger.resumed"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerResumedEvent]()) - c.eventSchemas["Debugger.scriptFailedToParse"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerScriptFailedToParseEvent]()) - c.eventSchemas["Debugger.scriptParsed"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerScriptParsedEvent]()) - c.eventSchemas["DeviceAccess.deviceRequestPrompted"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessDeviceRequestPromptedEvent]()) - c.eventSchemas["Emulation.virtualTimeBudgetExpired"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationVirtualTimeBudgetExpiredEvent]()) - c.eventSchemas["Emulation.screenOrientationLockChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationScreenOrientationLockChangedEvent]()) - c.eventSchemas["FedCm.dialogShown"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDialogShownEvent]()) - c.eventSchemas["FedCm.dialogClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDialogClosedEvent]()) - c.eventSchemas["Fetch.requestPaused"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchRequestPausedEvent]()) - c.eventSchemas["Fetch.authRequired"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchAuthRequiredEvent]()) - c.eventSchemas["HeapProfiler.addHeapSnapshotChunk"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerAddHeapSnapshotChunkEvent]()) - c.eventSchemas["HeapProfiler.heapStatsUpdate"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerHeapStatsUpdateEvent]()) - c.eventSchemas["HeapProfiler.lastSeenObjectId"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerLastSeenObjectIDEvent]()) - c.eventSchemas["HeapProfiler.reportHeapSnapshotProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerReportHeapSnapshotProgressEvent]()) - c.eventSchemas["HeapProfiler.resetProfiles"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerResetProfilesEvent]()) - c.eventSchemas["Input.dragIntercepted"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDragInterceptedEvent]()) - c.eventSchemas["Inspector.detached"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorDetachedEvent]()) - c.eventSchemas["Inspector.targetCrashed"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorTargetCrashedEvent]()) - c.eventSchemas["Inspector.targetReloadedAfterCrash"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorTargetReloadedAfterCrashEvent]()) - c.eventSchemas["Inspector.workerScriptLoaded"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorWorkerScriptLoadedEvent]()) - c.eventSchemas["LayerTree.layerPainted"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLayerPaintedEvent]()) - c.eventSchemas["LayerTree.layerTreeDidChange"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLayerTreeDidChangeEvent]()) - c.eventSchemas["Log.entryAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[LogEntryAddedEvent]()) - c.eventSchemas["Media.playerPropertiesChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerPropertiesChangedEvent]()) - c.eventSchemas["Media.playerEventsAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerEventsAddedEvent]()) - c.eventSchemas["Media.playerMessagesLogged"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerMessagesLoggedEvent]()) - c.eventSchemas["Media.playerErrorsRaised"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerErrorsRaisedEvent]()) - c.eventSchemas["Media.playerCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerCreatedEvent]()) - c.eventSchemas["Network.dataReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDataReceivedEvent]()) - c.eventSchemas["Network.eventSourceMessageReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEventSourceMessageReceivedEvent]()) - c.eventSchemas["Network.loadingFailed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadingFailedEvent]()) - c.eventSchemas["Network.loadingFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadingFinishedEvent]()) - c.eventSchemas["Network.requestIntercepted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestInterceptedEvent]()) - c.eventSchemas["Network.requestServedFromCache"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestServedFromCacheEvent]()) - c.eventSchemas["Network.requestWillBeSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestWillBeSentEvent]()) - c.eventSchemas["Network.resourceChangedPriority"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResourceChangedPriorityEvent]()) - c.eventSchemas["Network.signedExchangeReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSignedExchangeReceivedEvent]()) - c.eventSchemas["Network.responseReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedEvent]()) - c.eventSchemas["Network.webSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketClosedEvent]()) - c.eventSchemas["Network.webSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketCreatedEvent]()) - c.eventSchemas["Network.webSocketFrameError"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameErrorEvent]()) - c.eventSchemas["Network.webSocketFrameReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameReceivedEvent]()) - c.eventSchemas["Network.webSocketFrameSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameSentEvent]()) - c.eventSchemas["Network.webSocketHandshakeResponseReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketHandshakeResponseReceivedEvent]()) - c.eventSchemas["Network.webSocketWillSendHandshakeRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketWillSendHandshakeRequestEvent]()) - c.eventSchemas["Network.webTransportCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportCreatedEvent]()) - c.eventSchemas["Network.webTransportConnectionEstablished"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportConnectionEstablishedEvent]()) - c.eventSchemas["Network.webTransportClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportClosedEvent]()) - c.eventSchemas["Network.directTCPSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketCreatedEvent]()) - c.eventSchemas["Network.directTCPSocketOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketOpenedEvent]()) - c.eventSchemas["Network.directTCPSocketAborted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketAbortedEvent]()) - c.eventSchemas["Network.directTCPSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketClosedEvent]()) - c.eventSchemas["Network.directTCPSocketChunkSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketChunkSentEvent]()) - c.eventSchemas["Network.directTCPSocketChunkReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketChunkReceivedEvent]()) - c.eventSchemas["Network.directUDPSocketJoinedMulticastGroup"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketJoinedMulticastGroupEvent]()) - c.eventSchemas["Network.directUDPSocketLeftMulticastGroup"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketLeftMulticastGroupEvent]()) - c.eventSchemas["Network.directUDPSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketCreatedEvent]()) - c.eventSchemas["Network.directUDPSocketOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketOpenedEvent]()) - c.eventSchemas["Network.directUDPSocketAborted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketAbortedEvent]()) - c.eventSchemas["Network.directUDPSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketClosedEvent]()) - c.eventSchemas["Network.directUDPSocketChunkSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketChunkSentEvent]()) - c.eventSchemas["Network.directUDPSocketChunkReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketChunkReceivedEvent]()) - c.eventSchemas["Network.requestWillBeSentExtraInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestWillBeSentExtraInfoEvent]()) - c.eventSchemas["Network.responseReceivedExtraInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedExtraInfoEvent]()) - c.eventSchemas["Network.responseReceivedEarlyHints"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedEarlyHintsEvent]()) - c.eventSchemas["Network.trustTokenOperationDone"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkTrustTokenOperationDoneEvent]()) - c.eventSchemas["Network.policyUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkPolicyUpdatedEvent]()) - c.eventSchemas["Network.reportingApiReportAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIReportAddedEvent]()) - c.eventSchemas["Network.reportingApiReportUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIReportUpdatedEvent]()) - c.eventSchemas["Network.reportingApiEndpointsChangedForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIEndpointsChangedForOriginEvent]()) - c.eventSchemas["Network.deviceBoundSessionsAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeviceBoundSessionsAddedEvent]()) - c.eventSchemas["Network.deviceBoundSessionEventOccurred"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeviceBoundSessionEventOccurredEvent]()) - c.eventSchemas["Overlay.inspectNodeRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectNodeRequestedEvent]()) - c.eventSchemas["Overlay.nodeHighlightRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayNodeHighlightRequestedEvent]()) - c.eventSchemas["Overlay.screenshotRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayScreenshotRequestedEvent]()) - c.eventSchemas["Overlay.inspectPanelShowRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectPanelShowRequestedEvent]()) - c.eventSchemas["Overlay.inspectedElementWindowRestored"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectedElementWindowRestoredEvent]()) - c.eventSchemas["Overlay.inspectModeCanceled"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectModeCanceledEvent]()) - c.eventSchemas["Page.domContentEventFired"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDOMContentEventFiredEvent]()) - c.eventSchemas["Page.fileChooserOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFileChooserOpenedEvent]()) - c.eventSchemas["Page.frameAttached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameAttachedEvent]()) - c.eventSchemas["Page.frameClearedScheduledNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameClearedScheduledNavigationEvent]()) - c.eventSchemas["Page.frameDetached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameDetachedEvent]()) - c.eventSchemas["Page.frameSubtreeWillBeDetached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameSubtreeWillBeDetachedEvent]()) - c.eventSchemas["Page.frameNavigated"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameNavigatedEvent]()) - c.eventSchemas["Page.documentOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDocumentOpenedEvent]()) - c.eventSchemas["Page.frameResized"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameResizedEvent]()) - c.eventSchemas["Page.frameStartedNavigating"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStartedNavigatingEvent]()) - c.eventSchemas["Page.frameRequestedNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameRequestedNavigationEvent]()) - c.eventSchemas["Page.frameScheduledNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameScheduledNavigationEvent]()) - c.eventSchemas["Page.frameStartedLoading"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStartedLoadingEvent]()) - c.eventSchemas["Page.frameStoppedLoading"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStoppedLoadingEvent]()) - c.eventSchemas["Page.downloadWillBegin"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDownloadWillBeginEvent]()) - c.eventSchemas["Page.downloadProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDownloadProgressEvent]()) - c.eventSchemas["Page.interstitialHidden"] = nativeResultSchema(abxjsonschema.SchemaFor[PageInterstitialHiddenEvent]()) - c.eventSchemas["Page.interstitialShown"] = nativeResultSchema(abxjsonschema.SchemaFor[PageInterstitialShownEvent]()) - c.eventSchemas["Page.javascriptDialogClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[PageJavascriptDialogClosedEvent]()) - c.eventSchemas["Page.javascriptDialogOpening"] = nativeResultSchema(abxjsonschema.SchemaFor[PageJavascriptDialogOpeningEvent]()) - c.eventSchemas["Page.lifecycleEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[PageLifecycleEventEvent]()) - c.eventSchemas["Page.backForwardCacheNotUsed"] = nativeResultSchema(abxjsonschema.SchemaFor[PageBackForwardCacheNotUsedEvent]()) - c.eventSchemas["Page.loadEventFired"] = nativeResultSchema(abxjsonschema.SchemaFor[PageLoadEventFiredEvent]()) - c.eventSchemas["Page.navigatedWithinDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[PageNavigatedWithinDocumentEvent]()) - c.eventSchemas["Page.screencastFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastFrameEvent]()) - c.eventSchemas["Page.screencastVisibilityChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastVisibilityChangedEvent]()) - c.eventSchemas["Page.windowOpen"] = nativeResultSchema(abxjsonschema.SchemaFor[PageWindowOpenEvent]()) - c.eventSchemas["Page.compilationCacheProduced"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCompilationCacheProducedEvent]()) - c.eventSchemas["Performance.metrics"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceMetricsEvent]()) - c.eventSchemas["PerformanceTimeline.timelineEventAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceTimelineTimelineEventAddedEvent]()) - c.eventSchemas["Preload.ruleSetUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadRuleSetUpdatedEvent]()) - c.eventSchemas["Preload.ruleSetRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadRuleSetRemovedEvent]()) - c.eventSchemas["Preload.preloadEnabledStateUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPreloadEnabledStateUpdatedEvent]()) - c.eventSchemas["Preload.prefetchStatusUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPrefetchStatusUpdatedEvent]()) - c.eventSchemas["Preload.prerenderStatusUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPrerenderStatusUpdatedEvent]()) - c.eventSchemas["Preload.preloadingAttemptSourcesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPreloadingAttemptSourcesUpdatedEvent]()) - c.eventSchemas["Profiler.consoleProfileFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerConsoleProfileFinishedEvent]()) - c.eventSchemas["Profiler.consoleProfileStarted"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerConsoleProfileStartedEvent]()) - c.eventSchemas["Profiler.preciseCoverageDeltaUpdate"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerPreciseCoverageDeltaUpdateEvent]()) - c.eventSchemas["Runtime.bindingCalled"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeBindingCalledEvent]()) - c.eventSchemas["Runtime.consoleAPICalled"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeConsoleAPICalledEvent]()) - c.eventSchemas["Runtime.exceptionRevoked"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExceptionRevokedEvent]()) - c.eventSchemas["Runtime.exceptionThrown"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExceptionThrownEvent]()) - c.eventSchemas["Runtime.executionContextCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextCreatedEvent]()) - c.eventSchemas["Runtime.executionContextDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextDestroyedEvent]()) - c.eventSchemas["Runtime.executionContextsCleared"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextsClearedEvent]()) - c.eventSchemas["Runtime.inspectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeInspectRequestedEvent]()) - c.eventSchemas["Security.certificateError"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityCertificateErrorEvent]()) - c.eventSchemas["Security.visibleSecurityStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityVisibleSecurityStateChangedEvent]()) - c.eventSchemas["Security.securityStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[SecuritySecurityStateChangedEvent]()) - c.eventSchemas["ServiceWorker.workerErrorReported"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerErrorReportedEvent]()) - c.eventSchemas["ServiceWorker.workerRegistrationUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerRegistrationUpdatedEvent]()) - c.eventSchemas["ServiceWorker.workerVersionUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerVersionUpdatedEvent]()) - c.eventSchemas["SmartCardEmulation.establishContextRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEstablishContextRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.releaseContextRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReleaseContextRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.listReadersRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationListReadersRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.getStatusChangeRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationGetStatusChangeRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.cancelRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationCancelRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.connectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationConnectRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.disconnectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationDisconnectRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.transmitRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationTransmitRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.controlRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationControlRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.getAttribRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationGetAttribRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.setAttribRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationSetAttribRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.statusRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationStatusRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.beginTransactionRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationBeginTransactionRequestedEvent]()) - c.eventSchemas["SmartCardEmulation.endTransactionRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEndTransactionRequestedEvent]()) - c.eventSchemas["Storage.cacheStorageContentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageCacheStorageContentUpdatedEvent]()) - c.eventSchemas["Storage.cacheStorageListUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageCacheStorageListUpdatedEvent]()) - c.eventSchemas["Storage.indexedDBContentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageIndexedDBContentUpdatedEvent]()) - c.eventSchemas["Storage.indexedDBListUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageIndexedDBListUpdatedEvent]()) - c.eventSchemas["Storage.interestGroupAccessed"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAccessedEvent]()) - c.eventSchemas["Storage.interestGroupAuctionEventOccurred"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAuctionEventOccurredEvent]()) - c.eventSchemas["Storage.interestGroupAuctionNetworkRequestCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAuctionNetworkRequestCreatedEvent]()) - c.eventSchemas["Storage.sharedStorageAccessed"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSharedStorageAccessedEvent]()) - c.eventSchemas["Storage.sharedStorageWorkletOperationExecutionFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSharedStorageWorkletOperationExecutionFinishedEvent]()) - c.eventSchemas["Storage.storageBucketCreatedOrUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageStorageBucketCreatedOrUpdatedEvent]()) - c.eventSchemas["Storage.storageBucketDeleted"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageStorageBucketDeletedEvent]()) - c.eventSchemas["Storage.attributionReportingSourceRegistered"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingSourceRegisteredEvent]()) - c.eventSchemas["Storage.attributionReportingTriggerRegistered"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingTriggerRegisteredEvent]()) - c.eventSchemas["Storage.attributionReportingReportSent"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingReportSentEvent]()) - c.eventSchemas["Storage.attributionReportingVerboseDebugReportSent"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingVerboseDebugReportSentEvent]()) - c.eventSchemas["Target.attachedToTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachedToTargetEvent]()) - c.eventSchemas["Target.detachedFromTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetDetachedFromTargetEvent]()) - c.eventSchemas["Target.receivedMessageFromTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetReceivedMessageFromTargetEvent]()) - c.eventSchemas["Target.targetCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetCreatedEvent]()) - c.eventSchemas["Target.targetDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetDestroyedEvent]()) - c.eventSchemas["Target.targetCrashed"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetCrashedEvent]()) - c.eventSchemas["Target.targetInfoChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetInfoChangedEvent]()) - c.eventSchemas["Tethering.accepted"] = nativeResultSchema(abxjsonschema.SchemaFor[TetheringAcceptedEvent]()) - c.eventSchemas["Tracing.bufferUsage"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingBufferUsageEvent]()) - c.eventSchemas["Tracing.dataCollected"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingDataCollectedEvent]()) - c.eventSchemas["Tracing.tracingComplete"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingTracingCompleteEvent]()) - c.eventSchemas["WebAudio.contextCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextCreatedEvent]()) - c.eventSchemas["WebAudio.contextWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextWillBeDestroyedEvent]()) - c.eventSchemas["WebAudio.contextChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextChangedEvent]()) - c.eventSchemas["WebAudio.audioListenerCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioListenerCreatedEvent]()) - c.eventSchemas["WebAudio.audioListenerWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioListenerWillBeDestroyedEvent]()) - c.eventSchemas["WebAudio.audioNodeCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioNodeCreatedEvent]()) - c.eventSchemas["WebAudio.audioNodeWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioNodeWillBeDestroyedEvent]()) - c.eventSchemas["WebAudio.audioParamCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioParamCreatedEvent]()) - c.eventSchemas["WebAudio.audioParamWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioParamWillBeDestroyedEvent]()) - c.eventSchemas["WebAudio.nodesConnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodesConnectedEvent]()) - c.eventSchemas["WebAudio.nodesDisconnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodesDisconnectedEvent]()) - c.eventSchemas["WebAudio.nodeParamConnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodeParamConnectedEvent]()) - c.eventSchemas["WebAudio.nodeParamDisconnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodeParamDisconnectedEvent]()) - c.eventSchemas["WebAuthn.credentialAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialAddedEvent]()) - c.eventSchemas["WebAuthn.credentialDeleted"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialDeletedEvent]()) - c.eventSchemas["WebAuthn.credentialUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialUpdatedEvent]()) - c.eventSchemas["WebAuthn.credentialAsserted"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialAssertedEvent]()) +func (types *CDPTypes) hydrateNativeProtocolSchemas() { + types.mu.Lock() + defer types.mu.Unlock() + types.commandSchemas["Accessibility.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityDisableResult]()), + } + types.commandSchemas["Accessibility.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityEnableResult]()), + } + types.commandSchemas["Accessibility.getPartialAXTree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityGetPartialAXTreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetPartialAXTreeResult]()), + } + types.commandSchemas["Accessibility.getFullAXTree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityGetFullAXTreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetFullAXTreeResult]()), + } + types.commandSchemas["Accessibility.getRootAXNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityGetRootAXNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetRootAXNodeResult]()), + } + types.commandSchemas["Accessibility.getAXNodeAndAncestors"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityGetAXNodeAndAncestorsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetAXNodeAndAncestorsResult]()), + } + types.commandSchemas["Accessibility.getChildAXNodes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityGetChildAXNodesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityGetChildAXNodesResult]()), + } + types.commandSchemas["Accessibility.queryAXTree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AccessibilityQueryAXTreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityQueryAXTreeResult]()), + } + types.commandSchemas["Animation.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationDisableResult]()), + } + types.commandSchemas["Animation.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationEnableResult]()), + } + types.commandSchemas["Animation.getCurrentTime"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationGetCurrentTimeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationGetCurrentTimeResult]()), + } + types.commandSchemas["Animation.getPlaybackRate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationGetPlaybackRateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationGetPlaybackRateResult]()), + } + types.commandSchemas["Animation.releaseAnimations"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationReleaseAnimationsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationReleaseAnimationsResult]()), + } + types.commandSchemas["Animation.resolveAnimation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationResolveAnimationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationResolveAnimationResult]()), + } + types.commandSchemas["Animation.seekAnimations"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationSeekAnimationsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationSeekAnimationsResult]()), + } + types.commandSchemas["Animation.setPaused"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationSetPausedParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetPausedResult]()), + } + types.commandSchemas["Animation.setPlaybackRate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationSetPlaybackRateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetPlaybackRateResult]()), + } + types.commandSchemas["Animation.setTiming"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AnimationSetTimingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AnimationSetTimingResult]()), + } + types.commandSchemas["Audits.getEncodedResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AuditsGetEncodedResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AuditsGetEncodedResponseResult]()), + } + types.commandSchemas["Audits.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AuditsDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AuditsDisableResult]()), + } + types.commandSchemas["Audits.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AuditsEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AuditsEnableResult]()), + } + types.commandSchemas["Audits.checkFormsIssues"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AuditsCheckFormsIssuesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AuditsCheckFormsIssuesResult]()), + } + types.commandSchemas["Autofill.trigger"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AutofillTriggerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AutofillTriggerResult]()), + } + types.commandSchemas["Autofill.setAddresses"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AutofillSetAddressesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AutofillSetAddressesResult]()), + } + types.commandSchemas["Autofill.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AutofillDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AutofillDisableResult]()), + } + types.commandSchemas["Autofill.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[AutofillEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[AutofillEnableResult]()), + } + types.commandSchemas["BackgroundService.startObserving"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BackgroundServiceStartObservingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceStartObservingResult]()), + } + types.commandSchemas["BackgroundService.stopObserving"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BackgroundServiceStopObservingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceStopObservingResult]()), + } + types.commandSchemas["BackgroundService.setRecording"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BackgroundServiceSetRecordingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceSetRecordingResult]()), + } + types.commandSchemas["BackgroundService.clearEvents"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BackgroundServiceClearEventsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceClearEventsResult]()), + } + types.commandSchemas["BluetoothEmulation.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationEnableResult]()), + } + types.commandSchemas["BluetoothEmulation.setSimulatedCentralState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSetSimulatedCentralStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSetSimulatedCentralStateResult]()), + } + types.commandSchemas["BluetoothEmulation.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationDisableResult]()), + } + types.commandSchemas["BluetoothEmulation.simulatePreconnectedPeripheral"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulatePreconnectedPeripheralParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulatePreconnectedPeripheralResult]()), + } + types.commandSchemas["BluetoothEmulation.simulateAdvertisement"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulateAdvertisementParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateAdvertisementResult]()), + } + types.commandSchemas["BluetoothEmulation.simulateGATTOperationResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTOperationResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTOperationResponseResult]()), + } + types.commandSchemas["BluetoothEmulation.simulateCharacteristicOperationResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulateCharacteristicOperationResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateCharacteristicOperationResponseResult]()), + } + types.commandSchemas["BluetoothEmulation.simulateDescriptorOperationResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulateDescriptorOperationResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateDescriptorOperationResponseResult]()), + } + types.commandSchemas["BluetoothEmulation.addService"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationAddServiceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddServiceResult]()), + } + types.commandSchemas["BluetoothEmulation.removeService"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationRemoveServiceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveServiceResult]()), + } + types.commandSchemas["BluetoothEmulation.addCharacteristic"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationAddCharacteristicParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddCharacteristicResult]()), + } + types.commandSchemas["BluetoothEmulation.removeCharacteristic"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationRemoveCharacteristicParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveCharacteristicResult]()), + } + types.commandSchemas["BluetoothEmulation.addDescriptor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationAddDescriptorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationAddDescriptorResult]()), + } + types.commandSchemas["BluetoothEmulation.removeDescriptor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationRemoveDescriptorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationRemoveDescriptorResult]()), + } + types.commandSchemas["BluetoothEmulation.simulateGATTDisconnection"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTDisconnectionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationSimulateGATTDisconnectionResult]()), + } + types.commandSchemas["Browser.setPermission"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserSetPermissionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetPermissionResult]()), + } + types.commandSchemas["Browser.grantPermissions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGrantPermissionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGrantPermissionsResult]()), + } + types.commandSchemas["Browser.resetPermissions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserResetPermissionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserResetPermissionsResult]()), + } + types.commandSchemas["Browser.setDownloadBehavior"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserSetDownloadBehaviorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetDownloadBehaviorResult]()), + } + types.commandSchemas["Browser.cancelDownload"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserCancelDownloadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserCancelDownloadResult]()), + } + types.commandSchemas["Browser.close"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserCloseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserCloseResult]()), + } + types.commandSchemas["Browser.crash"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserCrashParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserCrashResult]()), + } + types.commandSchemas["Browser.crashGpuProcess"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserCrashGPUProcessParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserCrashGPUProcessResult]()), + } + types.commandSchemas["Browser.getVersion"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetVersionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetVersionResult]()), + } + types.commandSchemas["Browser.getBrowserCommandLine"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetBrowserCommandLineParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetBrowserCommandLineResult]()), + } + types.commandSchemas["Browser.getHistograms"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetHistogramsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetHistogramsResult]()), + } + types.commandSchemas["Browser.getHistogram"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetHistogramParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetHistogramResult]()), + } + types.commandSchemas["Browser.getWindowBounds"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetWindowBoundsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetWindowBoundsResult]()), + } + types.commandSchemas["Browser.getWindowForTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserGetWindowForTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserGetWindowForTargetResult]()), + } + types.commandSchemas["Browser.setWindowBounds"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserSetWindowBoundsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetWindowBoundsResult]()), + } + types.commandSchemas["Browser.setContentsSize"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserSetContentsSizeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetContentsSizeResult]()), + } + types.commandSchemas["Browser.setDockTile"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserSetDockTileParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserSetDockTileResult]()), + } + types.commandSchemas["Browser.executeBrowserCommand"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserExecuteBrowserCommandParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserExecuteBrowserCommandResult]()), + } + types.commandSchemas["Browser.addPrivacySandboxEnrollmentOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserAddPrivacySandboxEnrollmentOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserAddPrivacySandboxEnrollmentOverrideResult]()), + } + types.commandSchemas["Browser.addPrivacySandboxCoordinatorKeyConfig"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[BrowserAddPrivacySandboxCoordinatorKeyConfigParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[BrowserAddPrivacySandboxCoordinatorKeyConfigResult]()), + } + types.commandSchemas["CSS.addRule"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSAddRuleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSAddRuleResult]()), + } + types.commandSchemas["CSS.collectClassNames"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSCollectClassNamesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSCollectClassNamesResult]()), + } + types.commandSchemas["CSS.createStyleSheet"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSCreateStyleSheetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSCreateStyleSheetResult]()), + } + types.commandSchemas["CSS.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSDisableResult]()), + } + types.commandSchemas["CSS.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSEnableResult]()), + } + types.commandSchemas["CSS.forcePseudoState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSForcePseudoStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSForcePseudoStateResult]()), + } + types.commandSchemas["CSS.forceStartingStyle"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSForceStartingStyleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSForceStartingStyleResult]()), + } + types.commandSchemas["CSS.getBackgroundColors"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetBackgroundColorsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetBackgroundColorsResult]()), + } + types.commandSchemas["CSS.getComputedStyleForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetComputedStyleForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetComputedStyleForNodeResult]()), + } + types.commandSchemas["CSS.resolveValues"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSResolveValuesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSResolveValuesResult]()), + } + types.commandSchemas["CSS.getLonghandProperties"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetLonghandPropertiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLonghandPropertiesResult]()), + } + types.commandSchemas["CSS.getInlineStylesForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetInlineStylesForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetInlineStylesForNodeResult]()), + } + types.commandSchemas["CSS.getAnimatedStylesForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetAnimatedStylesForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetAnimatedStylesForNodeResult]()), + } + types.commandSchemas["CSS.getMatchedStylesForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetMatchedStylesForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetMatchedStylesForNodeResult]()), + } + types.commandSchemas["CSS.getEnvironmentVariables"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetEnvironmentVariablesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetEnvironmentVariablesResult]()), + } + types.commandSchemas["CSS.getMediaQueries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetMediaQueriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetMediaQueriesResult]()), + } + types.commandSchemas["CSS.getPlatformFontsForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetPlatformFontsForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetPlatformFontsForNodeResult]()), + } + types.commandSchemas["CSS.getStyleSheetText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetStyleSheetTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetStyleSheetTextResult]()), + } + types.commandSchemas["CSS.getLayersForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetLayersForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLayersForNodeResult]()), + } + types.commandSchemas["CSS.getLocationForSelector"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSGetLocationForSelectorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSGetLocationForSelectorResult]()), + } + types.commandSchemas["CSS.trackComputedStyleUpdatesForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesForNodeResult]()), + } + types.commandSchemas["CSS.trackComputedStyleUpdates"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSTrackComputedStyleUpdatesResult]()), + } + types.commandSchemas["CSS.takeComputedStyleUpdates"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSTakeComputedStyleUpdatesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSTakeComputedStyleUpdatesResult]()), + } + types.commandSchemas["CSS.setEffectivePropertyValueForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetEffectivePropertyValueForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetEffectivePropertyValueForNodeResult]()), + } + types.commandSchemas["CSS.setPropertyRulePropertyName"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetPropertyRulePropertyNameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetPropertyRulePropertyNameResult]()), + } + types.commandSchemas["CSS.setKeyframeKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetKeyframeKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetKeyframeKeyResult]()), + } + types.commandSchemas["CSS.setMediaText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetMediaTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetMediaTextResult]()), + } + types.commandSchemas["CSS.setContainerQueryText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetContainerQueryTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetContainerQueryTextResult]()), + } + types.commandSchemas["CSS.setSupportsText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetSupportsTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetSupportsTextResult]()), + } + types.commandSchemas["CSS.setNavigationText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetNavigationTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetNavigationTextResult]()), + } + types.commandSchemas["CSS.setScopeText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetScopeTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetScopeTextResult]()), + } + types.commandSchemas["CSS.setRuleSelector"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetRuleSelectorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetRuleSelectorResult]()), + } + types.commandSchemas["CSS.setStyleSheetText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetStyleSheetTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetStyleSheetTextResult]()), + } + types.commandSchemas["CSS.setStyleTexts"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetStyleTextsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetStyleTextsResult]()), + } + types.commandSchemas["CSS.startRuleUsageTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSStartRuleUsageTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSStartRuleUsageTrackingResult]()), + } + types.commandSchemas["CSS.stopRuleUsageTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSStopRuleUsageTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSStopRuleUsageTrackingResult]()), + } + types.commandSchemas["CSS.takeCoverageDelta"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSTakeCoverageDeltaParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSTakeCoverageDeltaResult]()), + } + types.commandSchemas["CSS.setLocalFontsEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CSSSetLocalFontsEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CSSSetLocalFontsEnabledResult]()), + } + types.commandSchemas["CacheStorage.deleteCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CacheStorageDeleteCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageDeleteCacheResult]()), + } + types.commandSchemas["CacheStorage.deleteEntry"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CacheStorageDeleteEntryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageDeleteEntryResult]()), + } + types.commandSchemas["CacheStorage.requestCacheNames"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CacheStorageRequestCacheNamesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestCacheNamesResult]()), + } + types.commandSchemas["CacheStorage.requestCachedResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CacheStorageRequestCachedResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestCachedResponseResult]()), + } + types.commandSchemas["CacheStorage.requestEntries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CacheStorageRequestEntriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CacheStorageRequestEntriesResult]()), + } + types.commandSchemas["Cast.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastEnableResult]()), + } + types.commandSchemas["Cast.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastDisableResult]()), + } + types.commandSchemas["Cast.setSinkToUse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastSetSinkToUseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastSetSinkToUseResult]()), + } + types.commandSchemas["Cast.startDesktopMirroring"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastStartDesktopMirroringParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastStartDesktopMirroringResult]()), + } + types.commandSchemas["Cast.startTabMirroring"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastStartTabMirroringParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastStartTabMirroringResult]()), + } + types.commandSchemas["Cast.stopCasting"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CastStopCastingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CastStopCastingResult]()), + } + types.commandSchemas["Console.clearMessages"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ConsoleClearMessagesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ConsoleClearMessagesResult]()), + } + types.commandSchemas["Console.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ConsoleDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ConsoleDisableResult]()), + } + types.commandSchemas["Console.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ConsoleEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ConsoleEnableResult]()), + } + types.commandSchemas["DOM.collectClassNamesFromSubtree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMCollectClassNamesFromSubtreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMCollectClassNamesFromSubtreeResult]()), + } + types.commandSchemas["DOM.copyTo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMCopyToParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMCopyToResult]()), + } + types.commandSchemas["DOM.describeNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDescribeNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDescribeNodeResult]()), + } + types.commandSchemas["DOM.scrollIntoViewIfNeeded"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMScrollIntoViewIfNeededParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMScrollIntoViewIfNeededResult]()), + } + types.commandSchemas["DOM.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDisableResult]()), + } + types.commandSchemas["DOM.discardSearchResults"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDiscardSearchResultsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDiscardSearchResultsResult]()), + } + types.commandSchemas["DOM.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMEnableResult]()), + } + types.commandSchemas["DOM.focus"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMFocusParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMFocusResult]()), + } + types.commandSchemas["DOM.getAttributes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetAttributesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetAttributesResult]()), + } + types.commandSchemas["DOM.getBoxModel"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetBoxModelParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetBoxModelResult]()), + } + types.commandSchemas["DOM.getContentQuads"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetContentQuadsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetContentQuadsResult]()), + } + types.commandSchemas["DOM.getDocument"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetDocumentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetDocumentResult]()), + } + types.commandSchemas["DOM.getFlattenedDocument"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetFlattenedDocumentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFlattenedDocumentResult]()), + } + types.commandSchemas["DOM.getNodesForSubtreeByStyle"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetNodesForSubtreeByStyleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodesForSubtreeByStyleResult]()), + } + types.commandSchemas["DOM.getNodeForLocation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetNodeForLocationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodeForLocationResult]()), + } + types.commandSchemas["DOM.getOuterHTML"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetOuterHTMLParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetOuterHTMLResult]()), + } + types.commandSchemas["DOM.getRelayoutBoundary"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetRelayoutBoundaryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetRelayoutBoundaryResult]()), + } + types.commandSchemas["DOM.getSearchResults"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetSearchResultsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetSearchResultsResult]()), + } + types.commandSchemas["DOM.hideHighlight"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMHideHighlightParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMHideHighlightResult]()), + } + types.commandSchemas["DOM.highlightNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMHighlightNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMHighlightNodeResult]()), + } + types.commandSchemas["DOM.highlightRect"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMHighlightRectParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMHighlightRectResult]()), + } + types.commandSchemas["DOM.markUndoableState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMMarkUndoableStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMMarkUndoableStateResult]()), + } + types.commandSchemas["DOM.moveTo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMMoveToParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMMoveToResult]()), + } + types.commandSchemas["DOM.performSearch"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMPerformSearchParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMPerformSearchResult]()), + } + types.commandSchemas["DOM.pushNodeByPathToFrontend"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMPushNodeByPathToFrontendParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMPushNodeByPathToFrontendResult]()), + } + types.commandSchemas["DOM.pushNodesByBackendIdsToFrontend"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMPushNodesByBackendIdsToFrontendParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMPushNodesByBackendIdsToFrontendResult]()), + } + types.commandSchemas["DOM.querySelector"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMQuerySelectorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMQuerySelectorResult]()), + } + types.commandSchemas["DOM.querySelectorAll"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMQuerySelectorAllParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMQuerySelectorAllResult]()), + } + types.commandSchemas["DOM.getTopLayerElements"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetTopLayerElementsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetTopLayerElementsResult]()), + } + types.commandSchemas["DOM.getElementByRelation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetElementByRelationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetElementByRelationResult]()), + } + types.commandSchemas["DOM.redo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMRedoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMRedoResult]()), + } + types.commandSchemas["DOM.removeAttribute"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMRemoveAttributeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMRemoveAttributeResult]()), + } + types.commandSchemas["DOM.removeNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMRemoveNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMRemoveNodeResult]()), + } + types.commandSchemas["DOM.requestChildNodes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMRequestChildNodesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMRequestChildNodesResult]()), + } + types.commandSchemas["DOM.requestNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMRequestNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMRequestNodeResult]()), + } + types.commandSchemas["DOM.resolveNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMResolveNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMResolveNodeResult]()), + } + types.commandSchemas["DOM.setAttributeValue"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetAttributeValueParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetAttributeValueResult]()), + } + types.commandSchemas["DOM.setAttributesAsText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetAttributesAsTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetAttributesAsTextResult]()), + } + types.commandSchemas["DOM.setFileInputFiles"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetFileInputFilesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetFileInputFilesResult]()), + } + types.commandSchemas["DOM.setNodeStackTracesEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetNodeStackTracesEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeStackTracesEnabledResult]()), + } + types.commandSchemas["DOM.getNodeStackTraces"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetNodeStackTracesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetNodeStackTracesResult]()), + } + types.commandSchemas["DOM.getFileInfo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetFileInfoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFileInfoResult]()), + } + types.commandSchemas["DOM.getDetachedDomNodes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetDetachedDOMNodesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetDetachedDOMNodesResult]()), + } + types.commandSchemas["DOM.setInspectedNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetInspectedNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetInspectedNodeResult]()), + } + types.commandSchemas["DOM.setNodeName"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetNodeNameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeNameResult]()), + } + types.commandSchemas["DOM.setNodeValue"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetNodeValueParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetNodeValueResult]()), + } + types.commandSchemas["DOM.setOuterHTML"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSetOuterHTMLParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSetOuterHTMLResult]()), + } + types.commandSchemas["DOM.undo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMUndoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMUndoResult]()), + } + types.commandSchemas["DOM.getFrameOwner"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetFrameOwnerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetFrameOwnerResult]()), + } + types.commandSchemas["DOM.getContainerForNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetContainerForNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetContainerForNodeResult]()), + } + types.commandSchemas["DOM.getQueryingDescendantsForContainer"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetQueryingDescendantsForContainerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetQueryingDescendantsForContainerResult]()), + } + types.commandSchemas["DOM.getAnchorElement"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMGetAnchorElementParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMGetAnchorElementResult]()), + } + types.commandSchemas["DOM.forceShowPopover"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMForceShowPopoverParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMForceShowPopoverResult]()), + } + types.commandSchemas["DOMDebugger.getEventListeners"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerGetEventListenersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerGetEventListenersResult]()), + } + types.commandSchemas["DOMDebugger.removeDOMBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerRemoveDOMBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveDOMBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.removeEventListenerBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerRemoveEventListenerBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveEventListenerBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.removeInstrumentationBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerRemoveInstrumentationBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveInstrumentationBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.removeXHRBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerRemoveXHRBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerRemoveXHRBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.setBreakOnCSPViolation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerSetBreakOnCSPViolationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetBreakOnCSPViolationResult]()), + } + types.commandSchemas["DOMDebugger.setDOMBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerSetDOMBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetDOMBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.setEventListenerBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerSetEventListenerBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetEventListenerBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.setInstrumentationBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerSetInstrumentationBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetInstrumentationBreakpointResult]()), + } + types.commandSchemas["DOMDebugger.setXHRBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMDebuggerSetXHRBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMDebuggerSetXHRBreakpointResult]()), + } + types.commandSchemas["DOMSnapshot.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSnapshotDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotDisableResult]()), + } + types.commandSchemas["DOMSnapshot.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSnapshotEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotEnableResult]()), + } + types.commandSchemas["DOMSnapshot.getSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSnapshotGetSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotGetSnapshotResult]()), + } + types.commandSchemas["DOMSnapshot.captureSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMSnapshotCaptureSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMSnapshotCaptureSnapshotResult]()), + } + types.commandSchemas["DOMStorage.clear"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageClearParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageClearResult]()), + } + types.commandSchemas["DOMStorage.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDisableResult]()), + } + types.commandSchemas["DOMStorage.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageEnableResult]()), + } + types.commandSchemas["DOMStorage.getDOMStorageItems"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageGetDOMStorageItemsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageGetDOMStorageItemsResult]()), + } + types.commandSchemas["DOMStorage.removeDOMStorageItem"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageRemoveDOMStorageItemParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageRemoveDOMStorageItemResult]()), + } + types.commandSchemas["DOMStorage.setDOMStorageItem"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DOMStorageSetDOMStorageItemParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageSetDOMStorageItemResult]()), + } + types.commandSchemas["Debugger.continueToLocation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerContinueToLocationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerContinueToLocationResult]()), + } + types.commandSchemas["Debugger.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerDisableResult]()), + } + types.commandSchemas["Debugger.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerEnableResult]()), + } + types.commandSchemas["Debugger.evaluateOnCallFrame"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerEvaluateOnCallFrameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerEvaluateOnCallFrameResult]()), + } + types.commandSchemas["Debugger.getPossibleBreakpoints"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerGetPossibleBreakpointsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetPossibleBreakpointsResult]()), + } + types.commandSchemas["Debugger.getScriptSource"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerGetScriptSourceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetScriptSourceResult]()), + } + types.commandSchemas["Debugger.disassembleWasmModule"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerDisassembleWasmModuleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerDisassembleWasmModuleResult]()), + } + types.commandSchemas["Debugger.nextWasmDisassemblyChunk"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerNextWasmDisassemblyChunkParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerNextWasmDisassemblyChunkResult]()), + } + types.commandSchemas["Debugger.getWasmBytecode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerGetWasmBytecodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetWasmBytecodeResult]()), + } + types.commandSchemas["Debugger.getStackTrace"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerGetStackTraceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerGetStackTraceResult]()), + } + types.commandSchemas["Debugger.pause"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerPauseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPauseResult]()), + } + types.commandSchemas["Debugger.pauseOnAsyncCall"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerPauseOnAsyncCallParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPauseOnAsyncCallResult]()), + } + types.commandSchemas["Debugger.removeBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerRemoveBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerRemoveBreakpointResult]()), + } + types.commandSchemas["Debugger.restartFrame"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerRestartFrameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerRestartFrameResult]()), + } + types.commandSchemas["Debugger.resume"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerResumeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerResumeResult]()), + } + types.commandSchemas["Debugger.searchInContent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSearchInContentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSearchInContentResult]()), + } + types.commandSchemas["Debugger.setAsyncCallStackDepth"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetAsyncCallStackDepthParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetAsyncCallStackDepthResult]()), + } + types.commandSchemas["Debugger.setBlackboxExecutionContexts"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBlackboxExecutionContextsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxExecutionContextsResult]()), + } + types.commandSchemas["Debugger.setBlackboxPatterns"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBlackboxPatternsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxPatternsResult]()), + } + types.commandSchemas["Debugger.setBlackboxedRanges"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBlackboxedRangesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBlackboxedRangesResult]()), + } + types.commandSchemas["Debugger.setBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointResult]()), + } + types.commandSchemas["Debugger.setInstrumentationBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetInstrumentationBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetInstrumentationBreakpointResult]()), + } + types.commandSchemas["Debugger.setBreakpointByUrl"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBreakpointByURLParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointByURLResult]()), + } + types.commandSchemas["Debugger.setBreakpointOnFunctionCall"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBreakpointOnFunctionCallParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointOnFunctionCallResult]()), + } + types.commandSchemas["Debugger.setBreakpointsActive"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetBreakpointsActiveParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetBreakpointsActiveResult]()), + } + types.commandSchemas["Debugger.setPauseOnExceptions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetPauseOnExceptionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetPauseOnExceptionsResult]()), + } + types.commandSchemas["Debugger.setReturnValue"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetReturnValueParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetReturnValueResult]()), + } + types.commandSchemas["Debugger.setScriptSource"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetScriptSourceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetScriptSourceResult]()), + } + types.commandSchemas["Debugger.setSkipAllPauses"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetSkipAllPausesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetSkipAllPausesResult]()), + } + types.commandSchemas["Debugger.setVariableValue"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerSetVariableValueParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerSetVariableValueResult]()), + } + types.commandSchemas["Debugger.stepInto"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerStepIntoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepIntoResult]()), + } + types.commandSchemas["Debugger.stepOut"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerStepOutParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepOutResult]()), + } + types.commandSchemas["Debugger.stepOver"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DebuggerStepOverParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DebuggerStepOverResult]()), + } + types.commandSchemas["DeviceAccess.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceAccessEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessEnableResult]()), + } + types.commandSchemas["DeviceAccess.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceAccessDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessDisableResult]()), + } + types.commandSchemas["DeviceAccess.selectPrompt"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceAccessSelectPromptParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessSelectPromptResult]()), + } + types.commandSchemas["DeviceAccess.cancelPrompt"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceAccessCancelPromptParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessCancelPromptResult]()), + } + types.commandSchemas["DeviceOrientation.clearDeviceOrientationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceOrientationClearDeviceOrientationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceOrientationClearDeviceOrientationOverrideResult]()), + } + types.commandSchemas["DeviceOrientation.setDeviceOrientationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[DeviceOrientationSetDeviceOrientationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[DeviceOrientationSetDeviceOrientationOverrideResult]()), + } + types.commandSchemas["Emulation.canEmulate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationCanEmulateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationCanEmulateResult]()), + } + types.commandSchemas["Emulation.clearDeviceMetricsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationClearDeviceMetricsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDeviceMetricsOverrideResult]()), + } + types.commandSchemas["Emulation.clearGeolocationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationClearGeolocationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearGeolocationOverrideResult]()), + } + types.commandSchemas["Emulation.resetPageScaleFactor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationResetPageScaleFactorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationResetPageScaleFactorResult]()), + } + types.commandSchemas["Emulation.setFocusEmulationEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetFocusEmulationEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetFocusEmulationEnabledResult]()), + } + types.commandSchemas["Emulation.setAutoDarkModeOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetAutoDarkModeOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetAutoDarkModeOverrideResult]()), + } + types.commandSchemas["Emulation.setCPUThrottlingRate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetCPUThrottlingRateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetCPUThrottlingRateResult]()), + } + types.commandSchemas["Emulation.setDefaultBackgroundColorOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDefaultBackgroundColorOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDefaultBackgroundColorOverrideResult]()), + } + types.commandSchemas["Emulation.setSafeAreaInsetsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetSafeAreaInsetsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSafeAreaInsetsOverrideResult]()), + } + types.commandSchemas["Emulation.setDeviceMetricsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDeviceMetricsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDeviceMetricsOverrideResult]()), + } + types.commandSchemas["Emulation.setDevicePostureOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDevicePostureOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDevicePostureOverrideResult]()), + } + types.commandSchemas["Emulation.clearDevicePostureOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationClearDevicePostureOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDevicePostureOverrideResult]()), + } + types.commandSchemas["Emulation.setDisplayFeaturesOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDisplayFeaturesOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDisplayFeaturesOverrideResult]()), + } + types.commandSchemas["Emulation.clearDisplayFeaturesOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationClearDisplayFeaturesOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearDisplayFeaturesOverrideResult]()), + } + types.commandSchemas["Emulation.setScrollbarsHidden"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetScrollbarsHiddenParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetScrollbarsHiddenResult]()), + } + types.commandSchemas["Emulation.setDocumentCookieDisabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDocumentCookieDisabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDocumentCookieDisabledResult]()), + } + types.commandSchemas["Emulation.setEmitTouchEventsForMouse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetEmitTouchEventsForMouseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmitTouchEventsForMouseResult]()), + } + types.commandSchemas["Emulation.setEmulatedMedia"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetEmulatedMediaParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedMediaResult]()), + } + types.commandSchemas["Emulation.setEmulatedVisionDeficiency"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetEmulatedVisionDeficiencyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedVisionDeficiencyResult]()), + } + types.commandSchemas["Emulation.setEmulatedOSTextScale"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetEmulatedOSTextScaleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetEmulatedOSTextScaleResult]()), + } + types.commandSchemas["Emulation.setGeolocationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetGeolocationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetGeolocationOverrideResult]()), + } + types.commandSchemas["Emulation.getOverriddenSensorInformation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationGetOverriddenSensorInformationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationGetOverriddenSensorInformationResult]()), + } + types.commandSchemas["Emulation.setSensorOverrideEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetSensorOverrideEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSensorOverrideEnabledResult]()), + } + types.commandSchemas["Emulation.setSensorOverrideReadings"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetSensorOverrideReadingsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSensorOverrideReadingsResult]()), + } + types.commandSchemas["Emulation.setPressureSourceOverrideEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetPressureSourceOverrideEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureSourceOverrideEnabledResult]()), + } + types.commandSchemas["Emulation.setPressureStateOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetPressureStateOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureStateOverrideResult]()), + } + types.commandSchemas["Emulation.setPressureDataOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetPressureDataOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPressureDataOverrideResult]()), + } + types.commandSchemas["Emulation.setIdleOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetIdleOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetIdleOverrideResult]()), + } + types.commandSchemas["Emulation.clearIdleOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationClearIdleOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationClearIdleOverrideResult]()), + } + types.commandSchemas["Emulation.setNavigatorOverrides"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetNavigatorOverridesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetNavigatorOverridesResult]()), + } + types.commandSchemas["Emulation.setPageScaleFactor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetPageScaleFactorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPageScaleFactorResult]()), + } + types.commandSchemas["Emulation.setScriptExecutionDisabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetScriptExecutionDisabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetScriptExecutionDisabledResult]()), + } + types.commandSchemas["Emulation.setTouchEmulationEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetTouchEmulationEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetTouchEmulationEnabledResult]()), + } + types.commandSchemas["Emulation.setVirtualTimePolicy"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetVirtualTimePolicyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetVirtualTimePolicyResult]()), + } + types.commandSchemas["Emulation.setLocaleOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetLocaleOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetLocaleOverrideResult]()), + } + types.commandSchemas["Emulation.setTimezoneOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetTimezoneOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetTimezoneOverrideResult]()), + } + types.commandSchemas["Emulation.setVisibleSize"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetVisibleSizeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetVisibleSizeResult]()), + } + types.commandSchemas["Emulation.setDisabledImageTypes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDisabledImageTypesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDisabledImageTypesResult]()), + } + types.commandSchemas["Emulation.setDataSaverOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetDataSaverOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetDataSaverOverrideResult]()), + } + types.commandSchemas["Emulation.setHardwareConcurrencyOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetHardwareConcurrencyOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetHardwareConcurrencyOverrideResult]()), + } + types.commandSchemas["Emulation.setUserAgentOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetUserAgentOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetUserAgentOverrideResult]()), + } + types.commandSchemas["Emulation.setAutomationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetAutomationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetAutomationOverrideResult]()), + } + types.commandSchemas["Emulation.setSmallViewportHeightDifferenceOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetSmallViewportHeightDifferenceOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetSmallViewportHeightDifferenceOverrideResult]()), + } + types.commandSchemas["Emulation.getScreenInfos"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationGetScreenInfosParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationGetScreenInfosResult]()), + } + types.commandSchemas["Emulation.addScreen"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationAddScreenParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationAddScreenResult]()), + } + types.commandSchemas["Emulation.updateScreen"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationUpdateScreenParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationUpdateScreenResult]()), + } + types.commandSchemas["Emulation.removeScreen"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationRemoveScreenParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationRemoveScreenResult]()), + } + types.commandSchemas["Emulation.setPrimaryScreen"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EmulationSetPrimaryScreenParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EmulationSetPrimaryScreenResult]()), + } + types.commandSchemas["EventBreakpoints.setInstrumentationBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EventBreakpointsSetInstrumentationBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsSetInstrumentationBreakpointResult]()), + } + types.commandSchemas["EventBreakpoints.removeInstrumentationBreakpoint"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EventBreakpointsRemoveInstrumentationBreakpointParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsRemoveInstrumentationBreakpointResult]()), + } + types.commandSchemas["EventBreakpoints.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[EventBreakpointsDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[EventBreakpointsDisableResult]()), + } + types.commandSchemas["Extensions.triggerAction"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsTriggerActionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsTriggerActionResult]()), + } + types.commandSchemas["Extensions.loadUnpacked"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[CDPParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[CDPResult]()), + } + types.commandSchemas["Extensions.getExtensions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsGetExtensionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsGetExtensionsResult]()), + } + types.commandSchemas["Extensions.uninstall"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsUninstallParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsUninstallResult]()), + } + types.commandSchemas["Extensions.getStorageItems"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsGetStorageItemsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsGetStorageItemsResult]()), + } + types.commandSchemas["Extensions.removeStorageItems"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsRemoveStorageItemsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsRemoveStorageItemsResult]()), + } + types.commandSchemas["Extensions.clearStorageItems"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsClearStorageItemsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsClearStorageItemsResult]()), + } + types.commandSchemas["Extensions.setStorageItems"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ExtensionsSetStorageItemsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ExtensionsSetStorageItemsResult]()), + } + types.commandSchemas["FedCm.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmEnableResult]()), + } + types.commandSchemas["FedCm.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmDisableResult]()), + } + types.commandSchemas["FedCm.selectAccount"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmSelectAccountParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmSelectAccountResult]()), + } + types.commandSchemas["FedCm.clickDialogButton"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmClickDialogButtonParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmClickDialogButtonResult]()), + } + types.commandSchemas["FedCm.openUrl"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmOpenURLParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmOpenURLResult]()), + } + types.commandSchemas["FedCm.dismissDialog"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmDismissDialogParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmDismissDialogResult]()), + } + types.commandSchemas["FedCm.resetCooldown"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FedCmResetCooldownParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FedCmResetCooldownResult]()), + } + types.commandSchemas["Fetch.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchDisableResult]()), + } + types.commandSchemas["Fetch.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchEnableResult]()), + } + types.commandSchemas["Fetch.failRequest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchFailRequestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchFailRequestResult]()), + } + types.commandSchemas["Fetch.fulfillRequest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchFulfillRequestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchFulfillRequestResult]()), + } + types.commandSchemas["Fetch.continueRequest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchContinueRequestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueRequestResult]()), + } + types.commandSchemas["Fetch.continueWithAuth"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchContinueWithAuthParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueWithAuthResult]()), + } + types.commandSchemas["Fetch.continueResponse"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchContinueResponseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchContinueResponseResult]()), + } + types.commandSchemas["Fetch.getResponseBody"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchGetResponseBodyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchGetResponseBodyResult]()), + } + types.commandSchemas["Fetch.takeResponseBodyAsStream"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FetchTakeResponseBodyAsStreamParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FetchTakeResponseBodyAsStreamResult]()), + } + types.commandSchemas["FileSystem.getDirectory"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[FileSystemGetDirectoryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[FileSystemGetDirectoryResult]()), + } + types.commandSchemas["HeadlessExperimental.beginFrame"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeadlessExperimentalBeginFrameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalBeginFrameResult]()), + } + types.commandSchemas["HeadlessExperimental.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeadlessExperimentalDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalDisableResult]()), + } + types.commandSchemas["HeadlessExperimental.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeadlessExperimentalEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeadlessExperimentalEnableResult]()), + } + types.commandSchemas["HeapProfiler.addInspectedHeapObject"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerAddInspectedHeapObjectParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerAddInspectedHeapObjectResult]()), + } + types.commandSchemas["HeapProfiler.collectGarbage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerCollectGarbageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerCollectGarbageResult]()), + } + types.commandSchemas["HeapProfiler.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerDisableResult]()), + } + types.commandSchemas["HeapProfiler.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerEnableResult]()), + } + types.commandSchemas["HeapProfiler.getHeapObjectId"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerGetHeapObjectIDParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetHeapObjectIDResult]()), + } + types.commandSchemas["HeapProfiler.getObjectByHeapObjectId"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerGetObjectByHeapObjectIDParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetObjectByHeapObjectIDResult]()), + } + types.commandSchemas["HeapProfiler.getSamplingProfile"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerGetSamplingProfileParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerGetSamplingProfileResult]()), + } + types.commandSchemas["HeapProfiler.startSampling"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerStartSamplingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStartSamplingResult]()), + } + types.commandSchemas["HeapProfiler.startTrackingHeapObjects"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerStartTrackingHeapObjectsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStartTrackingHeapObjectsResult]()), + } + types.commandSchemas["HeapProfiler.stopSampling"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerStopSamplingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStopSamplingResult]()), + } + types.commandSchemas["HeapProfiler.stopTrackingHeapObjects"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerStopTrackingHeapObjectsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerStopTrackingHeapObjectsResult]()), + } + types.commandSchemas["HeapProfiler.takeHeapSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[HeapProfilerTakeHeapSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerTakeHeapSnapshotResult]()), + } + types.commandSchemas["IO.close"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IOCloseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IOCloseResult]()), + } + types.commandSchemas["IO.read"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IOReadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IOReadResult]()), + } + types.commandSchemas["IO.resolveBlob"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IOResolveBlobParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IOResolveBlobResult]()), + } + types.commandSchemas["IndexedDB.clearObjectStore"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBClearObjectStoreParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBClearObjectStoreResult]()), + } + types.commandSchemas["IndexedDB.deleteDatabase"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBDeleteDatabaseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDeleteDatabaseResult]()), + } + types.commandSchemas["IndexedDB.deleteObjectStoreEntries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBDeleteObjectStoreEntriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDeleteObjectStoreEntriesResult]()), + } + types.commandSchemas["IndexedDB.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBDisableResult]()), + } + types.commandSchemas["IndexedDB.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBEnableResult]()), + } + types.commandSchemas["IndexedDB.requestData"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBRequestDataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDataResult]()), + } + types.commandSchemas["IndexedDB.getMetadata"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBGetMetadataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBGetMetadataResult]()), + } + types.commandSchemas["IndexedDB.requestDatabase"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBRequestDatabaseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDatabaseResult]()), + } + types.commandSchemas["IndexedDB.requestDatabaseNames"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[IndexedDBRequestDatabaseNamesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[IndexedDBRequestDatabaseNamesResult]()), + } + types.commandSchemas["Input.dispatchDragEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputDispatchDragEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchDragEventResult]()), + } + types.commandSchemas["Input.dispatchKeyEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputDispatchKeyEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchKeyEventResult]()), + } + types.commandSchemas["Input.insertText"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputInsertTextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputInsertTextResult]()), + } + types.commandSchemas["Input.imeSetComposition"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputImeSetCompositionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputImeSetCompositionResult]()), + } + types.commandSchemas["Input.dispatchMouseEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputDispatchMouseEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchMouseEventResult]()), + } + types.commandSchemas["Input.dispatchTouchEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputDispatchTouchEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputDispatchTouchEventResult]()), + } + types.commandSchemas["Input.cancelDragging"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputCancelDraggingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputCancelDraggingResult]()), + } + types.commandSchemas["Input.emulateTouchFromMouseEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputEmulateTouchFromMouseEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputEmulateTouchFromMouseEventResult]()), + } + types.commandSchemas["Input.setIgnoreInputEvents"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputSetIgnoreInputEventsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputSetIgnoreInputEventsResult]()), + } + types.commandSchemas["Input.setInterceptDrags"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputSetInterceptDragsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputSetInterceptDragsResult]()), + } + types.commandSchemas["Input.synthesizePinchGesture"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputSynthesizePinchGestureParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizePinchGestureResult]()), + } + types.commandSchemas["Input.synthesizeScrollGesture"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputSynthesizeScrollGestureParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizeScrollGestureResult]()), + } + types.commandSchemas["Input.synthesizeTapGesture"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InputSynthesizeTapGestureParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InputSynthesizeTapGestureResult]()), + } + types.commandSchemas["Inspector.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InspectorDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InspectorDisableResult]()), + } + types.commandSchemas["Inspector.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[InspectorEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[InspectorEnableResult]()), + } + types.commandSchemas["LayerTree.compositingReasons"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeCompositingReasonsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeCompositingReasonsResult]()), + } + types.commandSchemas["LayerTree.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeDisableResult]()), + } + types.commandSchemas["LayerTree.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeEnableResult]()), + } + types.commandSchemas["LayerTree.loadSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeLoadSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLoadSnapshotResult]()), + } + types.commandSchemas["LayerTree.makeSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeMakeSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeMakeSnapshotResult]()), + } + types.commandSchemas["LayerTree.profileSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeProfileSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeProfileSnapshotResult]()), + } + types.commandSchemas["LayerTree.releaseSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeReleaseSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeReleaseSnapshotResult]()), + } + types.commandSchemas["LayerTree.replaySnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeReplaySnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeReplaySnapshotResult]()), + } + types.commandSchemas["LayerTree.snapshotCommandLog"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LayerTreeSnapshotCommandLogParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeSnapshotCommandLogResult]()), + } + types.commandSchemas["Log.clear"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LogClearParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LogClearResult]()), + } + types.commandSchemas["Log.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LogDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LogDisableResult]()), + } + types.commandSchemas["Log.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LogEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LogEnableResult]()), + } + types.commandSchemas["Log.startViolationsReport"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LogStartViolationsReportParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LogStartViolationsReportResult]()), + } + types.commandSchemas["Log.stopViolationsReport"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[LogStopViolationsReportParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[LogStopViolationsReportResult]()), + } + types.commandSchemas["Media.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MediaEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MediaEnableResult]()), + } + types.commandSchemas["Media.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MediaDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MediaDisableResult]()), + } + types.commandSchemas["Memory.getDOMCounters"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryGetDOMCountersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetDOMCountersResult]()), + } + types.commandSchemas["Memory.getDOMCountersForLeakDetection"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryGetDOMCountersForLeakDetectionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetDOMCountersForLeakDetectionResult]()), + } + types.commandSchemas["Memory.prepareForLeakDetection"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryPrepareForLeakDetectionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryPrepareForLeakDetectionResult]()), + } + types.commandSchemas["Memory.forciblyPurgeJavaScriptMemory"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryForciblyPurgeJavaScriptMemoryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryForciblyPurgeJavaScriptMemoryResult]()), + } + types.commandSchemas["Memory.setPressureNotificationsSuppressed"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemorySetPressureNotificationsSuppressedParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemorySetPressureNotificationsSuppressedResult]()), + } + types.commandSchemas["Memory.simulatePressureNotification"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemorySimulatePressureNotificationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemorySimulatePressureNotificationResult]()), + } + types.commandSchemas["Memory.startSampling"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryStartSamplingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryStartSamplingResult]()), + } + types.commandSchemas["Memory.stopSampling"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryStopSamplingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryStopSamplingResult]()), + } + types.commandSchemas["Memory.getAllTimeSamplingProfile"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryGetAllTimeSamplingProfileParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetAllTimeSamplingProfileResult]()), + } + types.commandSchemas["Memory.getBrowserSamplingProfile"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryGetBrowserSamplingProfileParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetBrowserSamplingProfileResult]()), + } + types.commandSchemas["Memory.getSamplingProfile"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[MemoryGetSamplingProfileParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[MemoryGetSamplingProfileResult]()), + } + types.commandSchemas["Network.setAcceptedEncodings"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetAcceptedEncodingsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetAcceptedEncodingsResult]()), + } + types.commandSchemas["Network.clearAcceptedEncodingsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkClearAcceptedEncodingsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearAcceptedEncodingsOverrideResult]()), + } + types.commandSchemas["Network.canClearBrowserCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkCanClearBrowserCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanClearBrowserCacheResult]()), + } + types.commandSchemas["Network.canClearBrowserCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkCanClearBrowserCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanClearBrowserCookiesResult]()), + } + types.commandSchemas["Network.canEmulateNetworkConditions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkCanEmulateNetworkConditionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkCanEmulateNetworkConditionsResult]()), + } + types.commandSchemas["Network.clearBrowserCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkClearBrowserCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearBrowserCacheResult]()), + } + types.commandSchemas["Network.clearBrowserCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkClearBrowserCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkClearBrowserCookiesResult]()), + } + types.commandSchemas["Network.continueInterceptedRequest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkContinueInterceptedRequestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkContinueInterceptedRequestResult]()), + } + types.commandSchemas["Network.deleteCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkDeleteCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeleteCookiesResult]()), + } + types.commandSchemas["Network.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkDisableResult]()), + } + types.commandSchemas["Network.emulateNetworkConditions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsResult]()), + } + types.commandSchemas["Network.emulateNetworkConditionsByRule"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsByRuleParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkEmulateNetworkConditionsByRuleResult]()), + } + types.commandSchemas["Network.overrideNetworkState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkOverrideNetworkStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkOverrideNetworkStateResult]()), + } + types.commandSchemas["Network.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableResult]()), + } + types.commandSchemas["Network.configureDurableMessages"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkConfigureDurableMessagesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkConfigureDurableMessagesResult]()), + } + types.commandSchemas["Network.getAllCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetAllCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetAllCookiesResult]()), + } + types.commandSchemas["Network.getCertificate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetCertificateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetCertificateResult]()), + } + types.commandSchemas["Network.getCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetCookiesResult]()), + } + types.commandSchemas["Network.getResponseBody"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetResponseBodyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetResponseBodyResult]()), + } + types.commandSchemas["Network.getRequestPostData"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetRequestPostDataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetRequestPostDataResult]()), + } + types.commandSchemas["Network.getResponseBodyForInterception"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetResponseBodyForInterceptionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetResponseBodyForInterceptionResult]()), + } + types.commandSchemas["Network.takeResponseBodyForInterceptionAsStream"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkTakeResponseBodyForInterceptionAsStreamParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkTakeResponseBodyForInterceptionAsStreamResult]()), + } + types.commandSchemas["Network.replayXHR"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkReplayXHRParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkReplayXHRResult]()), + } + types.commandSchemas["Network.searchInResponseBody"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSearchInResponseBodyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSearchInResponseBodyResult]()), + } + types.commandSchemas["Network.setBlockedURLs"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetBlockedURLsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetBlockedURLsResult]()), + } + types.commandSchemas["Network.setBypassServiceWorker"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetBypassServiceWorkerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetBypassServiceWorkerResult]()), + } + types.commandSchemas["Network.setCacheDisabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetCacheDisabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCacheDisabledResult]()), + } + types.commandSchemas["Network.setCookie"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetCookieParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookieResult]()), + } + types.commandSchemas["Network.setCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookiesResult]()), + } + types.commandSchemas["Network.setExtraHTTPHeaders"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetExtraHTTPHeadersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetExtraHTTPHeadersResult]()), + } + types.commandSchemas["Network.setAttachDebugStack"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetAttachDebugStackParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetAttachDebugStackResult]()), + } + types.commandSchemas["Network.setRequestInterception"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetRequestInterceptionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetRequestInterceptionResult]()), + } + types.commandSchemas["Network.setUserAgentOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetUserAgentOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetUserAgentOverrideResult]()), + } + types.commandSchemas["Network.streamResourceContent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkStreamResourceContentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkStreamResourceContentResult]()), + } + types.commandSchemas["Network.getSecurityIsolationStatus"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkGetSecurityIsolationStatusParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkGetSecurityIsolationStatusResult]()), + } + types.commandSchemas["Network.enableReportingApi"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkEnableReportingAPIParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableReportingAPIResult]()), + } + types.commandSchemas["Network.enableDeviceBoundSessions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkEnableDeviceBoundSessionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkEnableDeviceBoundSessionsResult]()), + } + types.commandSchemas["Network.fetchSchemefulSite"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkFetchSchemefulSiteParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkFetchSchemefulSiteResult]()), + } + types.commandSchemas["Network.loadNetworkResource"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkLoadNetworkResourceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadNetworkResourceResult]()), + } + types.commandSchemas["Network.setCookieControls"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[NetworkSetCookieControlsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[NetworkSetCookieControlsResult]()), + } + types.commandSchemas["Overlay.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayDisableResult]()), + } + types.commandSchemas["Overlay.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayEnableResult]()), + } + types.commandSchemas["Overlay.getHighlightObjectForTest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayGetHighlightObjectForTestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetHighlightObjectForTestResult]()), + } + types.commandSchemas["Overlay.getGridHighlightObjectsForTest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayGetGridHighlightObjectsForTestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetGridHighlightObjectsForTestResult]()), + } + types.commandSchemas["Overlay.getSourceOrderHighlightObjectForTest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayGetSourceOrderHighlightObjectForTestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayGetSourceOrderHighlightObjectForTestResult]()), + } + types.commandSchemas["Overlay.hideHighlight"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHideHighlightParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHideHighlightResult]()), + } + types.commandSchemas["Overlay.highlightFrame"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHighlightFrameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightFrameResult]()), + } + types.commandSchemas["Overlay.highlightNode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHighlightNodeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightNodeResult]()), + } + types.commandSchemas["Overlay.highlightQuad"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHighlightQuadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightQuadResult]()), + } + types.commandSchemas["Overlay.highlightRect"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHighlightRectParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightRectResult]()), + } + types.commandSchemas["Overlay.highlightSourceOrder"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlayHighlightSourceOrderParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlayHighlightSourceOrderResult]()), + } + types.commandSchemas["Overlay.setInspectMode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetInspectModeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetInspectModeResult]()), + } + types.commandSchemas["Overlay.setShowAdHighlights"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowAdHighlightsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowAdHighlightsResult]()), + } + types.commandSchemas["Overlay.setPausedInDebuggerMessage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetPausedInDebuggerMessageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetPausedInDebuggerMessageResult]()), + } + types.commandSchemas["Overlay.setShowDebugBorders"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowDebugBordersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowDebugBordersResult]()), + } + types.commandSchemas["Overlay.setShowFPSCounter"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowFPSCounterParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowFPSCounterResult]()), + } + types.commandSchemas["Overlay.setShowGridOverlays"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowGridOverlaysParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowGridOverlaysResult]()), + } + types.commandSchemas["Overlay.setShowFlexOverlays"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowFlexOverlaysParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowFlexOverlaysResult]()), + } + types.commandSchemas["Overlay.setShowScrollSnapOverlays"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowScrollSnapOverlaysParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowScrollSnapOverlaysResult]()), + } + types.commandSchemas["Overlay.setShowContainerQueryOverlays"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowContainerQueryOverlaysParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowContainerQueryOverlaysResult]()), + } + types.commandSchemas["Overlay.setShowInspectedElementAnchor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowInspectedElementAnchorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowInspectedElementAnchorResult]()), + } + types.commandSchemas["Overlay.setShowPaintRects"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowPaintRectsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowPaintRectsResult]()), + } + types.commandSchemas["Overlay.setShowLayoutShiftRegions"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowLayoutShiftRegionsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowLayoutShiftRegionsResult]()), + } + types.commandSchemas["Overlay.setShowScrollBottleneckRects"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowScrollBottleneckRectsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowScrollBottleneckRectsResult]()), + } + types.commandSchemas["Overlay.setShowHitTestBorders"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowHitTestBordersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowHitTestBordersResult]()), + } + types.commandSchemas["Overlay.setShowWebVitals"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowWebVitalsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowWebVitalsResult]()), + } + types.commandSchemas["Overlay.setShowViewportSizeOnResize"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowViewportSizeOnResizeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowViewportSizeOnResizeResult]()), + } + types.commandSchemas["Overlay.setShowHinge"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowHingeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowHingeResult]()), + } + types.commandSchemas["Overlay.setShowIsolatedElements"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowIsolatedElementsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowIsolatedElementsResult]()), + } + types.commandSchemas["Overlay.setShowWindowControlsOverlay"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[OverlaySetShowWindowControlsOverlayParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[OverlaySetShowWindowControlsOverlayResult]()), + } + types.commandSchemas["PWA.getOsAppState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWAGetOsAppStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWAGetOsAppStateResult]()), + } + types.commandSchemas["PWA.install"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWAInstallParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWAInstallResult]()), + } + types.commandSchemas["PWA.uninstall"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWAUninstallParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWAUninstallResult]()), + } + types.commandSchemas["PWA.launch"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWALaunchParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWALaunchResult]()), + } + types.commandSchemas["PWA.launchFilesInApp"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWALaunchFilesInAppParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWALaunchFilesInAppResult]()), + } + types.commandSchemas["PWA.openCurrentPageInApp"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWAOpenCurrentPageInAppParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWAOpenCurrentPageInAppResult]()), + } + types.commandSchemas["PWA.changeAppUserSettings"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PWAChangeAppUserSettingsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PWAChangeAppUserSettingsResult]()), + } + types.commandSchemas["Page.addScriptToEvaluateOnLoad"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnLoadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnLoadResult]()), + } + types.commandSchemas["Page.addScriptToEvaluateOnNewDocument"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnNewDocumentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageAddScriptToEvaluateOnNewDocumentResult]()), + } + types.commandSchemas["Page.bringToFront"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageBringToFrontParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageBringToFrontResult]()), + } + types.commandSchemas["Page.captureScreenshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageCaptureScreenshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageCaptureScreenshotResult]()), + } + types.commandSchemas["Page.captureSnapshot"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageCaptureSnapshotParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageCaptureSnapshotResult]()), + } + types.commandSchemas["Page.clearDeviceMetricsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageClearDeviceMetricsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageClearDeviceMetricsOverrideResult]()), + } + types.commandSchemas["Page.clearDeviceOrientationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageClearDeviceOrientationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageClearDeviceOrientationOverrideResult]()), + } + types.commandSchemas["Page.clearGeolocationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageClearGeolocationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageClearGeolocationOverrideResult]()), + } + types.commandSchemas["Page.createIsolatedWorld"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageCreateIsolatedWorldParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageCreateIsolatedWorldResult]()), + } + types.commandSchemas["Page.deleteCookie"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageDeleteCookieParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageDeleteCookieResult]()), + } + types.commandSchemas["Page.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageDisableResult]()), + } + types.commandSchemas["Page.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageEnableResult]()), + } + types.commandSchemas["Page.getAppManifest"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetAppManifestParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetAppManifestResult]()), + } + types.commandSchemas["Page.getInstallabilityErrors"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetInstallabilityErrorsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetInstallabilityErrorsResult]()), + } + types.commandSchemas["Page.getManifestIcons"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetManifestIconsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetManifestIconsResult]()), + } + types.commandSchemas["Page.getAppId"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetAppIDParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetAppIDResult]()), + } + types.commandSchemas["Page.getAdScriptAncestry"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetAdScriptAncestryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetAdScriptAncestryResult]()), + } + types.commandSchemas["Page.getFrameTree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetFrameTreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetFrameTreeResult]()), + } + types.commandSchemas["Page.getLayoutMetrics"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetLayoutMetricsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetLayoutMetricsResult]()), + } + types.commandSchemas["Page.getNavigationHistory"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetNavigationHistoryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetNavigationHistoryResult]()), + } + types.commandSchemas["Page.resetNavigationHistory"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageResetNavigationHistoryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageResetNavigationHistoryResult]()), + } + types.commandSchemas["Page.getResourceContent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetResourceContentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetResourceContentResult]()), + } + types.commandSchemas["Page.getResourceTree"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetResourceTreeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetResourceTreeResult]()), + } + types.commandSchemas["Page.handleJavaScriptDialog"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageHandleJavaScriptDialogParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageHandleJavaScriptDialogResult]()), + } + types.commandSchemas["Page.navigate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageNavigateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageNavigateResult]()), + } + types.commandSchemas["Page.navigateToHistoryEntry"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageNavigateToHistoryEntryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageNavigateToHistoryEntryResult]()), + } + types.commandSchemas["Page.printToPDF"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PagePrintToPDFParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PagePrintToPDFResult]()), + } + types.commandSchemas["Page.reload"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageReloadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageReloadResult]()), + } + types.commandSchemas["Page.removeScriptToEvaluateOnLoad"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnLoadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnLoadResult]()), + } + types.commandSchemas["Page.removeScriptToEvaluateOnNewDocument"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnNewDocumentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageRemoveScriptToEvaluateOnNewDocumentResult]()), + } + types.commandSchemas["Page.screencastFrameAck"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageScreencastFrameAckParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastFrameAckResult]()), + } + types.commandSchemas["Page.searchInResource"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSearchInResourceParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSearchInResourceResult]()), + } + types.commandSchemas["Page.setAdBlockingEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetAdBlockingEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetAdBlockingEnabledResult]()), + } + types.commandSchemas["Page.setBypassCSP"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetBypassCSPParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetBypassCSPResult]()), + } + types.commandSchemas["Page.getPermissionsPolicyState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetPermissionsPolicyStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetPermissionsPolicyStateResult]()), + } + types.commandSchemas["Page.getOriginTrials"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetOriginTrialsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetOriginTrialsResult]()), + } + types.commandSchemas["Page.setDeviceMetricsOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetDeviceMetricsOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetDeviceMetricsOverrideResult]()), + } + types.commandSchemas["Page.setDeviceOrientationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetDeviceOrientationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetDeviceOrientationOverrideResult]()), + } + types.commandSchemas["Page.setFontFamilies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetFontFamiliesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetFontFamiliesResult]()), + } + types.commandSchemas["Page.setFontSizes"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetFontSizesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetFontSizesResult]()), + } + types.commandSchemas["Page.setDocumentContent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetDocumentContentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetDocumentContentResult]()), + } + types.commandSchemas["Page.setDownloadBehavior"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetDownloadBehaviorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetDownloadBehaviorResult]()), + } + types.commandSchemas["Page.setGeolocationOverride"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetGeolocationOverrideParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetGeolocationOverrideResult]()), + } + types.commandSchemas["Page.setLifecycleEventsEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetLifecycleEventsEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetLifecycleEventsEnabledResult]()), + } + types.commandSchemas["Page.setTouchEmulationEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetTouchEmulationEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetTouchEmulationEnabledResult]()), + } + types.commandSchemas["Page.startScreencast"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageStartScreencastParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageStartScreencastResult]()), + } + types.commandSchemas["Page.stopLoading"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageStopLoadingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageStopLoadingResult]()), + } + types.commandSchemas["Page.crash"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageCrashParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageCrashResult]()), + } + types.commandSchemas["Page.close"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageCloseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageCloseResult]()), + } + types.commandSchemas["Page.setWebLifecycleState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetWebLifecycleStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetWebLifecycleStateResult]()), + } + types.commandSchemas["Page.stopScreencast"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageStopScreencastParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageStopScreencastResult]()), + } + types.commandSchemas["Page.produceCompilationCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageProduceCompilationCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageProduceCompilationCacheResult]()), + } + types.commandSchemas["Page.addCompilationCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageAddCompilationCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageAddCompilationCacheResult]()), + } + types.commandSchemas["Page.clearCompilationCache"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageClearCompilationCacheParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageClearCompilationCacheResult]()), + } + types.commandSchemas["Page.setSPCTransactionMode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetSPCTransactionModeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetSPCTransactionModeResult]()), + } + types.commandSchemas["Page.setRPHRegistrationMode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetRPHRegistrationModeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetRPHRegistrationModeResult]()), + } + types.commandSchemas["Page.generateTestReport"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGenerateTestReportParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGenerateTestReportResult]()), + } + types.commandSchemas["Page.waitForDebugger"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageWaitForDebuggerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageWaitForDebuggerResult]()), + } + types.commandSchemas["Page.setInterceptFileChooserDialog"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetInterceptFileChooserDialogParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetInterceptFileChooserDialogResult]()), + } + types.commandSchemas["Page.setPrerenderingAllowed"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageSetPrerenderingAllowedParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageSetPrerenderingAllowedResult]()), + } + types.commandSchemas["Page.getAnnotatedPageContent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PageGetAnnotatedPageContentParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PageGetAnnotatedPageContentResult]()), + } + types.commandSchemas["Performance.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PerformanceDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PerformanceDisableResult]()), + } + types.commandSchemas["Performance.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PerformanceEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PerformanceEnableResult]()), + } + types.commandSchemas["Performance.setTimeDomain"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PerformanceSetTimeDomainParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PerformanceSetTimeDomainResult]()), + } + types.commandSchemas["Performance.getMetrics"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PerformanceGetMetricsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PerformanceGetMetricsResult]()), + } + types.commandSchemas["PerformanceTimeline.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PerformanceTimelineEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PerformanceTimelineEnableResult]()), + } + types.commandSchemas["Preload.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PreloadEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PreloadEnableResult]()), + } + types.commandSchemas["Preload.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[PreloadDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[PreloadDisableResult]()), + } + types.commandSchemas["Profiler.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerDisableResult]()), + } + types.commandSchemas["Profiler.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerEnableResult]()), + } + types.commandSchemas["Profiler.getBestEffortCoverage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerGetBestEffortCoverageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerGetBestEffortCoverageResult]()), + } + types.commandSchemas["Profiler.setSamplingInterval"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerSetSamplingIntervalParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerSetSamplingIntervalResult]()), + } + types.commandSchemas["Profiler.start"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerStartParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStartResult]()), + } + types.commandSchemas["Profiler.startPreciseCoverage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerStartPreciseCoverageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStartPreciseCoverageResult]()), + } + types.commandSchemas["Profiler.stop"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerStopParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStopResult]()), + } + types.commandSchemas["Profiler.stopPreciseCoverage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerStopPreciseCoverageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerStopPreciseCoverageResult]()), + } + types.commandSchemas["Profiler.takePreciseCoverage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ProfilerTakePreciseCoverageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ProfilerTakePreciseCoverageResult]()), + } + types.commandSchemas["Runtime.awaitPromise"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeAwaitPromiseParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeAwaitPromiseResult]()), + } + types.commandSchemas["Runtime.callFunctionOn"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeCallFunctionOnParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeCallFunctionOnResult]()), + } + types.commandSchemas["Runtime.compileScript"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeCompileScriptParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeCompileScriptResult]()), + } + types.commandSchemas["Runtime.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeDisableResult]()), + } + types.commandSchemas["Runtime.discardConsoleEntries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeDiscardConsoleEntriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeDiscardConsoleEntriesResult]()), + } + types.commandSchemas["Runtime.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeEnableResult]()), + } + types.commandSchemas["Runtime.evaluate"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeEvaluateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeEvaluateResult]()), + } + types.commandSchemas["Runtime.getIsolateId"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeGetIsolateIDParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetIsolateIDResult]()), + } + types.commandSchemas["Runtime.getHeapUsage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeGetHeapUsageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetHeapUsageResult]()), + } + types.commandSchemas["Runtime.getProperties"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeGetPropertiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetPropertiesResult]()), + } + types.commandSchemas["Runtime.globalLexicalScopeNames"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeGlobalLexicalScopeNamesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGlobalLexicalScopeNamesResult]()), + } + types.commandSchemas["Runtime.queryObjects"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeQueryObjectsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeQueryObjectsResult]()), + } + types.commandSchemas["Runtime.releaseObject"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeReleaseObjectParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeReleaseObjectResult]()), + } + types.commandSchemas["Runtime.releaseObjectGroup"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeReleaseObjectGroupParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeReleaseObjectGroupResult]()), + } + types.commandSchemas["Runtime.runIfWaitingForDebugger"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeRunIfWaitingForDebuggerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRunIfWaitingForDebuggerResult]()), + } + types.commandSchemas["Runtime.runScript"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeRunScriptParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRunScriptResult]()), + } + types.commandSchemas["Runtime.setAsyncCallStackDepth"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeSetAsyncCallStackDepthParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetAsyncCallStackDepthResult]()), + } + types.commandSchemas["Runtime.setCustomObjectFormatterEnabled"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeSetCustomObjectFormatterEnabledParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetCustomObjectFormatterEnabledResult]()), + } + types.commandSchemas["Runtime.setMaxCallStackSizeToCapture"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeSetMaxCallStackSizeToCaptureParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeSetMaxCallStackSizeToCaptureResult]()), + } + types.commandSchemas["Runtime.terminateExecution"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeTerminateExecutionParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeTerminateExecutionResult]()), + } + types.commandSchemas["Runtime.addBinding"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeAddBindingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeAddBindingResult]()), + } + types.commandSchemas["Runtime.removeBinding"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeRemoveBindingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeRemoveBindingResult]()), + } + types.commandSchemas["Runtime.getExceptionDetails"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[RuntimeGetExceptionDetailsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[RuntimeGetExceptionDetailsResult]()), + } + types.commandSchemas["Schema.getDomains"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SchemaGetDomainsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SchemaGetDomainsResult]()), + } + types.commandSchemas["Security.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SecurityDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SecurityDisableResult]()), + } + types.commandSchemas["Security.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SecurityEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SecurityEnableResult]()), + } + types.commandSchemas["Security.setIgnoreCertificateErrors"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SecuritySetIgnoreCertificateErrorsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SecuritySetIgnoreCertificateErrorsResult]()), + } + types.commandSchemas["Security.handleCertificateError"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SecurityHandleCertificateErrorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SecurityHandleCertificateErrorResult]()), + } + types.commandSchemas["Security.setOverrideCertificateErrors"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SecuritySetOverrideCertificateErrorsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SecuritySetOverrideCertificateErrorsResult]()), + } + types.commandSchemas["ServiceWorker.deliverPushMessage"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerDeliverPushMessageParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDeliverPushMessageResult]()), + } + types.commandSchemas["ServiceWorker.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDisableResult]()), + } + types.commandSchemas["ServiceWorker.dispatchSyncEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerDispatchSyncEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDispatchSyncEventResult]()), + } + types.commandSchemas["ServiceWorker.dispatchPeriodicSyncEvent"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerDispatchPeriodicSyncEventParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerDispatchPeriodicSyncEventResult]()), + } + types.commandSchemas["ServiceWorker.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerEnableResult]()), + } + types.commandSchemas["ServiceWorker.setForceUpdateOnPageLoad"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerSetForceUpdateOnPageLoadParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerSetForceUpdateOnPageLoadResult]()), + } + types.commandSchemas["ServiceWorker.skipWaiting"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerSkipWaitingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerSkipWaitingResult]()), + } + types.commandSchemas["ServiceWorker.startWorker"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerStartWorkerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStartWorkerResult]()), + } + types.commandSchemas["ServiceWorker.stopAllWorkers"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerStopAllWorkersParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStopAllWorkersResult]()), + } + types.commandSchemas["ServiceWorker.stopWorker"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerStopWorkerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerStopWorkerResult]()), + } + types.commandSchemas["ServiceWorker.unregister"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerUnregisterParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerUnregisterResult]()), + } + types.commandSchemas["ServiceWorker.updateRegistration"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[ServiceWorkerUpdateRegistrationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerUpdateRegistrationResult]()), + } + types.commandSchemas["SmartCardEmulation.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEnableResult]()), + } + types.commandSchemas["SmartCardEmulation.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationDisableResult]()), + } + types.commandSchemas["SmartCardEmulation.reportEstablishContextResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportEstablishContextResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportEstablishContextResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportReleaseContextResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportReleaseContextResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportReleaseContextResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportListReadersResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportListReadersResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportListReadersResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportGetStatusChangeResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportGetStatusChangeResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportGetStatusChangeResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportBeginTransactionResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportBeginTransactionResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportBeginTransactionResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportPlainResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportPlainResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportPlainResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportConnectResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportConnectResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportConnectResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportDataResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportDataResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportDataResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportStatusResult"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportStatusResultParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportStatusResultResult]()), + } + types.commandSchemas["SmartCardEmulation.reportError"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SmartCardEmulationReportErrorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReportErrorResult]()), + } + types.commandSchemas["Storage.getStorageKeyForFrame"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetStorageKeyForFrameParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetStorageKeyForFrameResult]()), + } + types.commandSchemas["Storage.getStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetStorageKeyResult]()), + } + types.commandSchemas["Storage.clearDataForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageClearDataForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageClearDataForOriginResult]()), + } + types.commandSchemas["Storage.clearDataForStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageClearDataForStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageClearDataForStorageKeyResult]()), + } + types.commandSchemas["Storage.getCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetCookiesResult]()), + } + types.commandSchemas["Storage.setCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetCookiesResult]()), + } + types.commandSchemas["Storage.clearCookies"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageClearCookiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageClearCookiesResult]()), + } + types.commandSchemas["Storage.getUsageAndQuota"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetUsageAndQuotaParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetUsageAndQuotaResult]()), + } + types.commandSchemas["Storage.overrideQuotaForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageOverrideQuotaForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageOverrideQuotaForOriginResult]()), + } + types.commandSchemas["Storage.trackCacheStorageForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageTrackCacheStorageForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackCacheStorageForOriginResult]()), + } + types.commandSchemas["Storage.trackCacheStorageForStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageTrackCacheStorageForStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackCacheStorageForStorageKeyResult]()), + } + types.commandSchemas["Storage.trackIndexedDBForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageTrackIndexedDBForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackIndexedDBForOriginResult]()), + } + types.commandSchemas["Storage.trackIndexedDBForStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageTrackIndexedDBForStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageTrackIndexedDBForStorageKeyResult]()), + } + types.commandSchemas["Storage.untrackCacheStorageForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageUntrackCacheStorageForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackCacheStorageForOriginResult]()), + } + types.commandSchemas["Storage.untrackCacheStorageForStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageUntrackCacheStorageForStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackCacheStorageForStorageKeyResult]()), + } + types.commandSchemas["Storage.untrackIndexedDBForOrigin"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageUntrackIndexedDBForOriginParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackIndexedDBForOriginResult]()), + } + types.commandSchemas["Storage.untrackIndexedDBForStorageKey"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageUntrackIndexedDBForStorageKeyParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageUntrackIndexedDBForStorageKeyResult]()), + } + types.commandSchemas["Storage.getTrustTokens"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetTrustTokensParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetTrustTokensResult]()), + } + types.commandSchemas["Storage.clearTrustTokens"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageClearTrustTokensParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageClearTrustTokensResult]()), + } + types.commandSchemas["Storage.getInterestGroupDetails"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetInterestGroupDetailsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetInterestGroupDetailsResult]()), + } + types.commandSchemas["Storage.setInterestGroupTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetInterestGroupTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetInterestGroupTrackingResult]()), + } + types.commandSchemas["Storage.setInterestGroupAuctionTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetInterestGroupAuctionTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetInterestGroupAuctionTrackingResult]()), + } + types.commandSchemas["Storage.getSharedStorageMetadata"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetSharedStorageMetadataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetSharedStorageMetadataResult]()), + } + types.commandSchemas["Storage.getSharedStorageEntries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetSharedStorageEntriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetSharedStorageEntriesResult]()), + } + types.commandSchemas["Storage.setSharedStorageEntry"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetSharedStorageEntryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetSharedStorageEntryResult]()), + } + types.commandSchemas["Storage.deleteSharedStorageEntry"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageDeleteSharedStorageEntryParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageDeleteSharedStorageEntryResult]()), + } + types.commandSchemas["Storage.clearSharedStorageEntries"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageClearSharedStorageEntriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageClearSharedStorageEntriesResult]()), + } + types.commandSchemas["Storage.resetSharedStorageBudget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageResetSharedStorageBudgetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageResetSharedStorageBudgetResult]()), + } + types.commandSchemas["Storage.setSharedStorageTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetSharedStorageTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetSharedStorageTrackingResult]()), + } + types.commandSchemas["Storage.setStorageBucketTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetStorageBucketTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetStorageBucketTrackingResult]()), + } + types.commandSchemas["Storage.deleteStorageBucket"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageDeleteStorageBucketParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageDeleteStorageBucketResult]()), + } + types.commandSchemas["Storage.runBounceTrackingMitigations"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageRunBounceTrackingMitigationsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageRunBounceTrackingMitigationsResult]()), + } + types.commandSchemas["Storage.setAttributionReportingLocalTestingMode"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetAttributionReportingLocalTestingModeParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetAttributionReportingLocalTestingModeResult]()), + } + types.commandSchemas["Storage.setAttributionReportingTracking"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetAttributionReportingTrackingParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetAttributionReportingTrackingResult]()), + } + types.commandSchemas["Storage.sendPendingAttributionReports"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSendPendingAttributionReportsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSendPendingAttributionReportsResult]()), + } + types.commandSchemas["Storage.getRelatedWebsiteSets"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetRelatedWebsiteSetsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetRelatedWebsiteSetsResult]()), + } + types.commandSchemas["Storage.getAffectedUrlsForThirdPartyCookieMetadata"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageGetAffectedUrlsForThirdPartyCookieMetadataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageGetAffectedUrlsForThirdPartyCookieMetadataResult]()), + } + types.commandSchemas["Storage.setProtectedAudienceKAnonymity"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[StorageSetProtectedAudienceKAnonymityParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[StorageSetProtectedAudienceKAnonymityResult]()), + } + types.commandSchemas["SystemInfo.getInfo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SystemInfoGetInfoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetInfoResult]()), + } + types.commandSchemas["SystemInfo.getFeatureState"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SystemInfoGetFeatureStateParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetFeatureStateResult]()), + } + types.commandSchemas["SystemInfo.getProcessInfo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[SystemInfoGetProcessInfoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[SystemInfoGetProcessInfoResult]()), + } + types.commandSchemas["Target.activateTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetActivateTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetActivateTargetResult]()), + } + types.commandSchemas["Target.attachToTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetAttachToTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachToTargetResult]()), + } + types.commandSchemas["Target.attachToBrowserTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetAttachToBrowserTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachToBrowserTargetResult]()), + } + types.commandSchemas["Target.closeTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetCloseTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetCloseTargetResult]()), + } + types.commandSchemas["Target.exposeDevToolsProtocol"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetExposeDevToolsProtocolParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetExposeDevToolsProtocolResult]()), + } + types.commandSchemas["Target.createBrowserContext"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetCreateBrowserContextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetCreateBrowserContextResult]()), + } + types.commandSchemas["Target.getBrowserContexts"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetGetBrowserContextsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetGetBrowserContextsResult]()), + } + types.commandSchemas["Target.createTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetCreateTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetCreateTargetResult]()), + } + types.commandSchemas["Target.detachFromTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetDetachFromTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetDetachFromTargetResult]()), + } + types.commandSchemas["Target.disposeBrowserContext"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetDisposeBrowserContextParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetDisposeBrowserContextResult]()), + } + types.commandSchemas["Target.getTargetInfo"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetGetTargetInfoParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetGetTargetInfoResult]()), + } + types.commandSchemas["Target.getTargets"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetGetTargetsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetGetTargetsResult]()), + } + types.commandSchemas["Target.sendMessageToTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetSendMessageToTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetSendMessageToTargetResult]()), + } + types.commandSchemas["Target.setAutoAttach"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetSetAutoAttachParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetSetAutoAttachResult]()), + } + types.commandSchemas["Target.autoAttachRelated"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetAutoAttachRelatedParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetAutoAttachRelatedResult]()), + } + types.commandSchemas["Target.setDiscoverTargets"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetSetDiscoverTargetsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetSetDiscoverTargetsResult]()), + } + types.commandSchemas["Target.setRemoteLocations"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetSetRemoteLocationsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetSetRemoteLocationsResult]()), + } + types.commandSchemas["Target.getDevToolsTarget"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetGetDevToolsTargetParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetGetDevToolsTargetResult]()), + } + types.commandSchemas["Target.openDevTools"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TargetOpenDevToolsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TargetOpenDevToolsResult]()), + } + types.commandSchemas["Tethering.bind"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TetheringBindParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TetheringBindResult]()), + } + types.commandSchemas["Tethering.unbind"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TetheringUnbindParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TetheringUnbindResult]()), + } + types.commandSchemas["Tracing.end"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingEndParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingEndResult]()), + } + types.commandSchemas["Tracing.getCategories"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingGetCategoriesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingGetCategoriesResult]()), + } + types.commandSchemas["Tracing.getTrackEventDescriptor"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingGetTrackEventDescriptorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingGetTrackEventDescriptorResult]()), + } + types.commandSchemas["Tracing.recordClockSyncMarker"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingRecordClockSyncMarkerParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingRecordClockSyncMarkerResult]()), + } + types.commandSchemas["Tracing.requestMemoryDump"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingRequestMemoryDumpParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingRequestMemoryDumpResult]()), + } + types.commandSchemas["Tracing.start"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[TracingStartParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[TracingStartResult]()), + } + types.commandSchemas["WebAudio.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAudioEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAudioEnableResult]()), + } + types.commandSchemas["WebAudio.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAudioDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAudioDisableResult]()), + } + types.commandSchemas["WebAudio.getRealtimeData"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAudioGetRealtimeDataParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAudioGetRealtimeDataResult]()), + } + types.commandSchemas["WebAuthn.enable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnEnableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnEnableResult]()), + } + types.commandSchemas["WebAuthn.disable"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnDisableParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnDisableResult]()), + } + types.commandSchemas["WebAuthn.addVirtualAuthenticator"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnAddVirtualAuthenticatorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnAddVirtualAuthenticatorResult]()), + } + types.commandSchemas["WebAuthn.setResponseOverrideBits"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnSetResponseOverrideBitsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetResponseOverrideBitsResult]()), + } + types.commandSchemas["WebAuthn.removeVirtualAuthenticator"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnRemoveVirtualAuthenticatorParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnRemoveVirtualAuthenticatorResult]()), + } + types.commandSchemas["WebAuthn.addCredential"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnAddCredentialParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnAddCredentialResult]()), + } + types.commandSchemas["WebAuthn.getCredential"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnGetCredentialParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnGetCredentialResult]()), + } + types.commandSchemas["WebAuthn.getCredentials"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnGetCredentialsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnGetCredentialsResult]()), + } + types.commandSchemas["WebAuthn.removeCredential"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnRemoveCredentialParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnRemoveCredentialResult]()), + } + types.commandSchemas["WebAuthn.clearCredentials"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnClearCredentialsParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnClearCredentialsResult]()), + } + types.commandSchemas["WebAuthn.setUserVerified"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnSetUserVerifiedParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetUserVerifiedResult]()), + } + types.commandSchemas["WebAuthn.setAutomaticPresenceSimulation"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnSetAutomaticPresenceSimulationParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetAutomaticPresenceSimulationResult]()), + } + types.commandSchemas["WebAuthn.setCredentialProperties"] = CDPCommandSchema{ + Params: abxjsonschema.SchemaFor[WebAuthnSetCredentialPropertiesParams](), + Result: nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnSetCredentialPropertiesResult]()), + } + types.eventSchemas["Accessibility.loadComplete"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityLoadCompleteEvent]()) + types.eventSchemas["Accessibility.nodesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[AccessibilityNodesUpdatedEvent]()) + types.eventSchemas["Animation.animationCanceled"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationCanceledEvent]()) + types.eventSchemas["Animation.animationCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationCreatedEvent]()) + types.eventSchemas["Animation.animationStarted"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationStartedEvent]()) + types.eventSchemas["Animation.animationUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[AnimationAnimationUpdatedEvent]()) + types.eventSchemas["Audits.issueAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[AuditsIssueAddedEvent]()) + types.eventSchemas["Autofill.addressFormFilled"] = nativeResultSchema(abxjsonschema.SchemaFor[AutofillAddressFormFilledEvent]()) + types.eventSchemas["BackgroundService.recordingStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceRecordingStateChangedEvent]()) + types.eventSchemas["BackgroundService.backgroundServiceEventReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BackgroundServiceBackgroundServiceEventReceivedEvent]()) + types.eventSchemas["BluetoothEmulation.gattOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationGattOperationReceivedEvent]()) + types.eventSchemas["BluetoothEmulation.characteristicOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationCharacteristicOperationReceivedEvent]()) + types.eventSchemas["BluetoothEmulation.descriptorOperationReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[BluetoothEmulationDescriptorOperationReceivedEvent]()) + types.eventSchemas["Browser.downloadWillBegin"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserDownloadWillBeginEvent]()) + types.eventSchemas["Browser.downloadProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[BrowserDownloadProgressEvent]()) + types.eventSchemas["CSS.fontsUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSFontsUpdatedEvent]()) + types.eventSchemas["CSS.mediaQueryResultChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSMediaQueryResultChangedEvent]()) + types.eventSchemas["CSS.styleSheetAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetAddedEvent]()) + types.eventSchemas["CSS.styleSheetChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetChangedEvent]()) + types.eventSchemas["CSS.styleSheetRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSStyleSheetRemovedEvent]()) + types.eventSchemas["CSS.computedStyleUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CSSComputedStyleUpdatedEvent]()) + types.eventSchemas["Cast.sinksUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CastSinksUpdatedEvent]()) + types.eventSchemas["Cast.issueUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[CastIssueUpdatedEvent]()) + types.eventSchemas["Console.messageAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[ConsoleMessageAddedEvent]()) + types.eventSchemas["DOM.attributeModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAttributeModifiedEvent]()) + types.eventSchemas["DOM.adoptedStyleSheetsModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAdoptedStyleSheetsModifiedEvent]()) + types.eventSchemas["DOM.attributeRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAttributeRemovedEvent]()) + types.eventSchemas["DOM.characterDataModified"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMCharacterDataModifiedEvent]()) + types.eventSchemas["DOM.childNodeCountUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeCountUpdatedEvent]()) + types.eventSchemas["DOM.childNodeInserted"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeInsertedEvent]()) + types.eventSchemas["DOM.childNodeRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMChildNodeRemovedEvent]()) + types.eventSchemas["DOM.distributedNodesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDistributedNodesUpdatedEvent]()) + types.eventSchemas["DOM.documentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMDocumentUpdatedEvent]()) + types.eventSchemas["DOM.inlineStyleInvalidated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMInlineStyleInvalidatedEvent]()) + types.eventSchemas["DOM.pseudoElementAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPseudoElementAddedEvent]()) + types.eventSchemas["DOM.topLayerElementsUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMTopLayerElementsUpdatedEvent]()) + types.eventSchemas["DOM.scrollableFlagUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMScrollableFlagUpdatedEvent]()) + types.eventSchemas["DOM.adRelatedStateUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAdRelatedStateUpdatedEvent]()) + types.eventSchemas["DOM.affectedByStartingStylesFlagUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMAffectedByStartingStylesFlagUpdatedEvent]()) + types.eventSchemas["DOM.pseudoElementRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMPseudoElementRemovedEvent]()) + types.eventSchemas["DOM.setChildNodes"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMSetChildNodesEvent]()) + types.eventSchemas["DOM.shadowRootPopped"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMShadowRootPoppedEvent]()) + types.eventSchemas["DOM.shadowRootPushed"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMShadowRootPushedEvent]()) + types.eventSchemas["DOMStorage.domStorageItemAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemAddedEvent]()) + types.eventSchemas["DOMStorage.domStorageItemRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemRemovedEvent]()) + types.eventSchemas["DOMStorage.domStorageItemUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemUpdatedEvent]()) + types.eventSchemas["DOMStorage.domStorageItemsCleared"] = nativeResultSchema(abxjsonschema.SchemaFor[DOMStorageDOMStorageItemsClearedEvent]()) + types.eventSchemas["Debugger.breakpointResolved"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerBreakpointResolvedEvent]()) + types.eventSchemas["Debugger.paused"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerPausedEvent]()) + types.eventSchemas["Debugger.resumed"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerResumedEvent]()) + types.eventSchemas["Debugger.scriptFailedToParse"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerScriptFailedToParseEvent]()) + types.eventSchemas["Debugger.scriptParsed"] = nativeResultSchema(abxjsonschema.SchemaFor[DebuggerScriptParsedEvent]()) + types.eventSchemas["DeviceAccess.deviceRequestPrompted"] = nativeResultSchema(abxjsonschema.SchemaFor[DeviceAccessDeviceRequestPromptedEvent]()) + types.eventSchemas["Emulation.virtualTimeBudgetExpired"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationVirtualTimeBudgetExpiredEvent]()) + types.eventSchemas["Emulation.screenOrientationLockChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[EmulationScreenOrientationLockChangedEvent]()) + types.eventSchemas["FedCm.dialogShown"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDialogShownEvent]()) + types.eventSchemas["FedCm.dialogClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[FedCmDialogClosedEvent]()) + types.eventSchemas["Fetch.requestPaused"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchRequestPausedEvent]()) + types.eventSchemas["Fetch.authRequired"] = nativeResultSchema(abxjsonschema.SchemaFor[FetchAuthRequiredEvent]()) + types.eventSchemas["HeapProfiler.addHeapSnapshotChunk"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerAddHeapSnapshotChunkEvent]()) + types.eventSchemas["HeapProfiler.heapStatsUpdate"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerHeapStatsUpdateEvent]()) + types.eventSchemas["HeapProfiler.lastSeenObjectId"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerLastSeenObjectIDEvent]()) + types.eventSchemas["HeapProfiler.reportHeapSnapshotProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerReportHeapSnapshotProgressEvent]()) + types.eventSchemas["HeapProfiler.resetProfiles"] = nativeResultSchema(abxjsonschema.SchemaFor[HeapProfilerResetProfilesEvent]()) + types.eventSchemas["Input.dragIntercepted"] = nativeResultSchema(abxjsonschema.SchemaFor[InputDragInterceptedEvent]()) + types.eventSchemas["Inspector.detached"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorDetachedEvent]()) + types.eventSchemas["Inspector.targetCrashed"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorTargetCrashedEvent]()) + types.eventSchemas["Inspector.targetReloadedAfterCrash"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorTargetReloadedAfterCrashEvent]()) + types.eventSchemas["Inspector.workerScriptLoaded"] = nativeResultSchema(abxjsonschema.SchemaFor[InspectorWorkerScriptLoadedEvent]()) + types.eventSchemas["LayerTree.layerPainted"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLayerPaintedEvent]()) + types.eventSchemas["LayerTree.layerTreeDidChange"] = nativeResultSchema(abxjsonschema.SchemaFor[LayerTreeLayerTreeDidChangeEvent]()) + types.eventSchemas["Log.entryAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[LogEntryAddedEvent]()) + types.eventSchemas["Media.playerPropertiesChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerPropertiesChangedEvent]()) + types.eventSchemas["Media.playerEventsAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerEventsAddedEvent]()) + types.eventSchemas["Media.playerMessagesLogged"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerMessagesLoggedEvent]()) + types.eventSchemas["Media.playerErrorsRaised"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerErrorsRaisedEvent]()) + types.eventSchemas["Media.playerCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[MediaPlayerCreatedEvent]()) + types.eventSchemas["Network.dataReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDataReceivedEvent]()) + types.eventSchemas["Network.eventSourceMessageReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkEventSourceMessageReceivedEvent]()) + types.eventSchemas["Network.loadingFailed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadingFailedEvent]()) + types.eventSchemas["Network.loadingFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkLoadingFinishedEvent]()) + types.eventSchemas["Network.requestIntercepted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestInterceptedEvent]()) + types.eventSchemas["Network.requestServedFromCache"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestServedFromCacheEvent]()) + types.eventSchemas["Network.requestWillBeSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestWillBeSentEvent]()) + types.eventSchemas["Network.resourceChangedPriority"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResourceChangedPriorityEvent]()) + types.eventSchemas["Network.signedExchangeReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkSignedExchangeReceivedEvent]()) + types.eventSchemas["Network.responseReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedEvent]()) + types.eventSchemas["Network.webSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketClosedEvent]()) + types.eventSchemas["Network.webSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketCreatedEvent]()) + types.eventSchemas["Network.webSocketFrameError"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameErrorEvent]()) + types.eventSchemas["Network.webSocketFrameReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameReceivedEvent]()) + types.eventSchemas["Network.webSocketFrameSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketFrameSentEvent]()) + types.eventSchemas["Network.webSocketHandshakeResponseReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketHandshakeResponseReceivedEvent]()) + types.eventSchemas["Network.webSocketWillSendHandshakeRequest"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebSocketWillSendHandshakeRequestEvent]()) + types.eventSchemas["Network.webTransportCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportCreatedEvent]()) + types.eventSchemas["Network.webTransportConnectionEstablished"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportConnectionEstablishedEvent]()) + types.eventSchemas["Network.webTransportClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkWebTransportClosedEvent]()) + types.eventSchemas["Network.directTCPSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketCreatedEvent]()) + types.eventSchemas["Network.directTCPSocketOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketOpenedEvent]()) + types.eventSchemas["Network.directTCPSocketAborted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketAbortedEvent]()) + types.eventSchemas["Network.directTCPSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketClosedEvent]()) + types.eventSchemas["Network.directTCPSocketChunkSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketChunkSentEvent]()) + types.eventSchemas["Network.directTCPSocketChunkReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectTCPSocketChunkReceivedEvent]()) + types.eventSchemas["Network.directUDPSocketJoinedMulticastGroup"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketJoinedMulticastGroupEvent]()) + types.eventSchemas["Network.directUDPSocketLeftMulticastGroup"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketLeftMulticastGroupEvent]()) + types.eventSchemas["Network.directUDPSocketCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketCreatedEvent]()) + types.eventSchemas["Network.directUDPSocketOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketOpenedEvent]()) + types.eventSchemas["Network.directUDPSocketAborted"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketAbortedEvent]()) + types.eventSchemas["Network.directUDPSocketClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketClosedEvent]()) + types.eventSchemas["Network.directUDPSocketChunkSent"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketChunkSentEvent]()) + types.eventSchemas["Network.directUDPSocketChunkReceived"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDirectUDPSocketChunkReceivedEvent]()) + types.eventSchemas["Network.requestWillBeSentExtraInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkRequestWillBeSentExtraInfoEvent]()) + types.eventSchemas["Network.responseReceivedExtraInfo"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedExtraInfoEvent]()) + types.eventSchemas["Network.responseReceivedEarlyHints"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkResponseReceivedEarlyHintsEvent]()) + types.eventSchemas["Network.trustTokenOperationDone"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkTrustTokenOperationDoneEvent]()) + types.eventSchemas["Network.policyUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkPolicyUpdatedEvent]()) + types.eventSchemas["Network.reportingApiReportAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIReportAddedEvent]()) + types.eventSchemas["Network.reportingApiReportUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIReportUpdatedEvent]()) + types.eventSchemas["Network.reportingApiEndpointsChangedForOrigin"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkReportingAPIEndpointsChangedForOriginEvent]()) + types.eventSchemas["Network.deviceBoundSessionsAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeviceBoundSessionsAddedEvent]()) + types.eventSchemas["Network.deviceBoundSessionEventOccurred"] = nativeResultSchema(abxjsonschema.SchemaFor[NetworkDeviceBoundSessionEventOccurredEvent]()) + types.eventSchemas["Overlay.inspectNodeRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectNodeRequestedEvent]()) + types.eventSchemas["Overlay.nodeHighlightRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayNodeHighlightRequestedEvent]()) + types.eventSchemas["Overlay.screenshotRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayScreenshotRequestedEvent]()) + types.eventSchemas["Overlay.inspectPanelShowRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectPanelShowRequestedEvent]()) + types.eventSchemas["Overlay.inspectedElementWindowRestored"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectedElementWindowRestoredEvent]()) + types.eventSchemas["Overlay.inspectModeCanceled"] = nativeResultSchema(abxjsonschema.SchemaFor[OverlayInspectModeCanceledEvent]()) + types.eventSchemas["Page.domContentEventFired"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDOMContentEventFiredEvent]()) + types.eventSchemas["Page.fileChooserOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFileChooserOpenedEvent]()) + types.eventSchemas["Page.frameAttached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameAttachedEvent]()) + types.eventSchemas["Page.frameClearedScheduledNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameClearedScheduledNavigationEvent]()) + types.eventSchemas["Page.frameDetached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameDetachedEvent]()) + types.eventSchemas["Page.frameSubtreeWillBeDetached"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameSubtreeWillBeDetachedEvent]()) + types.eventSchemas["Page.frameNavigated"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameNavigatedEvent]()) + types.eventSchemas["Page.documentOpened"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDocumentOpenedEvent]()) + types.eventSchemas["Page.frameResized"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameResizedEvent]()) + types.eventSchemas["Page.frameStartedNavigating"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStartedNavigatingEvent]()) + types.eventSchemas["Page.frameRequestedNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameRequestedNavigationEvent]()) + types.eventSchemas["Page.frameScheduledNavigation"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameScheduledNavigationEvent]()) + types.eventSchemas["Page.frameStartedLoading"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStartedLoadingEvent]()) + types.eventSchemas["Page.frameStoppedLoading"] = nativeResultSchema(abxjsonschema.SchemaFor[PageFrameStoppedLoadingEvent]()) + types.eventSchemas["Page.downloadWillBegin"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDownloadWillBeginEvent]()) + types.eventSchemas["Page.downloadProgress"] = nativeResultSchema(abxjsonschema.SchemaFor[PageDownloadProgressEvent]()) + types.eventSchemas["Page.interstitialHidden"] = nativeResultSchema(abxjsonschema.SchemaFor[PageInterstitialHiddenEvent]()) + types.eventSchemas["Page.interstitialShown"] = nativeResultSchema(abxjsonschema.SchemaFor[PageInterstitialShownEvent]()) + types.eventSchemas["Page.javascriptDialogClosed"] = nativeResultSchema(abxjsonschema.SchemaFor[PageJavascriptDialogClosedEvent]()) + types.eventSchemas["Page.javascriptDialogOpening"] = nativeResultSchema(abxjsonschema.SchemaFor[PageJavascriptDialogOpeningEvent]()) + types.eventSchemas["Page.lifecycleEvent"] = nativeResultSchema(abxjsonschema.SchemaFor[PageLifecycleEventEvent]()) + types.eventSchemas["Page.backForwardCacheNotUsed"] = nativeResultSchema(abxjsonschema.SchemaFor[PageBackForwardCacheNotUsedEvent]()) + types.eventSchemas["Page.loadEventFired"] = nativeResultSchema(abxjsonschema.SchemaFor[PageLoadEventFiredEvent]()) + types.eventSchemas["Page.navigatedWithinDocument"] = nativeResultSchema(abxjsonschema.SchemaFor[PageNavigatedWithinDocumentEvent]()) + types.eventSchemas["Page.screencastFrame"] = nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastFrameEvent]()) + types.eventSchemas["Page.screencastVisibilityChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[PageScreencastVisibilityChangedEvent]()) + types.eventSchemas["Page.windowOpen"] = nativeResultSchema(abxjsonschema.SchemaFor[PageWindowOpenEvent]()) + types.eventSchemas["Page.compilationCacheProduced"] = nativeResultSchema(abxjsonschema.SchemaFor[PageCompilationCacheProducedEvent]()) + types.eventSchemas["Performance.metrics"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceMetricsEvent]()) + types.eventSchemas["PerformanceTimeline.timelineEventAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[PerformanceTimelineTimelineEventAddedEvent]()) + types.eventSchemas["Preload.ruleSetUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadRuleSetUpdatedEvent]()) + types.eventSchemas["Preload.ruleSetRemoved"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadRuleSetRemovedEvent]()) + types.eventSchemas["Preload.preloadEnabledStateUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPreloadEnabledStateUpdatedEvent]()) + types.eventSchemas["Preload.prefetchStatusUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPrefetchStatusUpdatedEvent]()) + types.eventSchemas["Preload.prerenderStatusUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPrerenderStatusUpdatedEvent]()) + types.eventSchemas["Preload.preloadingAttemptSourcesUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[PreloadPreloadingAttemptSourcesUpdatedEvent]()) + types.eventSchemas["Profiler.consoleProfileFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerConsoleProfileFinishedEvent]()) + types.eventSchemas["Profiler.consoleProfileStarted"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerConsoleProfileStartedEvent]()) + types.eventSchemas["Profiler.preciseCoverageDeltaUpdate"] = nativeResultSchema(abxjsonschema.SchemaFor[ProfilerPreciseCoverageDeltaUpdateEvent]()) + types.eventSchemas["Runtime.bindingCalled"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeBindingCalledEvent]()) + types.eventSchemas["Runtime.consoleAPICalled"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeConsoleAPICalledEvent]()) + types.eventSchemas["Runtime.exceptionRevoked"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExceptionRevokedEvent]()) + types.eventSchemas["Runtime.exceptionThrown"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExceptionThrownEvent]()) + types.eventSchemas["Runtime.executionContextCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextCreatedEvent]()) + types.eventSchemas["Runtime.executionContextDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextDestroyedEvent]()) + types.eventSchemas["Runtime.executionContextsCleared"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeExecutionContextsClearedEvent]()) + types.eventSchemas["Runtime.inspectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[RuntimeInspectRequestedEvent]()) + types.eventSchemas["Security.certificateError"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityCertificateErrorEvent]()) + types.eventSchemas["Security.visibleSecurityStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[SecurityVisibleSecurityStateChangedEvent]()) + types.eventSchemas["Security.securityStateChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[SecuritySecurityStateChangedEvent]()) + types.eventSchemas["ServiceWorker.workerErrorReported"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerErrorReportedEvent]()) + types.eventSchemas["ServiceWorker.workerRegistrationUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerRegistrationUpdatedEvent]()) + types.eventSchemas["ServiceWorker.workerVersionUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[ServiceWorkerWorkerVersionUpdatedEvent]()) + types.eventSchemas["SmartCardEmulation.establishContextRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEstablishContextRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.releaseContextRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationReleaseContextRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.listReadersRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationListReadersRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.getStatusChangeRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationGetStatusChangeRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.cancelRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationCancelRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.connectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationConnectRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.disconnectRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationDisconnectRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.transmitRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationTransmitRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.controlRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationControlRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.getAttribRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationGetAttribRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.setAttribRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationSetAttribRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.statusRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationStatusRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.beginTransactionRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationBeginTransactionRequestedEvent]()) + types.eventSchemas["SmartCardEmulation.endTransactionRequested"] = nativeResultSchema(abxjsonschema.SchemaFor[SmartCardEmulationEndTransactionRequestedEvent]()) + types.eventSchemas["Storage.cacheStorageContentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageCacheStorageContentUpdatedEvent]()) + types.eventSchemas["Storage.cacheStorageListUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageCacheStorageListUpdatedEvent]()) + types.eventSchemas["Storage.indexedDBContentUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageIndexedDBContentUpdatedEvent]()) + types.eventSchemas["Storage.indexedDBListUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageIndexedDBListUpdatedEvent]()) + types.eventSchemas["Storage.interestGroupAccessed"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAccessedEvent]()) + types.eventSchemas["Storage.interestGroupAuctionEventOccurred"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAuctionEventOccurredEvent]()) + types.eventSchemas["Storage.interestGroupAuctionNetworkRequestCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageInterestGroupAuctionNetworkRequestCreatedEvent]()) + types.eventSchemas["Storage.sharedStorageAccessed"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSharedStorageAccessedEvent]()) + types.eventSchemas["Storage.sharedStorageWorkletOperationExecutionFinished"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageSharedStorageWorkletOperationExecutionFinishedEvent]()) + types.eventSchemas["Storage.storageBucketCreatedOrUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageStorageBucketCreatedOrUpdatedEvent]()) + types.eventSchemas["Storage.storageBucketDeleted"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageStorageBucketDeletedEvent]()) + types.eventSchemas["Storage.attributionReportingSourceRegistered"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingSourceRegisteredEvent]()) + types.eventSchemas["Storage.attributionReportingTriggerRegistered"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingTriggerRegisteredEvent]()) + types.eventSchemas["Storage.attributionReportingReportSent"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingReportSentEvent]()) + types.eventSchemas["Storage.attributionReportingVerboseDebugReportSent"] = nativeResultSchema(abxjsonschema.SchemaFor[StorageAttributionReportingVerboseDebugReportSentEvent]()) + types.eventSchemas["Target.attachedToTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetAttachedToTargetEvent]()) + types.eventSchemas["Target.detachedFromTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetDetachedFromTargetEvent]()) + types.eventSchemas["Target.receivedMessageFromTarget"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetReceivedMessageFromTargetEvent]()) + types.eventSchemas["Target.targetCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetCreatedEvent]()) + types.eventSchemas["Target.targetDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetDestroyedEvent]()) + types.eventSchemas["Target.targetCrashed"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetCrashedEvent]()) + types.eventSchemas["Target.targetInfoChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[TargetTargetInfoChangedEvent]()) + types.eventSchemas["Tethering.accepted"] = nativeResultSchema(abxjsonschema.SchemaFor[TetheringAcceptedEvent]()) + types.eventSchemas["Tracing.bufferUsage"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingBufferUsageEvent]()) + types.eventSchemas["Tracing.dataCollected"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingDataCollectedEvent]()) + types.eventSchemas["Tracing.tracingComplete"] = nativeResultSchema(abxjsonschema.SchemaFor[TracingTracingCompleteEvent]()) + types.eventSchemas["WebAudio.contextCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextCreatedEvent]()) + types.eventSchemas["WebAudio.contextWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextWillBeDestroyedEvent]()) + types.eventSchemas["WebAudio.contextChanged"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioContextChangedEvent]()) + types.eventSchemas["WebAudio.audioListenerCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioListenerCreatedEvent]()) + types.eventSchemas["WebAudio.audioListenerWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioListenerWillBeDestroyedEvent]()) + types.eventSchemas["WebAudio.audioNodeCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioNodeCreatedEvent]()) + types.eventSchemas["WebAudio.audioNodeWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioNodeWillBeDestroyedEvent]()) + types.eventSchemas["WebAudio.audioParamCreated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioParamCreatedEvent]()) + types.eventSchemas["WebAudio.audioParamWillBeDestroyed"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioAudioParamWillBeDestroyedEvent]()) + types.eventSchemas["WebAudio.nodesConnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodesConnectedEvent]()) + types.eventSchemas["WebAudio.nodesDisconnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodesDisconnectedEvent]()) + types.eventSchemas["WebAudio.nodeParamConnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodeParamConnectedEvent]()) + types.eventSchemas["WebAudio.nodeParamDisconnected"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAudioNodeParamDisconnectedEvent]()) + types.eventSchemas["WebAuthn.credentialAdded"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialAddedEvent]()) + types.eventSchemas["WebAuthn.credentialDeleted"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialDeletedEvent]()) + types.eventSchemas["WebAuthn.credentialUpdated"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialUpdatedEvent]()) + types.eventSchemas["WebAuthn.credentialAsserted"] = nativeResultSchema(abxjsonschema.SchemaFor[WebAuthnCredentialAssertedEvent]()) } diff --git a/go/modcdp/injector/BBBrowserExtensionInjector.go b/go/modcdp/injector/BBExtensionInjector.go similarity index 70% rename from go/modcdp/injector/BBBrowserExtensionInjector.go rename to go/modcdp/injector/BBExtensionInjector.go index 905edbf1..fc603a67 100644 --- a/go/modcdp/injector/BBBrowserExtensionInjector.go +++ b/go/modcdp/injector/BBExtensionInjector.go @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/BBExtensionInjector.ts +// - ./python/modcdp/injector/BBExtensionInjector.py package injector import ( @@ -15,26 +19,26 @@ import ( const DefaultBrowserbaseBaseURL = "https://api.browserbase.com" -type BBBrowserExtensionInjector struct { +type BBExtensionInjector struct { ExtensionInjector - ExtensionID string ZipPath string CleanupPath string } -func NewBBBrowserExtensionInjector(options ExtensionInjectorConfig) BBBrowserExtensionInjector { - return BBBrowserExtensionInjector{ExtensionInjector: NewExtensionInjector(options)} +func NewBBExtensionInjector(config InjectorConfig) BBExtensionInjector { + config.InjectorMode = "bb" + return BBExtensionInjector{ExtensionInjector: NewExtensionInjector(config)} } -func (i *BBBrowserExtensionInjector) Prepare() error { - if i.Options.InjectorExtensionID != "" { - i.ExtensionID = i.Options.InjectorExtensionID +func (i *BBExtensionInjector) Prepare() error { + if i.Config.InjectorBBExtensionID != "" { + i.ExtensionID = i.Config.InjectorBBExtensionID return nil } if i.ExtensionID != "" { return nil } - extensionPath := i.Options.InjectorExtensionPath + extensionPath := i.Config.InjectorBBExtensionPath if extensionPath == "" { return nil } else if strings.HasSuffix(extensionPath, ".zip") { @@ -53,22 +57,20 @@ func (i *BBBrowserExtensionInjector) Prepare() error { return err } i.ExtensionID = extensionID + i.Config.InjectorBBExtensionID = extensionID return nil } -func (i *BBBrowserExtensionInjector) GetLauncherConfig() LaunchOptions { - if i.ExtensionID == "" { - return LaunchOptions{} +func (i *BBExtensionInjector) ConfigForLauncher() LauncherConfig { + config := i.ExtensionInjector.ConfigForLauncher() + if i.ExtensionID != "" { + config.LauncherBBExtensionID = i.ExtensionID } - return LaunchOptions{InjectorExtensionID: i.ExtensionID} + return config } -func (i *BBBrowserExtensionInjector) Inject() (*ExtensionInjectionResult, error) { - extensionID := i.Options.InjectorExtensionID - i.Options.InjectorExtensionID = "" - defer func() { i.Options.InjectorExtensionID = extensionID }() - - discovered, err := i.waitForReadyServiceWorker(i.Options.InjectorServiceWorkerReadyTimeoutMS, i.Options.InjectorTrustServiceWorkerTarget) +func (i *BBExtensionInjector) Inject() (*ExtensionInjectionResult, error) { + discovered, err := i.waitForReadyServiceWorker(i.Config.InjectorServiceWorkerReadyTimeoutMS, i.Config.InjectorTrustServiceWorkerTarget) if err != nil || discovered == nil { return discovered, err } @@ -76,7 +78,7 @@ func (i *BBBrowserExtensionInjector) Inject() (*ExtensionInjectionResult, error) return discovered, nil } -func (i *BBBrowserExtensionInjector) Close() error { +func (i *BBExtensionInjector) Close() error { if i.CleanupPath != "" { _ = os.RemoveAll(i.CleanupPath) i.CleanupPath = "" @@ -84,12 +86,12 @@ func (i *BBBrowserExtensionInjector) Close() error { return nil } -func (i *BBBrowserExtensionInjector) uploadExtension(zipPath string) (string, error) { - browserbaseAPIKey := firstNonEmptyString(i.Options.InjectorBrowserbaseAPIKey, os.Getenv("BROWSERBASE_API_KEY")) +func (i *BBExtensionInjector) uploadExtension(zipPath string) (string, error) { + browserbaseAPIKey := firstNonEmptyString(i.Config.InjectorBBAPIKey, os.Getenv("BROWSERBASE_API_KEY")) if browserbaseAPIKey == "" { - return "", fmt.Errorf("BBBrowserExtensionInjector requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key") + return "", fmt.Errorf("BBExtensionInjector requires BROWSERBASE_API_KEY or injector.injector_bb_api_key.") } - baseURL := firstNonEmptyString(i.Options.InjectorBrowserbaseBaseURL, os.Getenv("BROWSERBASE_BASE_URL"), DefaultBrowserbaseBaseURL) + baseURL := i.Config.InjectorBBBaseURL body := &bytes.Buffer{} writer := multipart.NewWriter(body) fileWriter, err := writer.CreateFormFile("file", filepath.Base(zipPath)) diff --git a/go/modcdp/injector/BBBrowserExtensionInjector_test.go b/go/modcdp/injector/BBExtensionInjector_test.go similarity index 51% rename from go/modcdp/injector/BBBrowserExtensionInjector_test.go rename to go/modcdp/injector/BBExtensionInjector_test.go index 2f295267..7df9644c 100644 --- a/go/modcdp/injector/BBBrowserExtensionInjector_test.go +++ b/go/modcdp/injector/BBExtensionInjector_test.go @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.BBExtensionInjector.ts +// - ./python/tests/test_BBExtensionInjector.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package injector_test import ( @@ -9,7 +15,7 @@ import ( "testing" ) -func TestBBBrowserExtensionInjectorUploadsRealExtensionAndLaunchesBrowserbaseBrowserWithItInstalled(t *testing.T) { +func TestUploadsTheRealExtensionAndLaunchesABrowserbaseBrowserWithItInstalled(t *testing.T) { if os.Getenv("BROWSERBASE_API_KEY") == "" { t.Fatal("BROWSERBASE_API_KEY is required for live Browserbase tests") } @@ -17,20 +23,19 @@ func TestBBBrowserExtensionInjectorUploadsRealExtensionAndLaunchesBrowserbaseBro if err != nil { t.Fatal(err) } - launchOptions := modcdp.LaunchOptions{ - Timeout: 120, + launchConfig := modcdp.LauncherConfig{ + LauncherMode: "bb", + LauncherBBTimeout: 120, } if region := os.Getenv("BROWSERBASE_REGION"); region != "" { - launchOptions.Region = region + launchConfig.LauncherBBRegion = region } - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "bb", - LauncherOptions: launchOptions, - }, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws"}, + cdp := modcdp.New(modcdp.Config{ + Launcher: launchConfig, + Upstream: modcdp.UpstreamTransportConfig{UpstreamMode: "ws"}, Injector: modcdp.InjectorConfig{ - InjectorMode: "inject", - InjectorExtensionPath: extensionPath, + InjectorMode: "bb", + InjectorBBExtensionPath: extensionPath, InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, InjectorTrustServiceWorkerTarget: true, }, @@ -43,8 +48,8 @@ func TestBBBrowserExtensionInjectorUploadsRealExtensionAndLaunchesBrowserbaseBro if cdp.ConnectTiming["injector_source"] != "bb" { t.Fatalf("injector_source = %v", cdp.ConnectTiming["injector_source"]) } - if cdp.ExtensionID == "" { - t.Fatal("expected ExtensionID") + if cdp.Injector.ExtensionID == "" { + t.Fatal("expected Injector.ExtensionID") } result, err := cdp.Mod.Evaluate(map[string]any{ "expression": "chrome.runtime.getURL('modcdp/service_worker.js')", diff --git a/go/modcdp/injector/BorrowedExtensionInjector.go b/go/modcdp/injector/BorrowedExtensionInjector.go deleted file mode 100644 index c6af6ec5..00000000 --- a/go/modcdp/injector/BorrowedExtensionInjector.go +++ /dev/null @@ -1,187 +0,0 @@ -package injector - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "sort" - "strings" - "time" -) - -type BorrowedExtensionInjector struct { - ExtensionInjector -} - -func NewBorrowedExtensionInjector(options ExtensionInjectorConfig) BorrowedExtensionInjector { - return BorrowedExtensionInjector{ExtensionInjector: NewExtensionInjector(options)} -} - -func (i *BorrowedExtensionInjector) Inject() (*ExtensionInjectionResult, error) { - deadline := time.Now().Add(time.Duration(i.Options.InjectorServiceWorkerReadyTimeoutMS) * time.Millisecond) - for { - borrowed, err := i.borrowVisibleServiceWorkers() - if err != nil || borrowed != nil { - return borrowed, err - } - if time.Now().After(deadline) { - return nil, nil - } - time.Sleep(time.Duration(i.Options.InjectorServiceWorkerPollIntervalMS) * time.Millisecond) - } -} - -func (i *BorrowedExtensionInjector) borrowVisibleServiceWorkers() (*ExtensionInjectionResult, error) { - targets, err := i.targetInfos() - if err != nil { - return nil, err - } - hasConfiguredMatcher := i.Options.InjectorExtensionID != "" || len(i.Options.InjectorServiceWorkerURLIncludes) > 0 || len(i.Options.InjectorServiceWorkerURLSuffixes) > 0 - candidates := []map[string]any{} - for _, target := range targets { - targetType, _ := target["type"].(string) - targetURL, _ := target["url"].(string) - if targetType != "service_worker" || !strings.HasPrefix(targetURL, "chrome-extension://") { - continue - } - if hasConfiguredMatcher && !i.serviceWorkerTargetMatches(target) { - continue - } - candidates = append(candidates, target) - } - var borrowed []*ExtensionInjectionResult - for _, target := range candidates { - bootstrapped, err := i.bootstrapTarget(target) - if err == nil && bootstrapped != nil { - bootstrapped.Source = "borrowed" - borrowed = append(borrowed, bootstrapped) - } - } - sort.SliceStable(borrowed, func(left, right int) bool { - if borrowed[left].HasDebugger != borrowed[right].HasDebugger { - return borrowed[left].HasDebugger - } - return borrowed[left].HasTabs && !borrowed[right].HasTabs - }) - if len(borrowed) == 0 { - return nil, nil - } - return borrowed[0], nil -} - -func (i *BorrowedExtensionInjector) bootstrapTarget(target map[string]any) (*ExtensionInjectionResult, error) { - targetID, _ := target["targetId"].(string) - targetURL, _ := target["url"].(string) - sessionID := i.ensureSessionIDForTarget(targetID, i.Options.InjectorServiceWorkerProbeTimeoutMS, true) - if sessionID == "" { - return nil, nil - } - _, _ = i.sendWithTimeout("Runtime.enable", map[string]any{}, sessionID, i.Options.InjectorCDPSendTimeoutMS) - bootstrap, err := modcdpServerBootstrapExpressionFromPath(i.Options.InjectorExtensionPath) - if err != nil { - return nil, err - } - probe, err := i.sendWithTimeout("Runtime.evaluate", map[string]any{ - "expression": fmt.Sprintf("(%s)()", bootstrap), - "awaitPromise": true, - "returnByValue": true, - }, sessionID, i.Options.InjectorCDPSendTimeoutMS) - if err != nil { - return nil, err - } - result, _ := probe["result"].(map[string]any) - value, _ := result["value"].(map[string]any) - if hasTabs, _ := value["has_tabs"].(bool); !hasTabs { - return nil, nil - } - if hasDebugger, _ := value["has_debugger"].(bool); !hasDebugger { - return nil, nil - } - ready, _ := value["ok"].(bool) - if ready && i.readyExpression() != modcdpReadyExpression { - readyProbe, err := i.sendWithTimeout("Runtime.evaluate", map[string]any{ - "expression": i.readyExpression(), - "returnByValue": true, - }, sessionID, i.Options.InjectorCDPSendTimeoutMS) - if err != nil { - return nil, err - } - readyResult, _ := readyProbe["result"].(map[string]any) - ready, _ = readyResult["value"].(bool) - } - if !ready { - return nil, nil - } - extensionID, _ := value["extension_id"].(string) - if extensionID == "" { - if match := extIDFromURL.FindStringSubmatch(targetURL); len(match) > 1 { - extensionID = match[1] - } - } - hasTabs, _ := value["has_tabs"].(bool) - hasDebugger, _ := value["has_debugger"].(bool) - return &ExtensionInjectionResult{ - Source: "borrowed", - ExtensionID: extensionID, - TargetID: targetID, - URL: targetURL, - SessionID: sessionID, - HasTabs: hasTabs, - HasDebugger: hasDebugger, - }, nil -} - -func modcdpServerBootstrapExpressionFromPath(extensionPath string) (string, error) { - serverPath, err := modcdpServerPathFromExtensionPath(extensionPath) - if err != nil { - return "", err - } - body, err := os.ReadFile(serverPath) - if err != nil { - return "", err - } - source := string(body) - start := strings.Index(source, "export function installModCDPServer") - end := strings.Index(source, "export const ModCDPServer") - if start < 0 || end < start { - return "", fmt.Errorf("could not find installModCDPServer in ModCDPServer.js") - } - installer := strings.Replace(source[start:end], "export function", "function", 1) - return fmt.Sprintf(`function() { -const __name = (fn) => fn; -%s -const ModCDP = installModCDPServer(globalThis); -return { - ok: Boolean(ModCDP?.__ModCDPServerVersion >= 1 && ModCDP?.handleCommand && ModCDP?.addCustomEvent), - extension_id: globalThis.chrome?.runtime?.id ?? null, - has_tabs: Boolean(globalThis.chrome?.tabs?.query), - has_debugger: Boolean(globalThis.chrome?.debugger?.sendCommand && globalThis.chrome?.debugger?.getTargets), -}; -}`, installer), nil -} - -func modcdpServerPathFromExtensionPath(extensionPath string) (string, error) { - candidates := []string{} - if extensionPath != "" { - candidates = append(candidates, filepath.Join(extensionPath, "ModCDPServer.js")) - candidates = append(candidates, filepath.Join(extensionPath, "js", "src", "server", "ModCDPServer.js")) - candidates = append(candidates, filepath.Join(filepath.Dir(extensionPath), "js", "src", "server", "ModCDPServer.js")) - } - if _, file, _, ok := runtime.Caller(0); ok { - for dir := filepath.Dir(file); ; dir = filepath.Dir(dir) { - candidates = append(candidates, filepath.Join(dir, "dist", "js", "src", "server", "ModCDPServer.js")) - candidates = append(candidates, filepath.Join(dir, "dist", "extension", "js", "src", "server", "ModCDPServer.js")) - candidates = append(candidates, filepath.Join(dir, "dist", "extension", "ModCDPServer.js")) - if parent := filepath.Dir(dir); parent == dir { - break - } - } - } - for _, candidate := range candidates { - if _, err := os.Stat(candidate); err == nil { - return candidate, nil - } - } - return "", fmt.Errorf("unable to locate ModCDPServer.js; checked: %s", strings.Join(candidates, ", ")) -} diff --git a/go/modcdp/injector/BorrowedExtensionInjector_test.go b/go/modcdp/injector/BorrowedExtensionInjector_test.go deleted file mode 100644 index 9441946a..00000000 --- a/go/modcdp/injector/BorrowedExtensionInjector_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package injector_test - -import ( - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/injector" - "path/filepath" - "testing" -) - -func TestBorrowedExtensionInjectorBootstrapsModCDPInsideLiveExtensionServiceWorker(t *testing.T) { - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - headless := true - owner := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", LauncherOptions: modcdp.LaunchOptions{Headless: &headless}}, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws"}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - }) - defer owner.Close() - - if err := owner.Connect(); err != nil { - t.Fatal(err) - } - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "remote"}, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: owner.CDPURL}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "borrow", - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["injector_source"] != "borrowed" { - t.Fatalf("injector_source = %v", cdp.ConnectTiming["injector_source"]) - } - if cdp.ExtensionID != DefaultModCDPExtensionID { - t.Fatalf("ExtensionID = %q", cdp.ExtensionID) - } - result, err := cdp.Mod.Evaluate(map[string]any{ - "expression": "chrome.runtime.getURL('modcdp/service_worker.js')", - }) - if err != nil { - t.Fatal(err) - } - if result != "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js" { - t.Fatalf("Mod.evaluate = %#v", result) - } -} diff --git a/go/modcdp/injector/ExtensionsLoadUnpackedInjector.go b/go/modcdp/injector/CDPExtensionInjector.go similarity index 53% rename from go/modcdp/injector/ExtensionsLoadUnpackedInjector.go rename to go/modcdp/injector/CDPExtensionInjector.go index d13def9f..1d5c5cde 100644 --- a/go/modcdp/injector/ExtensionsLoadUnpackedInjector.go +++ b/go/modcdp/injector/CDPExtensionInjector.go @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/CDPExtensionInjector.ts +// - ./python/modcdp/injector/CDPExtensionInjector.py package injector import ( @@ -7,52 +11,50 @@ import ( "time" ) -type ExtensionsLoadUnpackedInjector struct { +type CDPExtensionInjector struct { ExtensionInjector UnpackedExtensionPath string CleanupPath string } -func NewExtensionsLoadUnpackedInjector(options ExtensionInjectorConfig) ExtensionsLoadUnpackedInjector { - return ExtensionsLoadUnpackedInjector{ExtensionInjector: NewExtensionInjector(options)} +func NewCDPExtensionInjector(config InjectorConfig) CDPExtensionInjector { + config.InjectorMode = "cdp" + return CDPExtensionInjector{ExtensionInjector: NewExtensionInjector(config)} } -func (i *ExtensionsLoadUnpackedInjector) Prepare() error { - extensionPath := i.Options.InjectorExtensionPath +func (i *CDPExtensionInjector) Prepare() error { + extensionPath := i.Config.InjectorCDPExtensionPath if i.UnpackedExtensionPath != "" { return nil } - unpackedPath, cleanupPath, err := prepareUnpackedExtension(extensionPath) + prepared, err := PrepareUnpackedExtension(extensionPath) if err != nil { return err } - i.UnpackedExtensionPath = unpackedPath - i.CleanupPath = cleanupPath + i.UnpackedExtensionPath = prepared.UnpackedExtensionPath + i.CleanupPath = prepared.CleanupPath return nil } -func (i *ExtensionsLoadUnpackedInjector) Inject() (*ExtensionInjectionResult, error) { +func (i *CDPExtensionInjector) Inject() (*ExtensionInjectionResult, error) { if i.UnpackedExtensionPath == "" { return nil, nil } - loadResult, err := i.sendWithTimeout("Extensions.loadUnpacked", map[string]any{"path": i.UnpackedExtensionPath}, "", i.Options.InjectorCDPSendTimeoutMS) + loadResult, err := i.sendWithTimeout("Extensions.loadUnpacked", map[string]any{"path": i.UnpackedExtensionPath}, "", i.Config.InjectorCDPSendTimeoutMS) if err != nil { if strings.Contains(err.Error(), "Method not available") || strings.Contains(err.Error(), "Method not found") || strings.Contains(err.Error(), "wasn't found") { - i.LastError = err return nil, nil } return nil, fmt.Errorf("Extensions.loadUnpacked failed for %s: %w", i.UnpackedExtensionPath, err) } extensionID, _ := loadResult["id"].(string) - if extensionID == "" { - extensionID, _ = loadResult["extensionId"].(string) - } if extensionID == "" { return nil, fmt.Errorf("Extensions.loadUnpacked returned no extension id") } - i.Options.InjectorExtensionID = extensionID + i.ExtensionID = extensionID + i.ServiceWorkerExtensionID = extensionID swURLPrefix := "chrome-extension://" + extensionID + "/" - deadline := time.Now().Add(time.Duration(i.Options.InjectorServiceWorkerReadyTimeoutMS) * time.Millisecond) + deadline := time.Now().Add(time.Duration(i.Config.InjectorServiceWorkerReadyTimeoutMS) * time.Millisecond) for time.Now().Before(deadline) { targets, err := i.targetInfos() if err != nil { @@ -64,22 +66,22 @@ func (i *ExtensionsLoadUnpackedInjector) Inject() (*ExtensionInjectionResult, er if targetType != "service_worker" || !strings.HasPrefix(targetURL, swURLPrefix) { continue } - probed, err := i.probeTarget(target, i.Options.InjectorServiceWorkerProbeTimeoutMS, true) + probed, err := i.probeTarget(target, i.Config.InjectorServiceWorkerProbeTimeoutMS, true) if err != nil { return nil, err } if probed != nil { - probed.Source = "extensions_load_unpacked" + probed.Source = "cdp" probed.ExtensionID = extensionID return probed, nil } } - time.Sleep(time.Duration(i.Options.InjectorServiceWorkerPollIntervalMS) * time.Millisecond) + time.Sleep(time.Duration(i.Config.InjectorServiceWorkerPollIntervalMS) * time.Millisecond) } return nil, fmt.Errorf("timed out waiting for service worker target for extension %s", extensionID) } -func (i *ExtensionsLoadUnpackedInjector) Close() error { +func (i *CDPExtensionInjector) Close() error { if i.CleanupPath != "" { _ = os.RemoveAll(i.CleanupPath) i.CleanupPath = "" diff --git a/go/modcdp/injector/CDPExtensionInjector_test.go b/go/modcdp/injector/CDPExtensionInjector_test.go new file mode 100644 index 00000000..e631687c --- /dev/null +++ b/go/modcdp/injector/CDPExtensionInjector_test.go @@ -0,0 +1,34 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.CDPExtensionInjector.ts +// - ./python/tests/test_CDPExtensionInjector.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package injector_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + . "github.com/browserbase/modcdp/go/modcdp/injector" +) + +func TestCDPExtensionInjectorPreparesTheDefaultPackagedExtensionZip(t *testing.T) { + injector := NewCDPExtensionInjector(InjectorConfig{}) + if err := injector.Prepare(); err != nil { + t.Fatal(err) + } + defer injector.Close() + + if injector.UnpackedExtensionPath == "" { + t.Fatal("expected UnpackedExtensionPath") + } + if !strings.Contains(injector.UnpackedExtensionPath, "modcdp-extension-") { + t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) + } + if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { + t.Fatalf("expected unpacked manifest: %v", err) + } +} diff --git a/go/modcdp/injector/CLIExtensionInjector.go b/go/modcdp/injector/CLIExtensionInjector.go new file mode 100644 index 00000000..5a93d9e0 --- /dev/null +++ b/go/modcdp/injector/CLIExtensionInjector.go @@ -0,0 +1,76 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/CLIExtensionInjector.ts +// - ./python/modcdp/injector/CLIExtensionInjector.py +package injector + +import ( + "os" +) + +type CLIExtensionInjector struct { + ExtensionInjector + UnpackedExtensionPath string + CleanupPath string +} + +func NewCLIExtensionInjector(config InjectorConfig) CLIExtensionInjector { + config.InjectorMode = "cli" + return CLIExtensionInjector{ExtensionInjector: NewExtensionInjector(config)} +} + +func (i *CLIExtensionInjector) Prepare() error { + extensionPath := i.Config.InjectorCLIExtensionPath + if i.UnpackedExtensionPath != "" { + return nil + } + prepared, err := PrepareUnpackedExtension(extensionPath) + if err != nil { + return err + } + i.UnpackedExtensionPath = prepared.UnpackedExtensionPath + i.CleanupPath = prepared.CleanupPath + _, err = i.resolveExtensionID() + return err +} + +func (i *CLIExtensionInjector) Inject() (*ExtensionInjectionResult, error) { + discovered, err := i.waitForReadyServiceWorker(i.Config.InjectorServiceWorkerReadyTimeoutMS, i.Config.InjectorTrustServiceWorkerTarget) + if err != nil || discovered == nil { + return discovered, err + } + discovered.Source = "cli" + return discovered, nil +} + +func (i *CLIExtensionInjector) Close() error { + if i.CleanupPath != "" { + _ = os.RemoveAll(i.CleanupPath) + i.CleanupPath = "" + } + return nil +} + +func (i *CLIExtensionInjector) resolveExtensionID() (string, error) { + if i.ExtensionID != "" { + return i.ExtensionID, nil + } + if i.Config.InjectorCLIExtensionID != "" { + i.ExtensionID = i.Config.InjectorCLIExtensionID + } else if i.UnpackedExtensionPath != "" { + extensionID, err := ExtensionIDFromManifestKey(i.UnpackedExtensionPath) + if err != nil { + return "", err + } + i.ExtensionID = extensionID + } + if i.ExtensionID != "" { + i.ServiceWorkerExtensionID = i.ExtensionID + i.Config.InjectorCLIExtensionID = i.ExtensionID + i.Config.InjectorServiceWorkerExtensionID = i.ExtensionID + } + if i.UnpackedExtensionPath != "" { + i.ExtraArgs = []string{"--load-extension=" + i.UnpackedExtensionPath} + } + return i.ExtensionID, nil +} diff --git a/go/modcdp/injector/CLIExtensionInjector_test.go b/go/modcdp/injector/CLIExtensionInjector_test.go new file mode 100644 index 00000000..cfdf3df1 --- /dev/null +++ b/go/modcdp/injector/CLIExtensionInjector_test.go @@ -0,0 +1,168 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.CLIExtensionInjector.ts +// - ./python/tests/test_CLIExtensionInjector.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package injector_test + +import ( + "archive/zip" + . "github.com/browserbase/modcdp/go/modcdp/injector" + "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/transport" + "os" + "path/filepath" + "strings" + "testing" +) + +const doesNotExistExtensionID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +func TestCLIExtensionInjectorRejectsZipEntriesOutsideExtractionDirectory(t *testing.T) { + tempDir := t.TempDir() + zipPath := filepath.Join(tempDir, "extension.zip") + file, err := os.Create(zipPath) + if err != nil { + t.Fatal(err) + } + writer := zip.NewWriter(file) + entry, err := writer.Create("../evil.txt") + if err != nil { + t.Fatal(err) + } + if _, err := entry.Write([]byte("evil")); err != nil { + t.Fatal(err) + } + if err := writer.Close(); err != nil { + t.Fatal(err) + } + if err := file.Close(); err != nil { + t.Fatal(err) + } + + injector := NewCLIExtensionInjector(InjectorConfig{InjectorCLIExtensionPath: zipPath}) + if err := injector.Prepare(); err == nil || !strings.Contains(err.Error(), "escapes extension extraction directory") { + t.Fatalf("Prepare error = %v", err) + } + if _, err := os.Stat(filepath.Join(tempDir, "evil.txt")); !os.IsNotExist(err) { + t.Fatalf("outside file was created: %v", err) + } +} + +func TestCLIExtensionInjectorPreparesAnUnpackedExtensionDirectoryForLoadExtension(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) + } + injector := NewCLIExtensionInjector(InjectorConfig{InjectorCLIExtensionPath: extensionPath}) + if err := injector.Prepare(); err != nil { + t.Fatal(err) + } + defer injector.Close() + + if injector.UnpackedExtensionPath == "" { + t.Fatal("expected UnpackedExtensionPath") + } + if injector.UnpackedExtensionPath == extensionPath { + t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) + } + if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { + t.Fatalf("expected unpacked manifest: %v", err) + } + if len(injector.ExtraArgs) != 1 || injector.ExtraArgs[0] != "--load-extension="+injector.UnpackedExtensionPath { + t.Fatalf("ExtraArgs = %#v", injector.ExtraArgs) + } + if injector.Config.InjectorServiceWorkerExtensionID != DefaultModCDPExtensionID { + t.Fatalf("InjectorServiceWorkerExtensionID = %q", injector.Config.InjectorServiceWorkerExtensionID) + } +} + +func TestCLIExtensionInjectorPreparesTheDefaultExtensionZipForLoadExtension(t *testing.T) { + injector := NewCLIExtensionInjector(InjectorConfig{}) + if err := injector.Prepare(); err != nil { + t.Fatal(err) + } + defer injector.Close() + + if injector.UnpackedExtensionPath == "" { + t.Fatal("expected UnpackedExtensionPath") + } + if !strings.Contains(injector.UnpackedExtensionPath, "modcdp-extension-") { + t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) + } + if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { + t.Fatalf("expected unpacked manifest: %v", err) + } + if len(injector.ExtraArgs) != 1 || injector.ExtraArgs[0] != "--load-extension="+injector.UnpackedExtensionPath { + t.Fatalf("ExtraArgs = %#v", injector.ExtraArgs) + } + if injector.Config.InjectorServiceWorkerExtensionID != DefaultModCDPExtensionID { + t.Fatalf("InjectorServiceWorkerExtensionID = %q", injector.Config.InjectorServiceWorkerExtensionID) + } +} + +func TestCLIExtensionInjectorReturnsNullWhenATrustedDoesNotExistExtensionIDIsAbsentInARealBrowser(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) + } + headless := true + injector := NewCLIExtensionInjector(InjectorConfig{ + InjectorCLIExtensionPath: extensionPath, + InjectorCLIExtensionID: doesNotExistExtensionID, + InjectorTrustServiceWorkerTarget: true, + InjectorServiceWorkerReadyTimeoutMS: 250, + InjectorServiceWorkerPollIntervalMS: 25, + }) + if err := injector.Prepare(); err != nil { + t.Fatal(err) + } + defer injector.Close() + + browserLauncher := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalExecutablePath: loadExtensionTestBrowserPath(t), + }) + browserLauncher.Update(injector.ConfigForLauncher()) + if _, err := browserLauncher.Launch(launcher.LauncherConfig{}); err != nil { + t.Fatal(err) + } + defer browserLauncher.Close() + + upstream := transport.NewWSUpstreamTransport(transport.UpstreamTransportConfig{}) + upstream.Update(browserLauncher.ConfigForUpstream()) + if err := upstream.Connect(); err != nil { + t.Fatal(err) + } + defer upstream.Close() + injector.Update(InjectorConfig{ + Send: func(method string, params map[string]any, sessionID string) (map[string]any, error) { + return upstream.Send(method, params, sessionID) + }, + }) + + targets, err := upstream.Send("Target.getTargets", map[string]any{}, "") + if err != nil { + t.Fatal(err) + } + targetInfos, _ := targets["targetInfos"].([]any) + for _, rawTarget := range targetInfos { + target, _ := rawTarget.(map[string]any) + if target == nil { + continue + } + targetURL, _ := target["url"].(string) + if strings.HasPrefix(targetURL, "chrome-extension://"+doesNotExistExtensionID+"/") { + t.Fatalf("found does-not-exist extension target: %#v", target) + } + } + + result, err := injector.Inject() + if err != nil { + t.Fatal(err) + } + if result != nil { + t.Fatalf("result = %#v", result) + } +} diff --git a/go/modcdp/injector/DiscoverExtensionInjector.go b/go/modcdp/injector/DiscoverExtensionInjector.go new file mode 100644 index 00000000..55f42430 --- /dev/null +++ b/go/modcdp/injector/DiscoverExtensionInjector.go @@ -0,0 +1,85 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/DiscoverExtensionInjector.ts +// - ./python/modcdp/injector/DiscoverExtensionInjector.py +package injector + +import ( + "fmt" + "os" + "strings" +) + +type DiscoverExtensionInjector struct { + ExtensionInjector + CleanupPath string +} + +func NewDiscoverExtensionInjector(config InjectorConfig) DiscoverExtensionInjector { + config.InjectorMode = "discover" + return DiscoverExtensionInjector{ExtensionInjector: NewExtensionInjector(config)} +} + +func (i *DiscoverExtensionInjector) Prepare() error { + extensionPath := i.Config.InjectorDiscoverExtensionPath + if i.Config.InjectorServiceWorkerExtensionID == "" && extensionPath != "" { + manifestPath := extensionPath + if strings.HasSuffix(extensionPath, ".zip") { + prepared, err := PrepareUnpackedExtension(extensionPath) + if err != nil { + return err + } + manifestPath = prepared.UnpackedExtensionPath + i.CleanupPath = prepared.CleanupPath + } + extensionID, err := ExtensionIDFromManifestKey(manifestPath) + if err != nil { + return err + } + i.ServiceWorkerExtensionID = extensionID + } + return nil +} + +func (i *DiscoverExtensionInjector) Inject() (*ExtensionInjectionResult, error) { + discovered, err := i.discoverReadyServiceWorker(false) + if err != nil || discovered != nil { + if discovered != nil { + discovered.Source = "discover" + } + return discovered, err + } + if i.Config.InjectorTrustServiceWorkerTarget { + waited, err := i.waitForReadyServiceWorker(i.Config.InjectorServiceWorkerProbeTimeoutMS, true) + if err != nil || waited != nil { + if waited != nil { + waited.Source = "discover" + } + return waited, err + } + } + if !i.Config.InjectorRequireServiceWorkerTarget { + return nil, nil + } + waited, err := i.waitForReadyServiceWorker(i.Config.InjectorServiceWorkerReadyTimeoutMS, i.Config.InjectorTrustServiceWorkerTarget) + if err != nil || waited != nil { + if waited != nil { + waited.Source = "discover" + } + return waited, err + } + matchers := append(append([]string{}, i.Config.InjectorServiceWorkerURLIncludes...), i.Config.InjectorServiceWorkerURLSuffixes...) + matcherText := strings.Join(matchers, ", ") + if matcherText == "" { + matcherText = "no matcher" + } + return nil, fmt.Errorf("required ModCDP service worker target was not visible (%s)", matcherText) +} + +func (i *DiscoverExtensionInjector) Close() error { + if i.CleanupPath != "" { + _ = os.RemoveAll(i.CleanupPath) + i.CleanupPath = "" + } + return nil +} diff --git a/go/modcdp/injector/DiscoverExtensionInjector_test.go b/go/modcdp/injector/DiscoverExtensionInjector_test.go new file mode 100644 index 00000000..fc7bf3b0 --- /dev/null +++ b/go/modcdp/injector/DiscoverExtensionInjector_test.go @@ -0,0 +1,174 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.DiscoverExtensionInjector.ts +// - ./python/tests/test_DiscoverExtensionInjector.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package injector_test + +import ( + modcdp "github.com/browserbase/modcdp/go/modcdp/client" + . "github.com/browserbase/modcdp/go/modcdp/injector" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "testing" +) + +func TestDiscoverExtensionInjectorAttachesToAnAlreadyLoadedRealModCDPExtension(t *testing.T) { + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) + if err != nil { + t.Fatal(err) + } + headless := true + owner := modcdp.New(modcdp.Config{ + Launcher: modcdp.LauncherConfig{LauncherMode: "local", LauncherLocalHeadless: &headless, LauncherLocalExecutablePath: loadExtensionTestBrowserPath(t)}, + Upstream: modcdp.UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: modcdp.InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + }) + defer owner.Close() + + if err := owner.Connect(); err != nil { + t.Fatal(err) + } + cdp := modcdp.New(modcdp.Config{ + Launcher: modcdp.LauncherConfig{LauncherMode: "remote", LauncherRemoteCDPURL: owner.CDPURL}, + Upstream: modcdp.UpstreamTransportConfig{UpstreamMode: "ws", UpstreamWSCDPURL: owner.CDPURL}, + Injector: modcdp.InjectorConfig{ + InjectorMode: "discover", + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + }) + defer cdp.Close() + + if err := cdp.Connect(); err != nil { + t.Fatal(err) + } + if cdp.ConnectTiming["injector_source"] != "discover" { + t.Fatalf("injector_source = %v", cdp.ConnectTiming["injector_source"]) + } + if cdp.Injector.ExtensionID != DefaultModCDPExtensionID { + t.Fatalf("Injector.ExtensionID = %q", cdp.Injector.ExtensionID) + } + result, err := cdp.Mod.Evaluate(map[string]any{ + "expression": "chrome.runtime.getURL('modcdp/service_worker.js')", + }) + if err != nil { + t.Fatal(err) + } + if result != "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js" { + t.Fatalf("Mod.evaluate = %#v", result) + } +} + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +func loadExtensionTestBrowserPath(t *testing.T) string { + t.Helper() + for _, candidate := range []string{os.Getenv("CHROME_PATH"), linuxChromiumPath()} { + if candidate == "" { + continue + } + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + home, err := os.UserHomeDir() + if err != nil || home == "" { + home = "." + } + localAppData := os.Getenv("LOCALAPPDATA") + if localAppData == "" { + localAppData = filepath.Join(home, "AppData", "Local") + } + var patterns []string + switch runtime.GOOS { + case "darwin": + patterns = []string{ + filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), + filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Chromium.app", "Contents", "MacOS", "Chromium"), + filepath.Join(home, "Library", "Caches", "puppeteer", "chrome", "mac*-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), + } + case "windows": + patterns = []string{ + filepath.Join(localAppData, "ms-playwright", "chromium-*", "chrome-win*", "chrome.exe"), + filepath.Join(home, ".cache", "puppeteer", "chrome", "win*-*", "chrome.exe"), + } + default: + patterns = []string{ + filepath.Join(home, ".cache", "ms-playwright", "chromium-*", "chrome-linux*", "chrome"), + filepath.Join("/opt", "pw-browsers", "chromium-*", "chrome-linux*", "chrome"), + filepath.Join(home, ".cache", "puppeteer", "chrome", "linux-*", "chrome-linux*", "chrome"), + } + } + var candidates []string + for _, pattern := range patterns { + matches, err := filepath.Glob(pattern) + if err == nil { + candidates = append(candidates, matches...) + } + } + candidates = newestChromeForTestingFirst(candidates) + if len(candidates) > 0 { + return candidates[0] + } + t.Fatal("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + return "" +} + +func linuxChromiumPath() string { + if runtime.GOOS == "linux" { + return "/usr/bin/chromium" + } + return "" +} + +func newestChromeForTestingFirst(candidates []string) []string { + seen := map[string]bool{} + deduped := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + if candidate == "" || seen[candidate] { + continue + } + seen[candidate] = true + deduped = append(deduped, candidate) + } + sort.Slice(deduped, func(i, j int) bool { + leftVersion, leftMtime := browserPathScore(deduped[i]) + rightVersion, rightMtime := browserPathScore(deduped[j]) + if leftVersion != rightVersion { + return leftVersion > rightVersion + } + if leftMtime != rightMtime { + return leftMtime > rightMtime + } + return deduped[i] < deduped[j] + }) + return deduped +} + +func browserPathScore(candidate string) (int, int64) { + version := 0 + for _, match := range regexp.MustCompile(`\d+`).FindAllString(candidate, -1) { + value := 0 + for _, digit := range match { + value = value*10 + int(digit-'0') + } + if value > version { + version = value + } + } + info, err := os.Stat(candidate) + if err != nil { + return version, 0 + } + return version, info.ModTime().UnixNano() +} diff --git a/go/modcdp/injector/DiscoveredExtensionInjector.go b/go/modcdp/injector/DiscoveredExtensionInjector.go deleted file mode 100644 index d83d1446..00000000 --- a/go/modcdp/injector/DiscoveredExtensionInjector.go +++ /dev/null @@ -1,49 +0,0 @@ -package injector - -import ( - "fmt" - "strings" -) - -type DiscoveredExtensionInjector struct { - ExtensionInjector -} - -func NewDiscoveredExtensionInjector(options ExtensionInjectorConfig) DiscoveredExtensionInjector { - return DiscoveredExtensionInjector{ExtensionInjector: NewExtensionInjector(options)} -} - -func (i *DiscoveredExtensionInjector) Inject() (*ExtensionInjectionResult, error) { - discovered, err := i.discoverReadyServiceWorker(false) - if err != nil || discovered != nil { - if discovered != nil { - discovered.Source = "discovered" - } - return discovered, err - } - if i.Options.InjectorTrustServiceWorkerTarget { - waited, err := i.waitForReadyServiceWorker(i.Options.InjectorServiceWorkerProbeTimeoutMS, true) - if err != nil || waited != nil { - if waited != nil { - waited.Source = "discovered" - } - return waited, err - } - } - if !i.Options.InjectorRequireServiceWorkerTarget { - return nil, nil - } - waited, err := i.waitForReadyServiceWorker(i.Options.InjectorServiceWorkerReadyTimeoutMS, i.Options.InjectorTrustServiceWorkerTarget) - if err != nil || waited != nil { - if waited != nil { - waited.Source = "discovered" - } - return waited, err - } - matchers := append(append([]string{}, i.Options.InjectorServiceWorkerURLIncludes...), i.Options.InjectorServiceWorkerURLSuffixes...) - matcherText := strings.Join(matchers, ", ") - if matcherText == "" { - matcherText = "no matcher" - } - return nil, fmt.Errorf("required ModCDP service worker target was not visible (%s)", matcherText) -} diff --git a/go/modcdp/injector/DiscoveredExtensionInjector_test.go b/go/modcdp/injector/DiscoveredExtensionInjector_test.go deleted file mode 100644 index b6898385..00000000 --- a/go/modcdp/injector/DiscoveredExtensionInjector_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package injector_test - -import ( - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/injector" - "path/filepath" - "testing" -) - -func TestDiscoveredExtensionInjectorAttachesToAlreadyLoadedRealModCDPExtension(t *testing.T) { - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - headless := true - owner := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", LauncherOptions: modcdp.LaunchOptions{Headless: &headless}}, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws"}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - }) - defer owner.Close() - - if err := owner.Connect(); err != nil { - t.Fatal(err) - } - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "remote"}, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws", UpstreamCDPURL: owner.CDPURL}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "discover", - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["injector_source"] != "discovered" { - t.Fatalf("injector_source = %v", cdp.ConnectTiming["injector_source"]) - } - if cdp.ExtensionID != DefaultModCDPExtensionID { - t.Fatalf("ExtensionID = %q", cdp.ExtensionID) - } - result, err := cdp.Mod.Evaluate(map[string]any{ - "expression": "chrome.runtime.getURL('modcdp/service_worker.js')", - }) - if err != nil { - t.Fatal(err) - } - if result != "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js" { - t.Fatalf("Mod.evaluate = %#v", result) - } -} diff --git a/go/modcdp/injector/ExtensionInjector.go b/go/modcdp/injector/ExtensionInjector.go index 7bfc8aac..aa81f303 100644 --- a/go/modcdp/injector/ExtensionInjector.go +++ b/go/modcdp/injector/ExtensionInjector.go @@ -1,17 +1,11 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/ExtensionInjector.ts +// - ./python/modcdp/injector/ExtensionInjector.py package injector import ( - "archive/zip" - "bytes" - "crypto/sha256" - _ "embed" - "encoding/base64" - "encoding/json" "fmt" - "io" - "io/fs" - "os" - "path/filepath" "regexp" "strings" "time" @@ -30,17 +24,11 @@ const DefaultTargetSessionPollIntervalMS = 20 var DefaultModCDPServiceWorkerURLSuffixes = []string{"/modcdp/service_worker.js"} var extIDFromURL = regexp.MustCompile(`^chrome-extension://([a-z]+)/`) -//go:embed extension.zip -var bundledExtensionZip []byte - -const modcdpReadyExpression = `Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)` +const modcdpReadyExpression = `Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)` type SendCDP = types.SendCDP -type SessionIDForTarget = types.SessionIDForTarget -type AttachToTarget = types.AttachToTarget -type WaitForExecutionContext = types.WaitForExecutionContext -type LaunchOptions = types.LaunchOptions -type ExtensionInjectorConfig = types.ExtensionInjectorConfig +type LauncherConfig = types.LauncherConfig +type InjectorConfig = types.InjectorConfig type ExtensionInjectionResult = types.ExtensionInjectionResult func boolPtr(value bool) *bool { @@ -48,116 +36,139 @@ func boolPtr(value bool) *bool { } type ExtensionInjector struct { - Options ExtensionInjectorConfig - UnusableTargetIDs map[string]bool - LastError error + Config InjectorConfig + Source string + ExtensionID string + ServiceWorkerExtensionID string + TargetID string + URL string + SessionID string + UnusableTargetIDs map[string]bool + ExtraArgs []string } -func NewExtensionInjector(options ExtensionInjectorConfig) ExtensionInjector { - if options.InjectorCDPSendTimeoutMS == 0 { - options.InjectorCDPSendTimeoutMS = DefaultCDPSendTimeoutMS +func NewExtensionInjector(config InjectorConfig) ExtensionInjector { + if config.InjectorCDPSendTimeoutMS == 0 { + config.InjectorCDPSendTimeoutMS = DefaultCDPSendTimeoutMS + } + if config.InjectorExecutionContextTimeoutMS == 0 { + config.InjectorExecutionContextTimeoutMS = DefaultExecutionContextTimeoutMS } - if options.InjectorExecutionContextTimeoutMS == 0 { - options.InjectorExecutionContextTimeoutMS = DefaultExecutionContextTimeoutMS + if config.InjectorServiceWorkerProbeTimeoutMS == 0 { + config.InjectorServiceWorkerProbeTimeoutMS = DefaultServiceWorkerProbeTimeoutMS } - if options.InjectorServiceWorkerProbeTimeoutMS == 0 { - options.InjectorServiceWorkerProbeTimeoutMS = DefaultServiceWorkerProbeTimeoutMS + if config.InjectorServiceWorkerReadyTimeoutMS == 0 { + config.InjectorServiceWorkerReadyTimeoutMS = DefaultServiceWorkerReadyTimeoutMS } - if options.InjectorServiceWorkerReadyTimeoutMS == 0 { - options.InjectorServiceWorkerReadyTimeoutMS = DefaultServiceWorkerReadyTimeoutMS + if config.InjectorServiceWorkerPollIntervalMS == 0 { + config.InjectorServiceWorkerPollIntervalMS = DefaultServiceWorkerPollIntervalMS } - if options.InjectorServiceWorkerPollIntervalMS == 0 { - options.InjectorServiceWorkerPollIntervalMS = DefaultServiceWorkerPollIntervalMS + if config.InjectorTargetSessionPollIntervalMS == 0 { + config.InjectorTargetSessionPollIntervalMS = DefaultTargetSessionPollIntervalMS } - if options.InjectorTargetSessionPollIntervalMS == 0 { - options.InjectorTargetSessionPollIntervalMS = DefaultTargetSessionPollIntervalMS + if config.InjectorBBBaseURL == "" { + config.InjectorBBBaseURL = DefaultBrowserbaseBaseURL } - return ExtensionInjector{Options: options, UnusableTargetIDs: map[string]bool{}} + return ExtensionInjector{Config: config, UnusableTargetIDs: map[string]bool{}} } -func (i *ExtensionInjector) Update(config ExtensionInjectorConfig) *ExtensionInjector { +func (i *ExtensionInjector) Update(config InjectorConfig) *ExtensionInjector { if config.Send != nil { - i.Options.Send = config.Send + i.Config.Send = config.Send + } + if config.InjectorCLIExtensionPath != "" { + i.Config.InjectorCLIExtensionPath = config.InjectorCLIExtensionPath } - if config.SessionIDForTarget != nil { - i.Options.SessionIDForTarget = config.SessionIDForTarget + if config.InjectorCLIExtensionID != "" { + i.Config.InjectorCLIExtensionID = config.InjectorCLIExtensionID } - if config.AttachToTarget != nil { - i.Options.AttachToTarget = config.AttachToTarget + if config.InjectorCDPExtensionPath != "" { + i.Config.InjectorCDPExtensionPath = config.InjectorCDPExtensionPath } - if config.WaitForExecutionContext != nil { - i.Options.WaitForExecutionContext = config.WaitForExecutionContext + if config.InjectorCDPExtensionID != "" { + i.Config.InjectorCDPExtensionID = config.InjectorCDPExtensionID } - if config.InjectorExtensionPath != "" { - i.Options.InjectorExtensionPath = config.InjectorExtensionPath + if config.InjectorBBExtensionPath != "" { + i.Config.InjectorBBExtensionPath = config.InjectorBBExtensionPath } - if config.InjectorExtensionID != "" { - i.Options.InjectorExtensionID = config.InjectorExtensionID + if config.InjectorBBExtensionID != "" { + i.Config.InjectorBBExtensionID = config.InjectorBBExtensionID + } + if config.InjectorDiscoverExtensionPath != "" { + i.Config.InjectorDiscoverExtensionPath = config.InjectorDiscoverExtensionPath + } + if config.InjectorServiceWorkerExtensionID != "" { + i.Config.InjectorServiceWorkerExtensionID = config.InjectorServiceWorkerExtensionID } if config.InjectorServiceWorkerURLIncludes != nil { - i.Options.InjectorServiceWorkerURLIncludes = append([]string{}, config.InjectorServiceWorkerURLIncludes...) + i.Config.InjectorServiceWorkerURLIncludes = append([]string{}, config.InjectorServiceWorkerURLIncludes...) } if config.InjectorServiceWorkerURLSuffixes != nil { - i.Options.InjectorServiceWorkerURLSuffixes = append([]string{}, config.InjectorServiceWorkerURLSuffixes...) + i.Config.InjectorServiceWorkerURLSuffixes = append([]string{}, config.InjectorServiceWorkerURLSuffixes...) } if config.InjectorTrustServiceWorkerTarget { - i.Options.InjectorTrustServiceWorkerTarget = true + i.Config.InjectorTrustServiceWorkerTarget = true } if config.InjectorRequireServiceWorkerTarget { - i.Options.InjectorRequireServiceWorkerTarget = true + i.Config.InjectorRequireServiceWorkerTarget = true } if config.InjectorServiceWorkerReadyExpression != "" { - i.Options.InjectorServiceWorkerReadyExpression = config.InjectorServiceWorkerReadyExpression + i.Config.InjectorServiceWorkerReadyExpression = config.InjectorServiceWorkerReadyExpression } if config.InjectorCDPSendTimeoutMS != 0 { - i.Options.InjectorCDPSendTimeoutMS = config.InjectorCDPSendTimeoutMS + i.Config.InjectorCDPSendTimeoutMS = config.InjectorCDPSendTimeoutMS } if config.InjectorExecutionContextTimeoutMS != 0 { - i.Options.InjectorExecutionContextTimeoutMS = config.InjectorExecutionContextTimeoutMS + i.Config.InjectorExecutionContextTimeoutMS = config.InjectorExecutionContextTimeoutMS } if config.InjectorServiceWorkerProbeTimeoutMS != 0 { - i.Options.InjectorServiceWorkerProbeTimeoutMS = config.InjectorServiceWorkerProbeTimeoutMS + i.Config.InjectorServiceWorkerProbeTimeoutMS = config.InjectorServiceWorkerProbeTimeoutMS } if config.InjectorServiceWorkerReadyTimeoutMS != 0 { - i.Options.InjectorServiceWorkerReadyTimeoutMS = config.InjectorServiceWorkerReadyTimeoutMS + i.Config.InjectorServiceWorkerReadyTimeoutMS = config.InjectorServiceWorkerReadyTimeoutMS } if config.InjectorServiceWorkerPollIntervalMS != 0 { - i.Options.InjectorServiceWorkerPollIntervalMS = config.InjectorServiceWorkerPollIntervalMS + i.Config.InjectorServiceWorkerPollIntervalMS = config.InjectorServiceWorkerPollIntervalMS } if config.InjectorTargetSessionPollIntervalMS != 0 { - i.Options.InjectorTargetSessionPollIntervalMS = config.InjectorTargetSessionPollIntervalMS - } - if config.InjectorBrowserbaseAPIKey != "" { - i.Options.InjectorBrowserbaseAPIKey = config.InjectorBrowserbaseAPIKey + i.Config.InjectorTargetSessionPollIntervalMS = config.InjectorTargetSessionPollIntervalMS } - if config.InjectorBrowserbaseBaseURL != "" { - i.Options.InjectorBrowserbaseBaseURL = config.InjectorBrowserbaseBaseURL + if config.InjectorBBAPIKey != "" { + i.Config.InjectorBBAPIKey = config.InjectorBBAPIKey } - if config.UpstreamNativeMessagingHostName != "" { - i.Options.UpstreamNativeMessagingHostName = config.UpstreamNativeMessagingHostName + if config.InjectorBBBaseURL != "" { + i.Config.InjectorBBBaseURL = config.InjectorBBBaseURL } - if config.UpstreamNATSURL != "" { - i.Options.UpstreamNATSURL = config.UpstreamNATSURL - } - if config.UpstreamNATSSubjectPrefix != "" { - i.Options.UpstreamNATSSubjectPrefix = config.UpstreamNATSSubjectPrefix + return i +} + +func (i *ExtensionInjector) RecordInjectionResult(result *ExtensionInjectionResult) *ExtensionInjector { + i.Source = result.Source + i.ExtensionID = result.ExtensionID + if result.ExtensionID != "" { + i.ServiceWorkerExtensionID = result.ExtensionID } + i.TargetID = result.TargetID + i.URL = result.URL + i.SessionID = result.SessionID return i } -func (i ExtensionInjector) GetInjectorConfig() ExtensionInjectorConfig { - return i.Options +func (i ExtensionInjector) ConfigForLauncher() LauncherConfig { + return LauncherConfig{ + LauncherLocalExtraArgs: i.ExtraArgs, + LauncherBBExtensionID: i.Config.InjectorBBExtensionID, + } } -func (i ExtensionInjector) GetLauncherConfig() LaunchOptions { - return LaunchOptions{} +func (i ExtensionInjector) ConfigForUpstream() map[string]any { + return map[string]any{} } -func (i ExtensionInjector) GetTransportConfig() map[string]any { - if i.Options.InjectorExtensionID == "" { - return map[string]any{} - } - return map[string]any{"injector_extension_id": i.Options.InjectorExtensionID} +func (i ExtensionInjector) ToJSON() map[string]any { + config := i.Config + config.Send = nil + return types.ModCDPToJSON(i, types.ModCDPJSONConfig{Config: config}) } func (i *ExtensionInjector) Prepare() error { @@ -173,24 +184,24 @@ func (i *ExtensionInjector) Inject() (*ExtensionInjectionResult, error) { } func (i ExtensionInjector) readyExpression() string { - if i.Options.InjectorServiceWorkerReadyExpression == "" { + if i.Config.InjectorServiceWorkerReadyExpression == "" || i.Config.InjectorServiceWorkerReadyExpression == modcdpReadyExpression { return modcdpReadyExpression } - return fmt.Sprintf("(%s) && Boolean(%s)", modcdpReadyExpression, i.Options.InjectorServiceWorkerReadyExpression) + return fmt.Sprintf("(%s) && Boolean(%s)", modcdpReadyExpression, i.Config.InjectorServiceWorkerReadyExpression) } func (i ExtensionInjector) sendWithTimeout(method string, params map[string]any, sessionID string, timeoutMS int) (map[string]any, error) { - if i.Options.Send == nil { + if i.Config.Send == nil { return nil, fmt.Errorf("%T requires a CDP send function", i) } if params == nil { params = map[string]any{} } if timeoutMS == 0 { - timeoutMS = i.Options.InjectorCDPSendTimeoutMS + timeoutMS = i.Config.InjectorCDPSendTimeoutMS } if timeoutMS <= 0 { - return i.Options.Send(method, params, sessionID) + return i.Config.Send(method, params, sessionID) } type sendResult struct { result map[string]any @@ -198,7 +209,7 @@ func (i ExtensionInjector) sendWithTimeout(method string, params map[string]any, } done := make(chan sendResult, 1) go func() { - result, err := i.Options.Send(method, params, sessionID) + result, err := i.Config.Send(method, params, sessionID) done <- sendResult{result: result, err: err} }() select { @@ -209,41 +220,8 @@ func (i ExtensionInjector) sendWithTimeout(method string, params map[string]any, } } -func (i ExtensionInjector) SendWithTimeout(method string, params map[string]any, sessionID string, timeoutMS int) (map[string]any, error) { - return i.sendWithTimeout(method, params, sessionID, timeoutMS) -} - -func (i ExtensionInjector) sessionIDForTarget(targetID string, timeoutMS int) string { - deadline := time.Now().Add(time.Duration(timeoutMS) * time.Millisecond) - for { - if i.Options.SessionIDForTarget != nil { - if sessionID := i.Options.SessionIDForTarget(targetID); sessionID != "" { - return sessionID - } - } - if timeoutMS <= 0 || time.Now().After(deadline) { - return "" - } - time.Sleep(time.Duration(i.Options.InjectorTargetSessionPollIntervalMS) * time.Millisecond) - } -} - -func (i ExtensionInjector) ensureSessionIDForTarget(targetID string, timeoutMS int, allowAttach bool) string { - if i.Options.SessionIDForTarget != nil { - if sessionID := i.Options.SessionIDForTarget(targetID); sessionID != "" { - return sessionID - } - } - if allowAttach && i.Options.AttachToTarget != nil { - if sessionID := i.Options.AttachToTarget(targetID); sessionID != "" { - return sessionID - } - } - return i.sessionIDForTarget(targetID, timeoutMS) -} - func (i ExtensionInjector) targetInfos() ([]map[string]any, error) { - result, err := i.sendWithTimeout("Target.getTargets", map[string]any{}, "", i.Options.InjectorCDPSendTimeoutMS) + result, err := i.sendWithTimeout("Target.getTargets", map[string]any{}, "", i.Config.InjectorCDPSendTimeoutMS) if err != nil { return nil, err } @@ -264,22 +242,32 @@ func (i ExtensionInjector) probeTarget(target map[string]any, sessionTimeoutMS i if targetID == "" || i.UnusableTargetIDs[targetID] { return nil, nil } - sessionID := i.ensureSessionIDForTarget(targetID, sessionTimeoutMS, allowAttach) + attached, err := i.sendWithTimeout("Target.attachToTarget", map[string]any{"targetId": targetID, "flatten": true}, "", sessionTimeoutMS) + if err != nil { + return nil, err + } + sessionID, _ := attached["sessionId"].(string) if sessionID == "" { - return nil, nil + return nil, fmt.Errorf("Target.attachToTarget returned no sessionId for targetId=%s", targetID) + } + detach := func() { + _, _ = i.sendWithTimeout("Target.detachFromTarget", map[string]any{"sessionId": sessionID}, "", i.Config.InjectorCDPSendTimeoutMS) } - if _, err := i.sendWithTimeout("Runtime.enable", map[string]any{}, sessionID, i.Options.InjectorCDPSendTimeoutMS); err != nil { + if _, err := i.sendWithTimeout("Runtime.enable", map[string]any{}, sessionID, i.Config.InjectorCDPSendTimeoutMS); err != nil { + detach() return nil, err } probe, err := i.sendWithTimeout("Runtime.evaluate", map[string]any{ "expression": i.readyExpression(), "returnByValue": true, - }, sessionID, i.Options.InjectorCDPSendTimeoutMS) + }, sessionID, i.Config.InjectorCDPSendTimeoutMS) if err != nil { + detach() return nil, err } result, _ := probe["result"].(map[string]any) if ready, _ := result["value"].(bool); !ready { + detach() return nil, nil } extensionID := "" @@ -287,7 +275,7 @@ func (i ExtensionInjector) probeTarget(target map[string]any, sessionTimeoutMS i extensionID = m[1] } return &ExtensionInjectionResult{ - Source: "discovered", + Source: "discover", ExtensionID: extensionID, TargetID: targetID, URL: targetURL, @@ -300,12 +288,12 @@ func (i ExtensionInjector) discoverReadyServiceWorker(matchedOnly bool) (*Extens if err != nil { return nil, err } - if i.Options.InjectorTrustServiceWorkerTarget { + if i.Config.InjectorTrustServiceWorkerTarget { for _, target := range targets { if !i.serviceWorkerTargetMatches(target) { continue } - probed, err := i.probeTarget(target, i.Options.InjectorServiceWorkerProbeTimeoutMS, true) + probed, err := i.probeTarget(target, i.Config.InjectorServiceWorkerProbeTimeoutMS, true) if err != nil { return nil, err } @@ -315,7 +303,7 @@ func (i ExtensionInjector) discoverReadyServiceWorker(matchedOnly bool) (*Extens } } } - if i.Options.InjectorTrustServiceWorkerTarget || matchedOnly { + if i.Config.InjectorTrustServiceWorkerTarget || matchedOnly { return nil, nil } for _, target := range targets { @@ -324,7 +312,7 @@ func (i ExtensionInjector) discoverReadyServiceWorker(matchedOnly bool) (*Extens if targetType != "service_worker" || !strings.HasPrefix(targetURL, "chrome-extension://") { continue } - probed, err := i.probeTarget(target, i.Options.InjectorServiceWorkerProbeTimeoutMS, false) + probed, err := i.probeTarget(target, i.Config.InjectorServiceWorkerProbeTimeoutMS, false) if err == nil && probed != nil { return probed, nil } @@ -339,33 +327,33 @@ func (i ExtensionInjector) waitForReadyServiceWorker(timeoutMS int, matchedOnly if err != nil || discovered != nil { return discovered, err } - time.Sleep(time.Duration(i.Options.InjectorServiceWorkerPollIntervalMS) * time.Millisecond) + time.Sleep(time.Duration(i.Config.InjectorServiceWorkerPollIntervalMS) * time.Millisecond) } return nil, nil } -func (i ExtensionInjector) WaitForReadyServiceWorker(timeoutMS int, matchedOnly bool) (*ExtensionInjectionResult, error) { - return i.waitForReadyServiceWorker(timeoutMS, matchedOnly) -} - func (i ExtensionInjector) serviceWorkerTargetMatches(target map[string]any) bool { targetURL, _ := target["url"].(string) targetType, _ := target["type"].(string) if targetType != "service_worker" || !strings.HasPrefix(targetURL, "chrome-extension://") { return false } - hasExtensionID := i.Options.InjectorExtensionID != "" - if i.Options.InjectorExtensionID != "" && !strings.HasPrefix(targetURL, "chrome-extension://"+i.Options.InjectorExtensionID+"/") { + serviceWorkerExtensionID := i.Config.InjectorServiceWorkerExtensionID + if serviceWorkerExtensionID == "" { + serviceWorkerExtensionID = i.ServiceWorkerExtensionID + } + hasExtensionID := serviceWorkerExtensionID != "" + if serviceWorkerExtensionID != "" && !strings.HasPrefix(targetURL, "chrome-extension://"+serviceWorkerExtensionID+"/") { return false } - for _, part := range i.Options.InjectorServiceWorkerURLIncludes { + for _, part := range i.Config.InjectorServiceWorkerURLIncludes { if !strings.Contains(targetURL, part) { return false } } - if len(i.Options.InjectorServiceWorkerURLSuffixes) > 0 { + if len(i.Config.InjectorServiceWorkerURLSuffixes) > 0 { matched := false - for _, suffix := range i.Options.InjectorServiceWorkerURLSuffixes { + for _, suffix := range i.Config.InjectorServiceWorkerURLSuffixes { if strings.HasSuffix(targetURL, suffix) { matched = true break @@ -375,186 +363,7 @@ func (i ExtensionInjector) serviceWorkerTargetMatches(target map[string]any) boo return false } } - return hasExtensionID || len(i.Options.InjectorServiceWorkerURLIncludes) > 0 || len(i.Options.InjectorServiceWorkerURLSuffixes) > 0 -} - -func (i ExtensionInjector) ServiceWorkerTargetMatches(target map[string]any) bool { - return i.serviceWorkerTargetMatches(target) -} - -func prepareUnpackedExtension(extensionPath string) (string, string, error) { - if extensionPath == "" { - dir, err := os.MkdirTemp("", "modcdp-extension-") - if err != nil { - return "", "", err - } - reader, err := zip.NewReader(bytes.NewReader(bundledExtensionZip), int64(len(bundledExtensionZip))) - if err != nil { - _ = os.RemoveAll(dir) - return "", "", err - } - if err := extractZipFiles(reader.File, dir); err != nil { - _ = os.RemoveAll(dir) - return "", "", err - } - return extensionRoot(dir), dir, nil - } - if !strings.HasSuffix(extensionPath, ".zip") { - dir, err := os.MkdirTemp("", "modcdp-extension-") - if err != nil { - return "", "", err - } - if err := copyDir(extensionPath, dir); err != nil { - _ = os.RemoveAll(dir) - return "", "", err - } - return dir, dir, nil - } - dir, err := os.MkdirTemp("", "modcdp-extension-") - if err != nil { - return "", "", err - } - reader, err := zip.OpenReader(extensionPath) - if err != nil { - _ = os.RemoveAll(dir) - return "", "", err - } - defer reader.Close() - if err := extractZipFiles(reader.File, dir); err != nil { - _ = os.RemoveAll(dir) - return "", "", err - } - return extensionRoot(dir), dir, nil -} - -func extractZipFiles(files []*zip.File, dir string) error { - root, err := filepath.Abs(dir) - if err != nil { - return err - } - for _, file := range files { - targetPath, err := safeZipTarget(root, file.Name) - if err != nil { - return err - } - if file.FileInfo().IsDir() { - if err := os.MkdirAll(targetPath, 0o755); err != nil { - return err - } - continue - } - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { - return err - } - src, err := file.Open() - if err != nil { - return err - } - dst, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.FileInfo().Mode()) - if err != nil { - _ = src.Close() - return err - } - _, copyErr := io.Copy(dst, src) - srcErr := src.Close() - dstErr := dst.Close() - if copyErr != nil { - return copyErr - } - if srcErr != nil { - return srcErr - } - if dstErr != nil { - return dstErr - } - } - return nil -} - -func safeZipTarget(root string, name string) (string, error) { - cleanName := filepath.Clean(name) - if filepath.IsAbs(cleanName) || cleanName == "." || cleanName == ".." || strings.HasPrefix(cleanName, ".."+string(os.PathSeparator)) { - return "", fmt.Errorf("zip entry %q escapes extension extraction directory", name) - } - targetPath := filepath.Join(root, cleanName) - targetAbs, err := filepath.Abs(targetPath) - if err != nil { - return "", err - } - if targetAbs != root && !strings.HasPrefix(targetAbs, root+string(os.PathSeparator)) { - return "", fmt.Errorf("zip entry %q escapes extension extraction directory", name) - } - return targetAbs, nil -} - -func extensionRoot(unpackedPath string) string { - if _, err := os.Stat(filepath.Join(unpackedPath, "manifest.json")); err == nil { - return unpackedPath - } - nested := filepath.Join(unpackedPath, "extension") - if _, err := os.Stat(filepath.Join(nested, "manifest.json")); err == nil { - return nested - } - return unpackedPath -} - -func extensionIDFromManifestKey(extensionPath string) (string, error) { - manifestBytes, err := os.ReadFile(filepath.Join(extensionPath, "manifest.json")) - if err != nil { - return "", nil - } - var manifest map[string]any - if err := json.Unmarshal(manifestBytes, &manifest); err != nil { - return "", err - } - key, _ := manifest["key"].(string) - if strings.TrimSpace(key) == "" { - return "", nil - } - keyBytes, err := base64.StdEncoding.DecodeString(key) - if err != nil { - return "", err - } - digest := sha256.Sum256(keyBytes) - alphabet := "abcdefghijklmnop" - result := strings.Builder{} - for _, value := range digest[:16] { - result.WriteByte(alphabet[value>>4]) - result.WriteByte(alphabet[value&0x0f]) - } - return result.String(), nil -} - -func copyDir(src string, dst string) error { - return filepath.WalkDir(src, func(path string, entry fs.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - relative, err := filepath.Rel(src, path) - if err != nil { - return err - } - target := filepath.Join(dst, relative) - if entry.IsDir() { - return os.MkdirAll(target, 0o755) - } - info, err := entry.Info() - if err != nil { - return err - } - sourceFile, err := os.Open(path) - if err != nil { - return err - } - defer sourceFile.Close() - targetFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) - if err != nil { - return err - } - defer targetFile.Close() - _, err = io.Copy(targetFile, sourceFile) - return err - }) + return hasExtensionID || len(i.Config.InjectorServiceWorkerURLIncludes) > 0 || len(i.Config.InjectorServiceWorkerURLSuffixes) > 0 } func firstNonEmptyString(values ...string) string { diff --git a/go/modcdp/injector/ExtensionInjector_test.go b/go/modcdp/injector/ExtensionInjector_test.go index 3be2e6a9..91094342 100644 --- a/go/modcdp/injector/ExtensionInjector_test.go +++ b/go/modcdp/injector/ExtensionInjector_test.go @@ -1,33 +1,39 @@ -package injector_test +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.ExtensionInjector.ts +// - ./python/tests/test_ExtensionInjector.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package injector import ( "strings" "testing" - - . "github.com/browserbase/modcdp/go/modcdp/injector" ) func TestExtensionInjectorOwnsSharedInjectorConfig(t *testing.T) { - injector := NewExtensionInjector(ExtensionInjectorConfig{ - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + injector := NewExtensionInjector(InjectorConfig{ + InjectorServiceWorkerExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, }) - transportConfig := injector.GetTransportConfig() - if transportConfig["injector_extension_id"] != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { - t.Fatalf("injector_extension_id = %v", transportConfig["injector_extension_id"]) + if injector.Config.InjectorServiceWorkerExtensionID != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { + t.Fatalf("InjectorServiceWorkerExtensionID = %v", injector.Config.InjectorServiceWorkerExtensionID) + } + if len(injector.ConfigForUpstream()) != 0 { + t.Fatalf("expected empty upstream config") } - if len(injector.GetLauncherConfig().ExtraArgs) != 0 { - t.Fatalf("expected empty launcher config") + if len(injector.ExtraArgs) != 0 { + t.Fatalf("expected empty extra args") } - if !injector.ServiceWorkerTargetMatches(map[string]any{ + if !injector.serviceWorkerTargetMatches(map[string]any{ "targetId": "target-1", "type": "service_worker", "url": "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/modcdp/service_worker.js", }) { t.Fatal("expected service worker target to match") } - if injector.ServiceWorkerTargetMatches(map[string]any{ + if injector.serviceWorkerTargetMatches(map[string]any{ "targetId": "target-1", "type": "service_worker", "url": "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/background.js", @@ -36,9 +42,26 @@ func TestExtensionInjectorOwnsSharedInjectorConfig(t *testing.T) { } } -func TestExtensionInjectorBaseInjectReportsTheClassName(t *testing.T) { - injector := NewExtensionInjector(ExtensionInjectorConfig{}) +func TestExtensionInjectorBaseInjectReportsTheSubclassName(t *testing.T) { + injector := NewExtensionInjector(InjectorConfig{}) if _, err := injector.Inject(); err == nil || !strings.Contains(err.Error(), "ExtensionInjector.Inject is not implemented") { t.Fatalf("Inject error = %v", err) } } + +func TestExtensionInjectorDoesNotWrapTheDefaultReadyExpressionTwice(t *testing.T) { + injector := NewExtensionInjector(InjectorConfig{ + InjectorServiceWorkerReadyExpression: modcdpReadyExpression, + }) + + if injector.readyExpression() != modcdpReadyExpression { + t.Fatalf("readyExpression = %q", injector.readyExpression()) + } + + injector.Update(InjectorConfig{ + InjectorServiceWorkerReadyExpression: "globalThis.ready === true", + }) + if got := injector.readyExpression(); got != "("+modcdpReadyExpression+") && Boolean(globalThis.ready === true)" { + t.Fatalf("custom readyExpression = %q", got) + } +} diff --git a/go/modcdp/injector/ExtensionsLoadUnpackedInjector_test.go b/go/modcdp/injector/ExtensionsLoadUnpackedInjector_test.go deleted file mode 100644 index 9251461a..00000000 --- a/go/modcdp/injector/ExtensionsLoadUnpackedInjector_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package injector_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - . "github.com/browserbase/modcdp/go/modcdp/injector" -) - -func TestExtensionsLoadUnpackedInjectorPreparesDefaultPackagedExtensionZip(t *testing.T) { - injector := NewExtensionsLoadUnpackedInjector(ExtensionInjectorConfig{}) - if err := injector.Prepare(); err != nil { - t.Fatal(err) - } - defer injector.Close() - - if injector.UnpackedExtensionPath == "" { - t.Fatal("expected UnpackedExtensionPath") - } - if !strings.Contains(injector.UnpackedExtensionPath, "modcdp-extension-") { - t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) - } - if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { - t.Fatalf("expected unpacked manifest: %v", err) - } -} diff --git a/go/modcdp/injector/LocalBrowserLaunchExtensionInjector.go b/go/modcdp/injector/LocalBrowserLaunchExtensionInjector.go deleted file mode 100644 index 927bdaff..00000000 --- a/go/modcdp/injector/LocalBrowserLaunchExtensionInjector.go +++ /dev/null @@ -1,74 +0,0 @@ -package injector - -import ( - "os" -) - -type LocalBrowserLaunchExtensionInjector struct { - ExtensionInjector - UnpackedExtensionPath string - ExtensionID string - CleanupPath string -} - -func NewLocalBrowserLaunchExtensionInjector(options ExtensionInjectorConfig) LocalBrowserLaunchExtensionInjector { - return LocalBrowserLaunchExtensionInjector{ExtensionInjector: NewExtensionInjector(options)} -} - -func (i *LocalBrowserLaunchExtensionInjector) Prepare() error { - extensionPath := i.Options.InjectorExtensionPath - if i.UnpackedExtensionPath != "" { - return nil - } - unpackedPath, cleanupPath, err := prepareUnpackedExtension(extensionPath) - if err != nil { - return err - } - i.UnpackedExtensionPath = unpackedPath - i.CleanupPath = cleanupPath - _, err = i.resolveExtensionID() - return err -} - -func (i *LocalBrowserLaunchExtensionInjector) GetLauncherConfig() LaunchOptions { - if i.UnpackedExtensionPath == "" { - return LaunchOptions{} - } - return LaunchOptions{ExtraArgs: []string{"--load-extension=" + i.UnpackedExtensionPath}} -} - -func (i *LocalBrowserLaunchExtensionInjector) Inject() (*ExtensionInjectionResult, error) { - discovered, err := i.discoverReadyServiceWorker(i.Options.InjectorTrustServiceWorkerTarget) - if err != nil || discovered == nil { - return discovered, err - } - discovered.Source = "local_launch" - return discovered, nil -} - -func (i *LocalBrowserLaunchExtensionInjector) Close() error { - if i.CleanupPath != "" { - _ = os.RemoveAll(i.CleanupPath) - i.CleanupPath = "" - } - return nil -} - -func (i *LocalBrowserLaunchExtensionInjector) resolveExtensionID() (string, error) { - if i.ExtensionID != "" { - return i.ExtensionID, nil - } - if i.Options.InjectorExtensionID != "" { - i.ExtensionID = i.Options.InjectorExtensionID - } else if i.UnpackedExtensionPath != "" { - extensionID, err := extensionIDFromManifestKey(i.UnpackedExtensionPath) - if err != nil { - return "", err - } - i.ExtensionID = extensionID - } - if i.ExtensionID != "" { - i.Options.InjectorExtensionID = i.ExtensionID - } - return i.ExtensionID, nil -} diff --git a/go/modcdp/injector/LocalBrowserLaunchExtensionInjector_test.go b/go/modcdp/injector/LocalBrowserLaunchExtensionInjector_test.go deleted file mode 100644 index 3d190463..00000000 --- a/go/modcdp/injector/LocalBrowserLaunchExtensionInjector_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package injector_test - -import ( - "archive/zip" - . "github.com/browserbase/modcdp/go/modcdp/injector" - "os" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestLocalBrowserLaunchExtensionInjectorRejectsZipEntriesOutsideExtractionDir(t *testing.T) { - tempDir := t.TempDir() - zipPath := filepath.Join(tempDir, "extension.zip") - file, err := os.Create(zipPath) - if err != nil { - t.Fatal(err) - } - writer := zip.NewWriter(file) - entry, err := writer.Create("../evil.txt") - if err != nil { - t.Fatal(err) - } - if _, err := entry.Write([]byte("evil")); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - if err := file.Close(); err != nil { - t.Fatal(err) - } - - injector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{InjectorExtensionPath: zipPath}) - if err := injector.Prepare(); err == nil || !strings.Contains(err.Error(), "escapes extension extraction directory") { - t.Fatalf("Prepare error = %v", err) - } - if _, err := os.Stat(filepath.Join(tempDir, "evil.txt")); !os.IsNotExist(err) { - t.Fatalf("outside file was created: %v", err) - } -} - -func TestLocalBrowserLaunchExtensionInjectorPreparesUnpackedExtensionDirectoryForLoadExtension(t *testing.T) { - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - injector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{InjectorExtensionPath: extensionPath}) - if err := injector.Prepare(); err != nil { - t.Fatal(err) - } - defer injector.Close() - - if injector.UnpackedExtensionPath == "" { - t.Fatal("expected UnpackedExtensionPath") - } - if injector.UnpackedExtensionPath == extensionPath { - t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) - } - if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { - t.Fatalf("expected unpacked manifest: %v", err) - } - launcherConfig := injector.GetLauncherConfig() - if len(launcherConfig.ExtraArgs) != 1 || launcherConfig.ExtraArgs[0] != "--load-extension="+injector.UnpackedExtensionPath { - t.Fatalf("ExtraArgs = %#v", launcherConfig.ExtraArgs) - } - if injector.Options.InjectorExtensionID != DefaultModCDPExtensionID { - t.Fatalf("InjectorExtensionID = %q", injector.Options.InjectorExtensionID) - } -} - -func TestLocalBrowserLaunchExtensionInjectorPreparesDefaultExtensionZipForLoadExtension(t *testing.T) { - injector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{}) - if err := injector.Prepare(); err != nil { - t.Fatal(err) - } - defer injector.Close() - - if injector.UnpackedExtensionPath == "" { - t.Fatal("expected UnpackedExtensionPath") - } - if !strings.Contains(injector.UnpackedExtensionPath, "modcdp-extension-") { - t.Fatalf("UnpackedExtensionPath = %q", injector.UnpackedExtensionPath) - } - if _, err := os.Stat(filepath.Join(injector.UnpackedExtensionPath, "manifest.json")); err != nil { - t.Fatalf("expected unpacked manifest: %v", err) - } - launcherConfig := injector.GetLauncherConfig() - if len(launcherConfig.ExtraArgs) != 1 || launcherConfig.ExtraArgs[0] != "--load-extension="+injector.UnpackedExtensionPath { - t.Fatalf("ExtraArgs = %#v", launcherConfig.ExtraArgs) - } - if injector.Options.InjectorExtensionID != DefaultModCDPExtensionID { - t.Fatalf("InjectorExtensionID = %q", injector.Options.InjectorExtensionID) - } -} - -func TestLocalBrowserLaunchExtensionInjectorReturnsImmediatelyWhenLaunchedExtensionTargetIsAbsent(t *testing.T) { - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - methods := []string{} - injector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{ - InjectorExtensionPath: extensionPath, - InjectorTrustServiceWorkerTarget: true, - Send: func(method string, params map[string]any, sessionID string) (map[string]any, error) { - methods = append(methods, method) - if method == "Target.getTargets" { - return map[string]any{"targetInfos": []any{}}, nil - } - t.Fatalf("unexpected %s", method) - return nil, nil - }, - }) - if err := injector.Prepare(); err != nil { - t.Fatal(err) - } - defer injector.Close() - - startedAt := time.Now() - result, err := injector.Inject() - if err != nil { - t.Fatal(err) - } - elapsed := time.Since(startedAt) - if result != nil { - t.Fatalf("result = %#v", result) - } - if strings.Join(methods, ",") != "Target.getTargets" { - t.Fatalf("methods = %#v", methods) - } - if elapsed >= 200*time.Millisecond { - t.Fatalf("Inject took %s", elapsed) - } -} diff --git a/go/modcdp/injector/NodeExtensionFiles.go b/go/modcdp/injector/NodeExtensionFiles.go new file mode 100644 index 00000000..616ed823 --- /dev/null +++ b/go/modcdp/injector/NodeExtensionFiles.go @@ -0,0 +1,199 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/injector/NodeExtensionFiles.ts +// - ./python/modcdp/injector/NodeExtensionFiles.py +package injector + +import ( + "archive/zip" + "bytes" + "crypto/sha256" + _ "embed" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" +) + +//go:embed extension.zip +var bundledExtensionZip []byte + +type PreparedExtension struct { + UnpackedExtensionPath string + CleanupPath string +} + +func DefaultModCDPExtensionPath() string { + return "" +} + +func PrepareUnpackedExtension(extensionPath string) (*PreparedExtension, error) { + dir, err := os.MkdirTemp("", "modcdp-extension-") + if err != nil { + return nil, err + } + if extensionPath == "" { + reader, err := zip.NewReader(bytes.NewReader(bundledExtensionZip), int64(len(bundledExtensionZip))) + if err != nil { + _ = os.RemoveAll(dir) + return nil, err + } + if err := extractZipFiles(reader.File, dir); err != nil { + _ = os.RemoveAll(dir) + return nil, err + } + return &PreparedExtension{UnpackedExtensionPath: extensionRoot(dir), CleanupPath: dir}, nil + } + if !strings.HasSuffix(extensionPath, ".zip") { + if err := copyDir(extensionPath, dir); err != nil { + _ = os.RemoveAll(dir) + return nil, err + } + return &PreparedExtension{UnpackedExtensionPath: extensionRoot(dir), CleanupPath: dir}, nil + } + reader, err := zip.OpenReader(extensionPath) + if err != nil { + _ = os.RemoveAll(dir) + return nil, err + } + defer reader.Close() + if err := extractZipFiles(reader.File, dir); err != nil { + _ = os.RemoveAll(dir) + return nil, err + } + return &PreparedExtension{UnpackedExtensionPath: extensionRoot(dir), CleanupPath: dir}, nil +} + +func ExtensionIDFromManifestKey(extensionPath string) (string, error) { + manifestBytes, err := os.ReadFile(filepath.Join(extensionPath, "manifest.json")) + if err != nil { + return "", nil + } + var manifest map[string]any + if err := json.Unmarshal(manifestBytes, &manifest); err != nil { + return "", err + } + key, _ := manifest["key"].(string) + if strings.TrimSpace(key) == "" { + return "", nil + } + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", err + } + digest := sha256.Sum256(keyBytes) + alphabet := "abcdefghijklmnop" + result := strings.Builder{} + for _, value := range digest[:16] { + result.WriteByte(alphabet[value>>4]) + result.WriteByte(alphabet[value&0x0f]) + } + return result.String(), nil +} + +func extractZipFiles(files []*zip.File, dir string) error { + root, err := filepath.Abs(dir) + if err != nil { + return err + } + for _, file := range files { + targetPath, err := safeZipTarget(root, file.Name) + if err != nil { + return err + } + if file.FileInfo().IsDir() { + if err := os.MkdirAll(targetPath, 0o755); err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + return err + } + src, err := file.Open() + if err != nil { + return err + } + dst, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.FileInfo().Mode()) + if err != nil { + _ = src.Close() + return err + } + _, copyErr := io.Copy(dst, src) + srcErr := src.Close() + dstErr := dst.Close() + if copyErr != nil { + return copyErr + } + if srcErr != nil { + return srcErr + } + if dstErr != nil { + return dstErr + } + } + return nil +} + +func safeZipTarget(root string, name string) (string, error) { + cleanName := filepath.Clean(name) + if filepath.IsAbs(cleanName) || cleanName == "." || cleanName == ".." || strings.HasPrefix(cleanName, ".."+string(os.PathSeparator)) { + return "", fmt.Errorf("zip entry %q escapes extension extraction directory", name) + } + targetPath := filepath.Join(root, cleanName) + targetAbs, err := filepath.Abs(targetPath) + if err != nil { + return "", err + } + if targetAbs != root && !strings.HasPrefix(targetAbs, root+string(os.PathSeparator)) { + return "", fmt.Errorf("zip entry %q escapes extension extraction directory", name) + } + return targetAbs, nil +} + +func extensionRoot(unpackedPath string) string { + if _, err := os.Stat(filepath.Join(unpackedPath, "manifest.json")); err == nil { + return unpackedPath + } + nested := filepath.Join(unpackedPath, "extension") + if _, err := os.Stat(filepath.Join(nested, "manifest.json")); err == nil { + return nested + } + return unpackedPath +} + +func copyDir(src string, dst string) error { + return filepath.WalkDir(src, func(path string, entry fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + relative, err := filepath.Rel(src, path) + if err != nil { + return err + } + target := filepath.Join(dst, relative) + if entry.IsDir() { + return os.MkdirAll(target, 0o755) + } + info, err := entry.Info() + if err != nil { + return err + } + sourceFile, err := os.Open(path) + if err != nil { + return err + } + defer sourceFile.Close() + targetFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + _, err = io.Copy(targetFile, sourceFile) + return err + }) +} diff --git a/go/modcdp/injector/extension.zip b/go/modcdp/injector/extension.zip index 8032e35e..e68ec9f4 100644 Binary files a/go/modcdp/injector/extension.zip and b/go/modcdp/injector/extension.zip differ diff --git a/go/modcdp/injector/helpers_test.go b/go/modcdp/injector/helpers_test.go deleted file mode 100644 index 37dba225..00000000 --- a/go/modcdp/injector/helpers_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package injector_test - -import ( - "io" - "os" - "path/filepath" -) - -func boolPtr(value bool) *bool { - return &value -} - -func copyDir(src string, dst string) error { - return filepath.WalkDir(src, func(path string, entry os.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - relPath, err := filepath.Rel(src, path) - if err != nil { - return err - } - targetPath := filepath.Join(dst, relPath) - if entry.IsDir() { - return os.MkdirAll(targetPath, 0o755) - } - source, err := os.Open(path) - if err != nil { - return err - } - defer source.Close() - info, err := entry.Info() - if err != nil { - return err - } - target, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) - if err != nil { - return err - } - defer target.Close() - _, err = io.Copy(target, source) - return err - }) -} diff --git a/go/modcdp/launcher/BrowserbaseBrowserLauncher.go b/go/modcdp/launcher/BBBrowserLauncher.go similarity index 78% rename from go/modcdp/launcher/BrowserbaseBrowserLauncher.go rename to go/modcdp/launcher/BBBrowserLauncher.go index 34f24945..c8c9dad3 100644 --- a/go/modcdp/launcher/BrowserbaseBrowserLauncher.go +++ b/go/modcdp/launcher/BBBrowserLauncher.go @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/launcher/BBBrowserLauncher.ts +// - ./python/modcdp/launcher/BBBrowserLauncher.py package launcher import ( @@ -19,7 +23,7 @@ const DefaultBrowserbaseLauncherBaseURL = "https://api.browserbase.com" var defaultBrowserbaseViewport = map[string]any{"width": 1288, "height": 711} -type BrowserbaseBrowserLauncher struct { +type BBBrowserLauncher struct { BrowserLauncher } @@ -31,21 +35,22 @@ type browserbaseSession struct { Status string `json:"status"` } -func NewBrowserbaseBrowserLauncher(options LaunchOptions) *BrowserbaseBrowserLauncher { - return &BrowserbaseBrowserLauncher{BrowserLauncher: NewBrowserLauncher(options)} +func NewBBBrowserLauncher(config LauncherConfig) *BBBrowserLauncher { + config.LauncherMode = "bb" + return &BBBrowserLauncher{BrowserLauncher: NewBrowserLauncher(config)} } -func (l *BrowserbaseBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, error) { - merged := mergeLaunchOptions(l.Options, options) - browserbaseAPIKey := firstString(merged.BrowserbaseAPIKey, os.Getenv("BROWSERBASE_API_KEY")) +func (l *BBBrowserLauncher) Launch(config LauncherConfig) (*LaunchedBrowser, error) { + merged := mergeLaunchConfig(l.Config, config) + browserbaseAPIKey := firstString(merged.LauncherBBAPIKey, os.Getenv("BROWSERBASE_API_KEY")) if browserbaseAPIKey == "" { - return nil, fmt.Errorf("launcher.launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key") + return nil, fmt.Errorf("launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_bb_api_key.") } - baseURL := firstString(merged.BrowserbaseBaseURL, os.Getenv("BROWSERBASE_BASE_URL"), DefaultBrowserbaseLauncherBaseURL) - resumeSessionID := firstString(merged.BrowserbaseSessionID) - keepAlive := boolValue(merged.BrowserbaseKeepAlive, false) - closeSessionOnClose := boolValue(merged.BrowserbaseCloseSessionOnClose, !keepAlive) + baseURL := merged.LauncherBBBaseURL + resumeSessionID := firstString(merged.LauncherBBSessionID) + keepAlive := boolValue(merged.LauncherBBKeepAlive, false) + closeSessionOnClose := boolValue(merged.LauncherBBCloseSessionOnClose, !keepAlive) createdSession := false var session browserbaseSession @@ -53,11 +58,11 @@ func (l *BrowserbaseBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBro if resumeSessionID != "" { err = browserbaseRequest(baseURL, browserbaseAPIKey, http.MethodGet, "/v1/sessions/"+resumeSessionID, nil, &session) } else { - sessionCreateParams := objectValue(merged.BrowserbaseSessionCreateParams) - browserSettings := mergeMap(objectValue(sessionCreateParams["browserSettings"]), objectValue(merged.BrowserbaseBrowserSettings)) - userMetadata := mergeMap(objectValue(sessionCreateParams["userMetadata"]), objectValue(merged.BrowserbaseUserMetadata)) + sessionCreateParams := objectValue(merged.LauncherBBSessionCreateParams) + browserSettings := mergeMap(objectValue(sessionCreateParams["browserSettings"]), objectValue(merged.LauncherBBBrowserSettings)) + userMetadata := mergeMap(objectValue(sessionCreateParams["userMetadata"]), objectValue(merged.LauncherBBUserMetadata)) extensionID := firstString( - merged.InjectorExtensionID, + merged.LauncherBBExtensionID, stringValue(sessionCreateParams["extensionId"]), stringValue(objectValue(sessionCreateParams["browserSettings"])["extensionId"]), ) @@ -65,11 +70,11 @@ func (l *BrowserbaseBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBro if keepAlive { body["keepAlive"] = true } - if region := firstString(merged.Region, stringValue(sessionCreateParams["region"])); region != "" { + if region := firstString(merged.LauncherBBRegion, stringValue(sessionCreateParams["region"])); region != "" { body["region"] = region } - if merged.Timeout != 0 { - body["timeout"] = merged.Timeout + if merged.LauncherBBTimeout != 0 { + body["timeout"] = merged.LauncherBBTimeout } if extensionID != "" { body["extensionId"] = extensionID diff --git a/go/modcdp/launcher/BBBrowserLauncher_test.go b/go/modcdp/launcher/BBBrowserLauncher_test.go new file mode 100644 index 00000000..b917f05e --- /dev/null +++ b/go/modcdp/launcher/BBBrowserLauncher_test.go @@ -0,0 +1,171 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.BBBrowserLauncher.ts +// - ./python/tests/test_BBBrowserLauncher.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package launcher_test + +import ( + "encoding/json" + "io" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/transport" +) + +func TestCreatesVerifiesResumesAndReleasesARealBrowserbaseBrowserSession(t *testing.T) { + if strings.TrimSpace(os.Getenv("BROWSERBASE_API_KEY")) == "" { + t.Fatal("BROWSERBASE_API_KEY is required for live Browserbase tests") + } + config := launcher.LauncherConfig{ + LauncherBBTimeout: 120, + LauncherBBBrowserSettings: map[string]any{ + "viewport": map[string]any{"width": 900, "height": 700}, + "recordSession": false, + }, + LauncherBBUserMetadata: map[string]any{ + "modcdp_launcher_test": "BBBrowserLauncher", + }, + } + if region := os.Getenv("BROWSERBASE_REGION"); region != "" { + config.LauncherBBRegion = region + } + bb_launcher := launcher.NewBBBrowserLauncher(config) + browser, err := bb_launcher.Launch(launcher.LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + var resumed *launcher.LaunchedBrowser + var cdp_transport *transport.WSUpstreamTransport + defer func() { + if cdp_transport != nil { + _ = cdp_transport.Close() + } + if resumed != nil { + resumed.Close() + } + browser.Close() + browser.Close() + }() + + if browser.BrowserbaseSessionID == "" { + t.Fatal("expected browserbase session id") + } + if bb_launcher.Launched != browser { + t.Fatal("expected launcher to retain launched browser") + } + transportConfig := bb_launcher.ConfigForUpstream() + if transportConfig["upstream_ws_cdp_url"] != browser.CDPURL { + t.Fatalf("transport cdp_url = %v, want %s", transportConfig["upstream_ws_cdp_url"], browser.CDPURL) + } + if !strings.Contains(browser.BrowserbaseSessionURL, browser.BrowserbaseSessionID) { + t.Fatalf("browserbase session url = %q", browser.BrowserbaseSessionURL) + } + if !strings.HasPrefix(browser.CDPURL, "wss://") { + t.Fatalf("ws url = %q", browser.CDPURL) + } + cdp_transport = connectBrowserbaseCDP(t, browser.CDPURL) + expectBrowserbaseCDPBrowserSurface(t, cdp_transport) + + retrieved := retrieveBrowserbaseSession(t, browser.BrowserbaseSessionID) + if retrieved["id"] != browser.BrowserbaseSessionID { + t.Fatalf("retrieved id = %v", retrieved["id"]) + } + if retrieved["status"] != "RUNNING" { + t.Fatalf("retrieved status = %v", retrieved["status"]) + } + + closeSessionOnClose := false + resumed, err = launcher.NewBBBrowserLauncher(launcher.LauncherConfig{ + LauncherBBSessionID: browser.BrowserbaseSessionID, + LauncherBBCloseSessionOnClose: &closeSessionOnClose, + }).Launch(launcher.LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + if resumed.BrowserbaseSessionID != browser.BrowserbaseSessionID { + t.Fatalf("resumed session id = %q", resumed.BrowserbaseSessionID) + } + if !strings.HasPrefix(resumed.CDPURL, "wss://") { + t.Fatalf("resumed ws url = %q", resumed.CDPURL) + } + expectBrowserbaseCDPBrowserSurface(t, cdp_transport) + + _ = cdp_transport.Close() + cdp_transport = nil + resumed.Close() + browser.Close() + browser.Close() + + deadline := time.Now().Add(30 * time.Second) + for time.Now().Before(deadline) { + if retrieveBrowserbaseSession(t, browser.BrowserbaseSessionID)["status"] != "RUNNING" { + return + } + time.Sleep(time.Second) + } + t.Fatal("Browserbase session did not leave RUNNING status after release") +} + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep the setup semantics above 1:1 with translated tests; helpers here only call real Browserbase APIs and real CDP endpoints. +func connectBrowserbaseCDP(t *testing.T, rawURL string) *transport.WSUpstreamTransport { + t.Helper() + cdp_transport := transport.NewWSUpstreamTransport(transport.UpstreamTransportConfig{ + UpstreamWSCDPURL: rawURL, + UpstreamCDPSendTimeoutMS: 120_000, + }) + if err := cdp_transport.Connect(); err != nil { + t.Fatal(err) + } + return cdp_transport +} + +func expectBrowserbaseCDPBrowserSurface(t *testing.T, cdp_transport *transport.WSUpstreamTransport) { + t.Helper() + result, err := cdp_transport.Send("Browser.getVersion", map[string]any{}, "", 120*time.Second) + if err != nil { + t.Fatal(err) + } + product, _ := result["product"].(string) + if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { + t.Fatalf("Browser.getVersion result = %#v", result) + } +} + +func retrieveBrowserbaseSession(t *testing.T, sessionID string) map[string]any { + t.Helper() + request, err := http.NewRequest(http.MethodGet, browserbaseAPIURL("/v1/sessions/"+sessionID), nil) + if err != nil { + t.Fatal(err) + } + request.Header.Set("x-bb-api-key", os.Getenv("BROWSERBASE_API_KEY")) + response, err := http.DefaultClient.Do(request) + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + body, _ := io.ReadAll(response.Body) + if response.StatusCode < 200 || response.StatusCode >= 300 { + t.Fatalf("Browserbase session fetch returned %d: %s", response.StatusCode, string(body)) + } + var session map[string]any + if err := json.Unmarshal(body, &session); err != nil { + t.Fatal(err) + } + return session +} + +func browserbaseAPIURL(pathname string) string { + baseURL := os.Getenv("BROWSERBASE_BASE_URL") + if baseURL == "" { + baseURL = launcher.DefaultBrowserbaseLauncherBaseURL + } + return strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(pathname, "/") +} diff --git a/go/modcdp/launcher/BrowserLauncher.go b/go/modcdp/launcher/BrowserLauncher.go index b4944d23..f2c78c37 100644 --- a/go/modcdp/launcher/BrowserLauncher.go +++ b/go/modcdp/launcher/BrowserLauncher.go @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/launcher/BrowserLauncher.ts +// - ./python/modcdp/launcher/BrowserLauncher.py package launcher import ( @@ -6,26 +10,19 @@ import ( "io" "net" "net/http" - "net/url" - "os" "strings" "time" "github.com/browserbase/modcdp/go/modcdp/types" ) -type LaunchOptions = types.LaunchOptions -type ExtensionInjectorConfig = types.ExtensionInjectorConfig +type LauncherConfig = types.LauncherConfig +type InjectorConfig = types.InjectorConfig +type UpstreamTransportConfig = types.UpstreamTransportConfig const DefaultChromeReadyTimeoutMS = 45_000 const DefaultChromeReadyPollIntervalMS = 100 -func boolPtr(value bool) *bool { - return &value -} - -var writePipeMessage = WritePipeMessage -var readPipeMessage = ReadPipeMessage var cdpHTTPClient = &http.Client{Timeout: 2 * time.Second} func freePort() (int, error) { @@ -37,10 +34,6 @@ func freePort() (int, error) { return listener.Addr().(*net.TCPAddr).Port, nil } -func websocketURLFor(endpoint string) (string, error) { - return WebsocketURLFor(endpoint) -} - func WebsocketURLFor(endpoint string) (string, error) { if strings.HasPrefix(endpoint, "ws://") || strings.HasPrefix(endpoint, "wss://") { return endpoint, nil @@ -54,20 +47,8 @@ func WebsocketURLFor(endpoint string) (string, error) { return "", fmt.Errorf("GET /json/version: %w", err) } defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - parsed, parseErr := url.Parse(httpEndpoint) - if parseErr != nil { - return "", parseErr - } - if parsed.Scheme == "https" { - parsed.Scheme = "wss" - } else { - parsed.Scheme = "ws" - } - parsed.Path = "/devtools/browser" - parsed.RawQuery = "" - parsed.Fragment = "" - return parsed.String(), nil + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return "", fmt.Errorf("GET %s/json/version -> %d", httpEndpoint, resp.StatusCode) } body, _ := io.ReadAll(resp.Body) var version map[string]any @@ -76,139 +57,157 @@ func WebsocketURLFor(endpoint string) (string, error) { } wsURL, _ := version["webSocketDebuggerUrl"].(string) if wsURL == "" { - return "", fmt.Errorf("HTTP discovery for %s returned no webSocketDebuggerUrl", endpoint) + return "", fmt.Errorf("cdp_url HTTP discovery returned no webSocketDebuggerUrl") } return wsURL, nil } type LaunchedBrowser struct { - // CDPURL is the effective CDP endpoint for the selected transport; launchers resolve HTTP discovery endpoints to ws:// before returning when they can. - CDPURL string `json:"cdp_url,omitempty"` - LoopbackCDPURL string `json:"loopback_cdp_url,omitempty"` - Close func() `json:"-"` - ProfileDir string `json:"profile_dir,omitempty"` - PipeRead *os.File `json:"-"` - PipeWrite *os.File `json:"-"` - BrowserbaseSessionID string `json:"browserbase_session_id,omitempty"` - BrowserbaseSessionURL string `json:"browserbase_session_url,omitempty"` - BrowserbaseDebugURL string `json:"browserbase_debug_url,omitempty"` + CDPURL string `json:"cdp_url,omitempty"` + CDPListenPort int `json:"cdp_listen_port,omitempty"` + LoopbackCDPURL string `json:"loopback_cdp_url,omitempty"` + Close func() `json:"-"` + ProfileDir string `json:"profile_dir,omitempty"` + BrowserbaseSessionID string `json:"browserbase_session_id,omitempty"` + BrowserbaseSessionURL string `json:"browserbase_session_url,omitempty"` + BrowserbaseDebugURL string `json:"browserbase_debug_url,omitempty"` } type BrowserLauncher struct { - Options LaunchOptions + Config LauncherConfig Launched *LaunchedBrowser } -func NewBrowserLauncher(options LaunchOptions) BrowserLauncher { - return BrowserLauncher{Options: options} +func NewBrowserLauncher(config LauncherConfig) BrowserLauncher { + if config.LauncherBBBaseURL == "" { + config.LauncherBBBaseURL = DefaultBrowserbaseLauncherBaseURL + } + return BrowserLauncher{Config: config} } -func (l *BrowserLauncher) Update(config LaunchOptions) *BrowserLauncher { - l.Options = mergeLaunchOptions(l.Options, config) +func (l *BrowserLauncher) Update(config LauncherConfig) *BrowserLauncher { + l.Config = mergeLaunchConfig(l.Config, config) return l } -func (l BrowserLauncher) GetTransportConfig() map[string]any { - return map[string]any{ - "cdp_url": firstString(launchedCDPURL(l.Launched), l.Options.CDPURL), - "user_data_dir": firstString(launchedProfileDir(l.Launched), l.Options.UserDataDir), - "pipe_read": launchedPipeRead(l.Launched), - "pipe_write": launchedPipeWrite(l.Launched), +func (l BrowserLauncher) ConfigForUpstream() map[string]any { + config := map[string]any{} + upstreamWSCDPURL := firstString(launchedCDPURL(l.Launched), l.Config.LauncherRemoteCDPURL) + if upstreamWSCDPURL != "" { + config["upstream_ws_cdp_url"] = upstreamWSCDPURL } + return config } -func (l BrowserLauncher) GetServerConfig() map[string]any { +func (l BrowserLauncher) ConfigForServer(upstreamConfig UpstreamTransportConfig) map[string]any { + launcherLocalLoopbackCDPURL := "" if l.Launched != nil && l.Launched.LoopbackCDPURL != "" { - return map[string]any{"server_loopback_cdp_url": l.Launched.LoopbackCDPURL} + launcherLocalLoopbackCDPURL = l.Launched.LoopbackCDPURL + } else if upstreamConfig.UpstreamMode == "ws" && upstreamConfig.UpstreamWSCDPURL != "" { + launcherLocalLoopbackCDPURL = upstreamConfig.UpstreamWSCDPURL + } + if launcherLocalLoopbackCDPURL != "" { + return map[string]any{"upstream": map[string]any{"upstream_mode": "ws", "upstream_ws_cdp_url": launcherLocalLoopbackCDPURL}} } return map[string]any{} } -func (l BrowserLauncher) GetInjectorConfig() ExtensionInjectorConfig { - return ExtensionInjectorConfig{ - InjectorBrowserbaseAPIKey: l.Options.BrowserbaseAPIKey, - InjectorBrowserbaseBaseURL: l.Options.BrowserbaseBaseURL, - InjectorExtensionID: l.Options.InjectorExtensionID, +func (l BrowserLauncher) Launch(config LauncherConfig) (*LaunchedBrowser, error) { + return nil, fmt.Errorf("%T.Launch is not implemented", l) +} + +func (l *BrowserLauncher) Close() { + launched := l.Launched + l.Launched = nil + if launched != nil && launched.Close != nil { + launched.Close() } } -func (l BrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, error) { - return nil, fmt.Errorf("%T.Launch is not implemented", l) +func (l BrowserLauncher) ToJSON() map[string]any { + state := map[string]any{"launched": l.Launched != nil} + if l.Launched != nil { + state["cdp_url"] = l.Launched.CDPURL + state["loopback_cdp_url"] = l.Launched.LoopbackCDPURL + state["cdp_listen_port"] = l.Launched.CDPListenPort + state["profile_dir"] = l.Launched.ProfileDir + state["browserbase_session_id"] = l.Launched.BrowserbaseSessionID + state["browserbase_session_url"] = l.Launched.BrowserbaseSessionURL + state["browserbase_debug_url"] = l.Launched.BrowserbaseDebugURL + } + return types.ModCDPToJSON(l, types.ModCDPJSONConfig{State: state}) } -func mergeLaunchOptions(existing LaunchOptions, incoming LaunchOptions) LaunchOptions { +func mergeLaunchConfig(existing LauncherConfig, incoming LauncherConfig) LauncherConfig { merged := existing - if incoming.ExecutablePath != "" { - merged.ExecutablePath = incoming.ExecutablePath - } - if incoming.Port != 0 { - merged.Port = incoming.Port + if incoming.LauncherLocalExecutablePath != "" { + merged.LauncherLocalExecutablePath = incoming.LauncherLocalExecutablePath } - if incoming.RemoteDebugging != "" { - merged.RemoteDebugging = incoming.RemoteDebugging + if incoming.LauncherLocalCDPListenPort != 0 { + merged.LauncherLocalCDPListenPort = incoming.LauncherLocalCDPListenPort } - if incoming.LoopbackCDP != nil { - merged.LoopbackCDP = incoming.LoopbackCDP + if incoming.LauncherLocalLoopbackCDP != nil { + merged.LauncherLocalLoopbackCDP = incoming.LauncherLocalLoopbackCDP } - if incoming.UserDataDir != "" { - merged.UserDataDir = incoming.UserDataDir + if incoming.LauncherLocalUserDataDir != "" { + merged.LauncherLocalUserDataDir = incoming.LauncherLocalUserDataDir } - if incoming.CleanupUserDataDir != nil { - merged.CleanupUserDataDir = incoming.CleanupUserDataDir + if incoming.LauncherLocalCleanupUserDataDir != nil { + merged.LauncherLocalCleanupUserDataDir = incoming.LauncherLocalCleanupUserDataDir } - if incoming.ChromeReadyTimeoutMS != 0 { - merged.ChromeReadyTimeoutMS = incoming.ChromeReadyTimeoutMS + if incoming.LauncherLocalChromeReadyTimeoutMS != 0 { + merged.LauncherLocalChromeReadyTimeoutMS = incoming.LauncherLocalChromeReadyTimeoutMS } - if incoming.ChromeReadyPollIntervalMS != 0 { - merged.ChromeReadyPollIntervalMS = incoming.ChromeReadyPollIntervalMS + if incoming.LauncherLocalChromeReadyPollIntervalMS != 0 { + merged.LauncherLocalChromeReadyPollIntervalMS = incoming.LauncherLocalChromeReadyPollIntervalMS } - if incoming.Headless != nil { - merged.Headless = incoming.Headless + if incoming.LauncherLocalHeadless != nil { + merged.LauncherLocalHeadless = incoming.LauncherLocalHeadless } - if incoming.Sandbox != nil { - merged.Sandbox = incoming.Sandbox + if incoming.LauncherLocalSandbox != nil { + merged.LauncherLocalSandbox = incoming.LauncherLocalSandbox } - if len(incoming.Args) > 0 { - merged.Args = mergeChromeArgs(existing.Args, incoming.Args) + if len(incoming.LauncherLocalArgs) > 0 { + merged.LauncherLocalArgs = mergeChromeArgs(existing.LauncherLocalArgs, incoming.LauncherLocalArgs) } - if len(incoming.ExtraArgs) > 0 { - merged.ExtraArgs = mergeChromeArgs(existing.ExtraArgs, incoming.ExtraArgs) + if len(incoming.LauncherLocalExtraArgs) > 0 { + merged.LauncherLocalExtraArgs = mergeChromeArgs(existing.LauncherLocalExtraArgs, incoming.LauncherLocalExtraArgs) } - if incoming.CDPURL != "" { - merged.CDPURL = incoming.CDPURL + if incoming.LauncherRemoteCDPURL != "" { + merged.LauncherRemoteCDPURL = incoming.LauncherRemoteCDPURL } - if incoming.BrowserbaseAPIKey != "" { - merged.BrowserbaseAPIKey = incoming.BrowserbaseAPIKey + if incoming.LauncherBBAPIKey != "" { + merged.LauncherBBAPIKey = incoming.LauncherBBAPIKey } - if incoming.BrowserbaseBaseURL != "" { - merged.BrowserbaseBaseURL = incoming.BrowserbaseBaseURL + if incoming.LauncherBBBaseURL != "" { + merged.LauncherBBBaseURL = incoming.LauncherBBBaseURL } - if incoming.BrowserbaseSessionID != "" { - merged.BrowserbaseSessionID = incoming.BrowserbaseSessionID + if incoming.LauncherBBSessionID != "" { + merged.LauncherBBSessionID = incoming.LauncherBBSessionID } - if incoming.BrowserbaseKeepAlive != nil { - merged.BrowserbaseKeepAlive = incoming.BrowserbaseKeepAlive + if incoming.LauncherBBKeepAlive != nil { + merged.LauncherBBKeepAlive = incoming.LauncherBBKeepAlive } - if incoming.BrowserbaseCloseSessionOnClose != nil { - merged.BrowserbaseCloseSessionOnClose = incoming.BrowserbaseCloseSessionOnClose + if incoming.LauncherBBCloseSessionOnClose != nil { + merged.LauncherBBCloseSessionOnClose = incoming.LauncherBBCloseSessionOnClose } - if incoming.Region != "" { - merged.Region = incoming.Region + if incoming.LauncherBBRegion != "" { + merged.LauncherBBRegion = incoming.LauncherBBRegion } - if incoming.Timeout != 0 { - merged.Timeout = incoming.Timeout + if incoming.LauncherBBTimeout != 0 { + merged.LauncherBBTimeout = incoming.LauncherBBTimeout } - if incoming.InjectorExtensionID != "" { - merged.InjectorExtensionID = incoming.InjectorExtensionID + if incoming.LauncherBBExtensionID != "" { + merged.LauncherBBExtensionID = incoming.LauncherBBExtensionID } - if incoming.BrowserbaseBrowserSettings != nil { - merged.BrowserbaseBrowserSettings = incoming.BrowserbaseBrowserSettings + if incoming.LauncherBBBrowserSettings != nil { + merged.LauncherBBBrowserSettings = incoming.LauncherBBBrowserSettings } - if incoming.BrowserbaseUserMetadata != nil { - merged.BrowserbaseUserMetadata = incoming.BrowserbaseUserMetadata + if incoming.LauncherBBUserMetadata != nil { + merged.LauncherBBUserMetadata = incoming.LauncherBBUserMetadata } - if incoming.BrowserbaseSessionCreateParams != nil { - merged.BrowserbaseSessionCreateParams = incoming.BrowserbaseSessionCreateParams + if incoming.LauncherBBSessionCreateParams != nil { + merged.LauncherBBSessionCreateParams = incoming.LauncherBBSessionCreateParams } return merged } @@ -277,17 +276,3 @@ func launchedProfileDir(launched *LaunchedBrowser) string { } return launched.ProfileDir } - -func launchedPipeRead(launched *LaunchedBrowser) *os.File { - if launched == nil { - return nil - } - return launched.PipeRead -} - -func launchedPipeWrite(launched *LaunchedBrowser) *os.File { - if launched == nil { - return nil - } - return launched.PipeWrite -} diff --git a/go/modcdp/launcher/BrowserLauncher_test.go b/go/modcdp/launcher/BrowserLauncher_test.go index a2b62c3b..b83e1ba4 100644 --- a/go/modcdp/launcher/BrowserLauncher_test.go +++ b/go/modcdp/launcher/BrowserLauncher_test.go @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.BrowserLauncher.ts +// - ./python/tests/test_BrowserLauncher.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package launcher import ( @@ -5,53 +11,36 @@ import ( "testing" ) -func TestBrowserLauncherMergesLaunchConfigAndExposesTransportAndInjectorConfig(t *testing.T) { - launcher := NewBrowserLauncher(LaunchOptions{ - CDPURL: "ws://127.0.0.1:9222/devtools/browser/initial", - UserDataDir: "/tmp/modcdp-browser-launcher", - BrowserbaseAPIKey: "test-key", - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - Args: []string{"--load-extension=/tmp/args-one"}, - ExtraArgs: []string{"--load-extension=/tmp/one"}, +func TestMergesConfigAndExposesUpstreamConfig(t *testing.T) { + launcher := NewBrowserLauncher(LauncherConfig{ + LauncherRemoteCDPURL: "ws://127.0.0.1:9222/devtools/browser/initial", + LauncherLocalUserDataDir: "/tmp/modcdp-browser-launcher", }) - launcher.Update(LaunchOptions{ - CDPURL: "ws://127.0.0.1:9222/devtools/browser/updated", - Args: []string{"--load-extension=/tmp/args-two", "--lang=en-US"}, - ExtraArgs: []string{"--load-extension=/tmp/two", "--window-size=900,700"}, + launcher.Update(LauncherConfig{ + LauncherRemoteCDPURL: "ws://127.0.0.1:9222/devtools/browser/updated", }) - assertStringsEqual(t, launcher.Options.Args, []string{"--lang=en-US", "--load-extension=/tmp/args-one,/tmp/args-two"}) - assertStringsEqual(t, launcher.Options.ExtraArgs, []string{"--window-size=900,700", "--load-extension=/tmp/one,/tmp/two"}) - - transportConfig := launcher.GetTransportConfig() - if transportConfig["cdp_url"] != "ws://127.0.0.1:9222/devtools/browser/updated" { - t.Fatalf("cdp_url = %v", transportConfig["cdp_url"]) - } - if transportConfig["user_data_dir"] != "/tmp/modcdp-browser-launcher" { - t.Fatalf("user_data_dir = %v", transportConfig["user_data_dir"]) + transportConfig := launcher.ConfigForUpstream() + if transportConfig["upstream_ws_cdp_url"] != "ws://127.0.0.1:9222/devtools/browser/updated" { + t.Fatalf("cdp_url = %v", transportConfig["upstream_ws_cdp_url"]) } - - injectorConfig := launcher.GetInjectorConfig() - if injectorConfig.InjectorBrowserbaseAPIKey != "test-key" { - t.Fatalf("InjectorBrowserbaseAPIKey = %v", injectorConfig.InjectorBrowserbaseAPIKey) + if launcher.Config.LauncherLocalUserDataDir != "/tmp/modcdp-browser-launcher" { + t.Fatalf("LauncherLocalUserDataDir = %q", launcher.Config.LauncherLocalUserDataDir) } - if injectorConfig.InjectorExtensionID != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { - t.Fatalf("InjectorExtensionID = %v", injectorConfig.InjectorExtensionID) - } - - if _, err := launcher.Launch(LaunchOptions{}); err == nil || !strings.Contains(err.Error(), "BrowserLauncher.Launch is not implemented") { + if _, err := launcher.Launch(LauncherConfig{}); err == nil || !strings.Contains(err.Error(), "BrowserLauncher.Launch is not implemented") { t.Fatalf("Launch error = %v", err) } } -func assertStringsEqual(t *testing.T, actual []string, expected []string) { - t.Helper() - if len(actual) != len(expected) { - t.Fatalf("len(%v) != len(%v)", actual, expected) - } - for index := range actual { - if actual[index] != expected[index] { - t.Fatalf("index %d: %q != %q in %v", index, actual[index], expected[index], actual) - } +func TestCarriesRemoteCDPConfigSeparatelyFromLaunchArgs(t *testing.T) { + launcher := NewBrowserLauncher(LauncherConfig{ + LauncherRemoteCDPURL: "ws://127.0.0.1:9222/devtools/browser/initial", + }) + launcher.Update(LauncherConfig{ + LauncherRemoteCDPURL: "ws://127.0.0.1:9222/devtools/browser/updated", + }) + + if launcher.Config.LauncherRemoteCDPURL != "ws://127.0.0.1:9222/devtools/browser/updated" { + t.Fatalf("LauncherRemoteCDPURL = %q", launcher.Config.LauncherRemoteCDPURL) } } diff --git a/go/modcdp/launcher/BrowserbaseBrowserLauncher_test.go b/go/modcdp/launcher/BrowserbaseBrowserLauncher_test.go deleted file mode 100644 index b225c9c7..00000000 --- a/go/modcdp/launcher/BrowserbaseBrowserLauncher_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package launcher - -import ( - "context" - "encoding/json" - "io" - "net/http" - "os" - "strings" - "testing" - "time" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -const liveBrowserbaseTimeout = 120 * time.Second - -func TestBrowserbaseBrowserLauncherCreatesVerifiesResumesAndReleasesRealSession(t *testing.T) { - if strings.TrimSpace(os.Getenv("BROWSERBASE_API_KEY")) == "" { - t.Fatal("BROWSERBASE_API_KEY is required for live Browserbase tests") - } - options := LaunchOptions{ - Timeout: 120, - BrowserbaseBrowserSettings: map[string]any{ - "viewport": map[string]any{"width": 900, "height": 700}, - "recordSession": false, - }, - BrowserbaseUserMetadata: map[string]any{ - "modcdp_launcher_test": "BrowserbaseBrowserLauncher", - }, - } - if region := os.Getenv("BROWSERBASE_REGION"); region != "" { - options.Region = region - } - launcher := NewBrowserbaseBrowserLauncher(options) - browser, err := launcher.Launch(LaunchOptions{}) - if err != nil { - t.Fatal(err) - } - var resumed *LaunchedBrowser - var conn io.ReadWriteCloser - defer func() { - if conn != nil { - _ = conn.Close() - } - if resumed != nil { - resumed.Close() - } - browser.Close() - browser.Close() - }() - - if browser.BrowserbaseSessionID == "" { - t.Fatal("expected browserbase session id") - } - if launcher.Launched != browser { - t.Fatal("expected launcher to retain launched browser") - } - transportConfig := launcher.GetTransportConfig() - if transportConfig["cdp_url"] != browser.CDPURL { - t.Fatalf("transport cdp_url = %v, want %s", transportConfig["cdp_url"], browser.CDPURL) - } - if !strings.Contains(browser.BrowserbaseSessionURL, browser.BrowserbaseSessionID) { - t.Fatalf("browserbase session url = %q", browser.BrowserbaseSessionURL) - } - if !strings.HasPrefix(browser.CDPURL, "wss://") { - t.Fatalf("ws url = %q", browser.CDPURL) - } - conn = connectBrowserbaseCDP(t, browser.CDPURL) - expectCDPBrowserSurface(t, conn) - - retrieved := retrieveBrowserbaseSession(t, browser.BrowserbaseSessionID) - if retrieved["id"] != browser.BrowserbaseSessionID { - t.Fatalf("retrieved id = %v", retrieved["id"]) - } - if retrieved["status"] != "RUNNING" { - t.Fatalf("retrieved status = %v", retrieved["status"]) - } - - closeSessionOnClose := false - resumed, err = NewBrowserbaseBrowserLauncher(LaunchOptions{ - BrowserbaseSessionID: browser.BrowserbaseSessionID, - BrowserbaseCloseSessionOnClose: &closeSessionOnClose, - }).Launch(LaunchOptions{}) - if err != nil { - t.Fatal(err) - } - if resumed.BrowserbaseSessionID != browser.BrowserbaseSessionID { - t.Fatalf("resumed session id = %q", resumed.BrowserbaseSessionID) - } - if !strings.HasPrefix(resumed.CDPURL, "wss://") { - t.Fatalf("resumed ws url = %q", resumed.CDPURL) - } - expectCDPBrowserSurface(t, conn) - - _ = conn.Close() - conn = nil - resumed.Close() - browser.Close() - browser.Close() - - deadline := time.Now().Add(30 * time.Second) - for time.Now().Before(deadline) { - if retrieveBrowserbaseSession(t, browser.BrowserbaseSessionID)["status"] != "RUNNING" { - return - } - time.Sleep(time.Second) - } - t.Fatal("Browserbase session did not leave RUNNING status after release") -} - -func connectBrowserbaseCDP(t *testing.T, rawURL string) io.ReadWriteCloser { - t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), liveBrowserbaseTimeout) - defer cancel() - conn, _, _, err := ws.Dial(ctx, rawURL) - if err != nil { - t.Fatal(err) - } - return conn -} - -func expectCDPBrowserSurface(t *testing.T, conn io.ReadWriter) { - t.Helper() - body, _ := json.Marshal(map[string]any{"id": 1, "method": "Browser.getVersion", "params": map[string]any{}}) - if err := wsutil.WriteClientText(conn, body); err != nil { - t.Fatal(err) - } - data, _, err := wsutil.ReadServerData(conn) - if err != nil { - t.Fatal(err) - } - var message map[string]any - if err := json.Unmarshal(data, &message); err != nil { - t.Fatal(err) - } - result, _ := message["result"].(map[string]any) - if _, ok := result["product"].(string); !ok { - t.Fatalf("Browser.getVersion result = %#v", message) - } -} - -func retrieveBrowserbaseSession(t *testing.T, sessionID string) map[string]any { - t.Helper() - request, err := http.NewRequest(http.MethodGet, browserbaseAPIURL("/v1/sessions/"+sessionID), nil) - if err != nil { - t.Fatal(err) - } - request.Header.Set("x-bb-api-key", os.Getenv("BROWSERBASE_API_KEY")) - response, err := http.DefaultClient.Do(request) - if err != nil { - t.Fatal(err) - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - if response.StatusCode < 200 || response.StatusCode >= 300 { - t.Fatalf("Browserbase session fetch returned %d: %s", response.StatusCode, string(body)) - } - var session map[string]any - if err := json.Unmarshal(body, &session); err != nil { - t.Fatal(err) - } - return session -} - -func browserbaseAPIURL(pathname string) string { - baseURL := firstString(os.Getenv("BROWSERBASE_BASE_URL"), DefaultBrowserbaseLauncherBaseURL) - return strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(pathname, "/") -} diff --git a/go/modcdp/launcher/LocalBrowserLauncher.go b/go/modcdp/launcher/LocalBrowserLauncher.go index 617e5b58..9335e35c 100644 --- a/go/modcdp/launcher/LocalBrowserLauncher.go +++ b/go/modcdp/launcher/LocalBrowserLauncher.go @@ -1,10 +1,12 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/launcher/LocalBrowserLauncher.ts +// - ./python/modcdp/launcher/LocalBrowserLauncher.py package launcher import ( - "bytes" "encoding/json" "fmt" - "io" "net/http" "os" "os/exec" @@ -22,8 +24,9 @@ type LocalBrowserLauncher struct { BrowserLauncher } -func NewLocalBrowserLauncher(options LaunchOptions) *LocalBrowserLauncher { - return &LocalBrowserLauncher{BrowserLauncher: NewBrowserLauncher(options)} +func NewLocalBrowserLauncher(config LauncherConfig) *LocalBrowserLauncher { + config.LauncherMode = "local" + return &LocalBrowserLauncher{BrowserLauncher: NewBrowserLauncher(config)} } func (l *LocalBrowserLauncher) FindChromeBinary(explicit string) (string, error) { @@ -34,25 +37,23 @@ func (l *LocalBrowserLauncher) FreePort() (int, error) { return freePort() } -func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, error) { - options = mergeLaunchOptions(l.Options, options) +func (l *LocalBrowserLauncher) Launch(config LauncherConfig) (*LaunchedBrowser, error) { + config = mergeLaunchConfig(l.Config, config) - executablePath, err := l.FindChromeBinary(options.ExecutablePath) + executablePath, err := l.FindChromeBinary(config.LauncherLocalExecutablePath) if err != nil { return nil, err } - chromeReadyTimeoutMS := options.ChromeReadyTimeoutMS + chromeReadyTimeoutMS := config.LauncherLocalChromeReadyTimeoutMS if chromeReadyTimeoutMS == 0 { chromeReadyTimeoutMS = DefaultChromeReadyTimeoutMS } - chromeReadyPollIntervalMS := options.ChromeReadyPollIntervalMS + chromeReadyPollIntervalMS := config.LauncherLocalChromeReadyPollIntervalMS if chromeReadyPollIntervalMS == 0 { chromeReadyPollIntervalMS = DefaultChromeReadyPollIntervalMS } - usePipe := options.RemoteDebugging == "pipe" - useLoopbackCDP := !usePipe || options.Port != 0 || (options.LoopbackCDP != nil && *options.LoopbackCDP) - port := options.Port - profileDir := options.UserDataDir + port := config.LauncherLocalCDPListenPort + profileDir := config.LauncherLocalUserDataDir ownsProfileDir := false if profileDir == "" { profileDir, err = os.MkdirTemp("", "modcdp.") @@ -62,8 +63,8 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, ownsProfileDir = true } cleanupProfileDir := ownsProfileDir - if options.CleanupUserDataDir != nil { - cleanupProfileDir = *options.CleanupUserDataDir + if !ownsProfileDir && config.LauncherLocalCleanupUserDataDir != nil { + cleanupProfileDir = *config.LauncherLocalCleanupUserDataDir } args := []string{ "--enable-unsafe-extension-debugging", @@ -83,65 +84,30 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, "--disable-gpu", fmt.Sprintf("--user-data-dir=%s", profileDir), } - if useLoopbackCDP { - args = append(args, "--remote-debugging-address=127.0.0.1", fmt.Sprintf("--remote-debugging-port=%d", port)) - } - if usePipe { - args = append(args, "--remote-debugging-pipe") - } - headless := runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" - if options.Headless != nil { - headless = *options.Headless + args = append(args, "--remote-debugging-address=127.0.0.1", fmt.Sprintf("--remote-debugging-port=%d", port)) + defaultHeadless := runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" + headless := defaultHeadless + if config.LauncherLocalHeadless != nil { + headless = *config.LauncherLocalHeadless } if headless { args = append(args, "--headless=new") } - sandbox := runtime.GOOS != "linux" - if options.Sandbox != nil { - sandbox = *options.Sandbox + sandbox := !defaultHeadless + if config.LauncherLocalSandbox != nil { + sandbox = *config.LauncherLocalSandbox } if !sandbox { args = append(args, "--no-sandbox") } - args = append(args, options.Args...) - args = append(args, options.ExtraArgs...) + args = append(args, config.LauncherLocalArgs...) + args = append(args, config.LauncherLocalExtraArgs...) args = append(args, "about:blank") cmd := exec.Command(executablePath, args...) if runtime.GOOS != "windows" { cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} } - var pipeRead *os.File - var pipeWrite *os.File - if usePipe { - var childRead *os.File - var childWrite *os.File - pipeRead, childWrite, err = os.Pipe() - if err != nil { - if cleanupProfileDir { - _ = os.RemoveAll(profileDir) - } - return nil, err - } - childRead, pipeWrite, err = os.Pipe() - if err != nil { - _ = pipeRead.Close() - _ = childWrite.Close() - if cleanupProfileDir { - _ = os.RemoveAll(profileDir) - } - return nil, err - } - cmd.ExtraFiles = []*os.File{childRead, childWrite} - defer childRead.Close() - defer childWrite.Close() - } if err := cmd.Start(); err != nil { - if pipeRead != nil { - _ = pipeRead.Close() - } - if pipeWrite != nil { - _ = pipeWrite.Close() - } if cleanupProfileDir { _ = os.RemoveAll(profileDir) } @@ -155,12 +121,6 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, close(processDone) }() close := func() { - if pipeRead != nil { - _ = pipeRead.Close() - } - if pipeWrite != nil { - _ = pipeWrite.Close() - } if cmd.Process != nil { select { case <-processDone: @@ -200,38 +160,6 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, return nil } } - if usePipe { - if err := processExitedError(); err != nil { - close() - return nil, err - } - if err := waitForPipeReady(pipeRead, pipeWrite, time.Duration(chromeReadyTimeoutMS)*time.Millisecond); err != nil { - close() - return nil, err - } - loopbackCDPURL := "" - if useLoopbackCDP { - if port == 0 { - loopbackCDPURL, _, err = waitForBrowserSelectedCdpWebSocketURL(profileDir, time.Duration(chromeReadyTimeoutMS)*time.Millisecond, time.Duration(chromeReadyPollIntervalMS)*time.Millisecond) - } else { - loopbackCDPURL, err = waitForCdpWebSocketURL(fmt.Sprintf("http://127.0.0.1:%d", port), time.Duration(chromeReadyTimeoutMS)*time.Millisecond, time.Duration(chromeReadyPollIntervalMS)*time.Millisecond) - } - if err != nil { - close() - return nil, err - } - } - launched := &LaunchedBrowser{ - CDPURL: fmt.Sprintf("pipe://%d", cmd.Process.Pid), - LoopbackCDPURL: loopbackCDPURL, - Close: close, - ProfileDir: profileDir, - PipeRead: pipeRead, - PipeWrite: pipeWrite, - } - l.Launched = launched - return launched, nil - } deadline := time.Now().Add(time.Duration(chromeReadyTimeoutMS) * time.Millisecond) client := &http.Client{Timeout: 2 * time.Second} for time.Now().Before(deadline) { @@ -264,8 +192,13 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, if resolvedCDPURL == "" { resolvedCDPURL = cdpURL } + cdpListenPort := port + if port == 0 { + activePort, _ := strconv.Atoi(strings.TrimPrefix(cdpURL, "http://127.0.0.1:")) + cdpListenPort = activePort + } // CDPURL is resolved from the HTTP discovery endpoint before returning. - launched := &LaunchedBrowser{CDPURL: resolvedCDPURL, LoopbackCDPURL: resolvedCDPURL, Close: close, ProfileDir: profileDir} + launched := &LaunchedBrowser{CDPURL: resolvedCDPURL, CDPListenPort: cdpListenPort, LoopbackCDPURL: resolvedCDPURL, Close: close, ProfileDir: profileDir} l.Launched = launched return launched, nil } @@ -276,41 +209,11 @@ func (l *LocalBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, return nil, fmt.Errorf("Chrome did not become ready within %dms", chromeReadyTimeoutMS) } -func waitForPipeReady(pipeRead *os.File, pipeWrite *os.File, timeout time.Duration) error { - if err := WritePipeMessage(pipeWrite, map[string]any{"id": 1, "method": "Browser.getVersion", "params": map[string]any{}}); err != nil { - return err - } - type result struct { - message map[string]any - err error - } - ch := make(chan result, 1) - go func() { - message, err := ReadPipeMessage(pipeRead) - ch <- result{message: message, err: err} - }() - select { - case result := <-ch: - if result.err != nil { - return result.err - } - if id, _ := result.message["id"].(float64); id != 1 { - return fmt.Errorf("unexpected pipe ready response id %v", result.message["id"]) - } - if errorValue, ok := result.message["error"].(map[string]any); ok { - return fmt.Errorf("Browser.getVersion failed over pipe: %v", errorValue["message"]) - } - return nil - case <-time.After(timeout): - return fmt.Errorf("Chrome remote-debugging pipe did not respond within %s", timeout) - } -} - func waitForCdpWebSocketURL(cdpURL string, timeout time.Duration, pollInterval time.Duration) (string, error) { deadline := time.Now().Add(timeout) var lastErr error for time.Now().Before(deadline) { - loopbackCDPURL, err := websocketURLFor(cdpURL) + loopbackCDPURL, err := WebsocketURLFor(cdpURL) if err == nil && loopbackCDPURL != "" { return loopbackCDPURL, nil } @@ -352,7 +255,7 @@ func waitForBrowserSelectedCdpWebSocketURL(profileDir string, timeout time.Durat } if ready { cdpURL := fmt.Sprintf("http://127.0.0.1:%d", port) - loopbackCDPURL, err := websocketURLFor(cdpURL) + loopbackCDPURL, err := WebsocketURLFor(cdpURL) if err == nil && loopbackCDPURL != "" { return loopbackCDPURL, port, nil } @@ -378,42 +281,6 @@ func removeProfileDir(profileDir string) { _ = os.RemoveAll(profileDir) } -func WritePipeMessage(pipeWrite *os.File, message map[string]any) error { - body, err := json.Marshal(message) - if err != nil { - return err - } - body = append(body, 0) - _, err = pipeWrite.Write(body) - return err -} - -func ReadPipeMessage(pipeRead *os.File) (map[string]any, error) { - var buffer bytes.Buffer - for { - var b [1]byte - _, err := pipeRead.Read(b[:]) - if err != nil { - if err == io.EOF { - return nil, fmt.Errorf("CDP pipe closed") - } - return nil, err - } - if b[0] != 0 { - buffer.WriteByte(b[0]) - continue - } - if buffer.Len() == 0 { - continue - } - var message map[string]any - if err := json.Unmarshal(buffer.Bytes(), &message); err != nil { - return nil, err - } - return message, nil - } -} - func findChromeBinary(explicit string) (string, error) { candidates := append([]string{explicit, os.Getenv("CHROME_PATH")}, candidatePaths()...) for _, candidate := range candidates { @@ -430,7 +297,7 @@ func findChromeBinary(explicit string) (string, error) { tried = append(tried, candidate) } } - return "", fmt.Errorf("no Chrome/Chromium binary found. Tried: %s. Set CHROME_PATH or pass Launch.Options.ExecutablePath", strings.Join(tried, ", ")) + return "", fmt.Errorf("no Chrome/Chromium binary found. Tried: %s. Set CHROME_PATH or pass Launch.Config.LauncherLocalExecutablePath", strings.Join(tried, ", ")) } func candidatePaths() []string { diff --git a/go/modcdp/launcher/LocalBrowserLauncher_test.go b/go/modcdp/launcher/LocalBrowserLauncher_test.go index 92f463dd..8c97bdd4 100644 --- a/go/modcdp/launcher/LocalBrowserLauncher_test.go +++ b/go/modcdp/launcher/LocalBrowserLauncher_test.go @@ -1,45 +1,47 @@ -package launcher +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.LocalBrowserLauncher.ts +// - ./python/tests/test_LocalBrowserLauncher.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package launcher_test import ( - "context" - "encoding/json" "os" - "runtime" "strconv" "strings" "testing" "time" - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" + "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/transport" ) -func TestLocalBrowserLauncherClassHelpersMatchLocalLauncherSurface(t *testing.T) { - launcher := NewLocalBrowserLauncher(LaunchOptions{}) - if chromePath, err := launcher.FindChromeBinary(""); err != nil || chromePath == "" { +func TestClassHelpersMatchTheLocalLauncherSurface(t *testing.T) { + local_launcher := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}) + if chromePath, err := local_launcher.FindChromeBinary(""); err != nil || chromePath == "" { t.Fatalf("FindChromeBinary = %q, %v", chromePath, err) } - if port, err := launcher.FreePort(); err != nil || port <= 0 { + if port, err := local_launcher.FreePort(); err != nil || port <= 0 { t.Fatalf("FreePort = %d, %v", port, err) } } -func TestLocalBrowserLauncherLaunchesRealBrowserOverChosenCDPPortAndHonorsLaunchOptions(t *testing.T) { +func TestLaunchesARealBrowserOverAChosenCDPPortAndExplicitProfileDir(t *testing.T) { headless := true profileDir := t.TempDir() - port, err := freePort() + port, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).FreePort() if err != nil { t.Fatal(err) } - launcher := NewLocalBrowserLauncher(LaunchOptions{ - Headless: &headless, - ChromeReadyTimeoutMS: 45_000, - ChromeReadyPollIntervalMS: 50, + local_launcher := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalChromeReadyTimeoutMS: 45_000, + LauncherLocalChromeReadyPollIntervalMS: 50, }) - chrome, err := launcher.Launch(LaunchOptions{ - Port: port, - UserDataDir: profileDir, - ExtraArgs: []string{"--window-size=900,700"}, + chrome, err := local_launcher.Launch(launcher.LauncherConfig{ + LauncherLocalCDPListenPort: port, + LauncherLocalUserDataDir: profileDir, }) if err != nil { t.Fatal(err) @@ -50,7 +52,7 @@ func TestLocalBrowserLauncherLaunchesRealBrowserOverChosenCDPPortAndHonorsLaunch t.Fatalf("expected explicit user data dir to remain after close: %v", err) } }() - if launcher.Launched != chrome { + if local_launcher.Launched != chrome { t.Fatal("expected launcher to retain launched browser") } expectedPrefix := "ws://127.0.0.1:" + strconv.Itoa(port) + "/" @@ -60,183 +62,80 @@ func TestLocalBrowserLauncherLaunchesRealBrowserOverChosenCDPPortAndHonorsLaunch if chrome.ProfileDir != profileDir { t.Fatalf("ProfileDir = %q, want %q", chrome.ProfileDir, profileDir) } - transportConfig := launcher.GetTransportConfig() - if transportConfig["cdp_url"] != chrome.CDPURL { - t.Fatalf("transport cdp_url = %v, want %s", transportConfig["cdp_url"], chrome.CDPURL) + if chrome.CDPListenPort != port { + t.Fatalf("CDPListenPort = %d, want %d", chrome.CDPListenPort, port) } - if transportConfig["user_data_dir"] != chrome.ProfileDir { - t.Fatalf("transport user_data_dir = %v, want %s", transportConfig["user_data_dir"], chrome.ProfileDir) + transportConfig := local_launcher.ConfigForUpstream() + if transportConfig["upstream_ws_cdp_url"] != chrome.CDPURL { + t.Fatalf("transport cdp_url = %v, want %s", transportConfig["upstream_ws_cdp_url"], chrome.CDPURL) } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - conn, _, _, err := ws.Dial(ctx, chrome.CDPURL) - if err != nil { + cdp_transport := transport.NewWSUpstreamTransport(transport.UpstreamTransportConfig{UpstreamWSCDPURL: chrome.CDPURL}) + if err := cdp_transport.Connect(); err != nil { t.Fatal(err) } - defer conn.Close() - - if err := wsutil.WriteClientText(conn, []byte(`{"id":1,"method":"Browser.getVersion","params":{}}`)); err != nil { - t.Fatal(err) - } - body, err := wsutil.ReadServerText(conn) + defer cdp_transport.Close() + response, err := cdp_transport.Send("Browser.getVersion", map[string]any{}, "", 10*time.Second) if err != nil { t.Fatal(err) } - var response struct { - ID int `json:"id"` - Result struct { - Product string `json:"product"` - ProtocolVersion string `json:"protocolVersion"` - } `json:"result"` - } - if err := json.Unmarshal(body, &response); err != nil { - t.Fatal(err) - } - if response.ID != 1 { - t.Fatalf("unexpected response id %d", response.ID) - } - if !strings.Contains(response.Result.Product, "Chrome") && !strings.Contains(response.Result.Product, "Chromium") { - t.Fatalf("unexpected product %q", response.Result.Product) + product, _ := response["product"].(string) + if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { + t.Fatalf("unexpected product %q", product) } - if response.Result.ProtocolVersion == "" { + protocolVersion, _ := response["protocolVersion"].(string) + if protocolVersion == "" { t.Fatal("expected protocolVersion") } - if err := wsutil.WriteClientText(conn, []byte(`{"id":2,"method":"SystemInfo.getInfo","params":{}}`)); err != nil { - t.Fatal(err) - } - systemInfoBody, err := wsutil.ReadServerText(conn) - if err != nil { - t.Fatal(err) - } - var systemInfoResponse struct { - ID int `json:"id"` - Result struct { - CommandLine string `json:"commandLine"` - } `json:"result"` - } - if err := json.Unmarshal(systemInfoBody, &systemInfoResponse); err != nil { - t.Fatal(err) - } - if systemInfoResponse.ID != 2 { - t.Fatalf("unexpected SystemInfo.getInfo response id %d", systemInfoResponse.ID) - } - command := systemInfoResponse.Result.CommandLine - if !strings.Contains(command, "--window-size=900,700") { - t.Fatalf("expected browser command to include --window-size=900,700: %s", command) - } - if runtime.GOOS == "linux" { - if !strings.Contains(command, "--no-sandbox") { - t.Fatalf("expected Linux browser command to include --no-sandbox: %s", command) - } - } else if strings.Contains(command, "--no-sandbox") { - t.Fatalf("expected browser command not to include --no-sandbox: %s", command) - } } -func TestLocalBrowserLauncherLaunchesRealBrowserOverRemoteDebuggingPipe(t *testing.T) { - headless := true - launcher := NewLocalBrowserLauncher(LaunchOptions{ - Headless: &headless, - ChromeReadyTimeoutMS: 45_000, - }) - chrome, err := launcher.Launch(LaunchOptions{RemoteDebugging: "pipe"}) - if err != nil { - t.Fatal(err) - } - defer chrome.Close() - if launcher.Launched != chrome { - t.Fatal("expected launcher to retain launched browser") - } - transportConfig := launcher.GetTransportConfig() - if transportConfig["cdp_url"] != chrome.CDPURL { - t.Fatalf("transport cdp_url = %v, want %s", transportConfig["cdp_url"], chrome.CDPURL) - } - if transportConfig["pipe_read"] != chrome.PipeRead { - t.Fatal("expected transport pipe_read to use launched pipe") - } - if transportConfig["pipe_write"] != chrome.PipeWrite { - t.Fatal("expected transport pipe_write to use launched pipe") - } - if !strings.HasPrefix(chrome.CDPURL, "pipe://") { - t.Fatalf("CDPURL = %q", chrome.CDPURL) - } - if chrome.LoopbackCDPURL != "" { - t.Fatalf("LoopbackCDPURL = %q", chrome.LoopbackCDPURL) - } - if chrome.PipeRead == nil || chrome.PipeWrite == nil { - t.Fatal("expected pipe handles") - } - if err := writePipeMessage(chrome.PipeWrite, map[string]any{"id": 10, "method": "Browser.getVersion", "params": map[string]any{}}); err != nil { - t.Fatal(err) - } - response, err := readPipeMessage(chrome.PipeRead) - if err != nil { - t.Fatal(err) - } - if response["id"] != float64(10) { - t.Fatalf("response id = %v", response["id"]) - } - result, _ := response["result"].(map[string]any) - product, _ := result["product"].(string) - if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { - t.Fatalf("product = %q", product) - } -} - -func TestLocalBrowserLauncherLaunchesPipeBrowserWithAuxiliaryLoopbackOnlyWhenRequested(t *testing.T) { +func TestLaunchesARealBrowserWithAnAuxiliaryLoopbackCDPEndpointWhenRequested(t *testing.T) { headless := true loopbackCDP := true - chrome, err := NewLocalBrowserLauncher(LaunchOptions{ - Headless: &headless, - ChromeReadyTimeoutMS: 45_000, - }).Launch(LaunchOptions{RemoteDebugging: "pipe", LoopbackCDP: &loopbackCDP}) + chrome, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalChromeReadyTimeoutMS: 45_000, + }).Launch(launcher.LauncherConfig{LauncherLocalLoopbackCDP: &loopbackCDP}) if err != nil { t.Fatal(err) } defer chrome.Close() - if !strings.HasPrefix(chrome.CDPURL, "pipe://") { + if !strings.HasPrefix(chrome.CDPURL, "ws://127.0.0.1:") { t.Fatalf("CDPURL = %q", chrome.CDPURL) } if !strings.HasPrefix(chrome.LoopbackCDPURL, "ws://127.0.0.1:") { t.Fatalf("LoopbackCDPURL = %q", chrome.LoopbackCDPURL) } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - conn, _, _, err := ws.Dial(ctx, chrome.LoopbackCDPURL) - if err != nil { - t.Fatal(err) + if chrome.CDPListenPort <= 0 { + t.Fatalf("CDPListenPort = %d", chrome.CDPListenPort) } - defer conn.Close() - if err := wsutil.WriteClientText(conn, []byte(`{"id":1,"method":"Browser.getVersion","params":{}}`)); err != nil { + cdp_transport := transport.NewWSUpstreamTransport(transport.UpstreamTransportConfig{UpstreamWSCDPURL: chrome.LoopbackCDPURL}) + if err := cdp_transport.Connect(); err != nil { t.Fatal(err) } - body, err := wsutil.ReadServerText(conn) + defer cdp_transport.Close() + response, err := cdp_transport.Send("Browser.getVersion", map[string]any{}, "", 10*time.Second) if err != nil { t.Fatal(err) } - var response map[string]any - if err := json.Unmarshal(body, &response); err != nil { - t.Fatal(err) - } - if response["id"] != float64(1) { - t.Fatalf("response id = %v", response["id"]) + product, _ := response["product"].(string) + if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { + t.Fatalf("unexpected product %q", product) } } -func TestLocalBrowserLauncherCleansExplicitUserDataDirWhenRequested(t *testing.T) { +func TestRemovesAnExplicitUserDataDirWhenCleanupUserDataDirIsSet(t *testing.T) { headless := true cleanupUserDataDir := true profileDir, err := os.MkdirTemp("", "modcdp-go-local-profile-") if err != nil { t.Fatal(err) } - chrome, err := NewLocalBrowserLauncher(LaunchOptions{ - Headless: &headless, - ChromeReadyTimeoutMS: 45_000, - }).Launch(LaunchOptions{ - UserDataDir: profileDir, - CleanupUserDataDir: &cleanupUserDataDir, + chrome, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalChromeReadyTimeoutMS: 45_000, + }).Launch(launcher.LauncherConfig{ + LauncherLocalUserDataDir: profileDir, + LauncherLocalCleanupUserDataDir: &cleanupUserDataDir, }) if err != nil { _ = os.RemoveAll(profileDir) diff --git a/go/modcdp/launcher/NoneBrowserLauncher.go b/go/modcdp/launcher/NoneBrowserLauncher.go new file mode 100644 index 00000000..95c898b9 --- /dev/null +++ b/go/modcdp/launcher/NoneBrowserLauncher.go @@ -0,0 +1,20 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/launcher/NoneBrowserLauncher.ts +// - ./python/modcdp/launcher/NoneBrowserLauncher.py +package launcher + +type NoneBrowserLauncher struct { + BrowserLauncher +} + +func NewNoneBrowserLauncher(config LauncherConfig) *NoneBrowserLauncher { + config.LauncherMode = "none" + return &NoneBrowserLauncher{BrowserLauncher: NewBrowserLauncher(config)} +} + +func (l *NoneBrowserLauncher) Launch(config LauncherConfig) (*LaunchedBrowser, error) { + launched := &LaunchedBrowser{Close: func() {}} + l.Launched = launched + return launched, nil +} diff --git a/go/modcdp/launcher/NoneBrowserLauncher_test.go b/go/modcdp/launcher/NoneBrowserLauncher_test.go new file mode 100644 index 00000000..7dca7e66 --- /dev/null +++ b/go/modcdp/launcher/NoneBrowserLauncher_test.go @@ -0,0 +1,25 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.NoneBrowserLauncher.ts +// - ./python/tests/test_NoneBrowserLauncher.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package launcher + +import "testing" + +func TestNoneBrowserLauncherRecordsAnEmptyLaunchedBrowser(t *testing.T) { + launcher := NewNoneBrowserLauncher(LauncherConfig{}) + + launched, err := launcher.Launch(LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + if launched.CDPURL != "" { + t.Fatalf("launched.CDPURL = %q", launched.CDPURL) + } + if launcher.Launched != launched { + t.Fatal("expected launcher to retain launched browser") + } + launched.Close() +} diff --git a/go/modcdp/launcher/NoopBrowserLauncher.go b/go/modcdp/launcher/NoopBrowserLauncher.go deleted file mode 100644 index 838b6ccc..00000000 --- a/go/modcdp/launcher/NoopBrowserLauncher.go +++ /dev/null @@ -1,15 +0,0 @@ -package launcher - -type NoopBrowserLauncher struct { - BrowserLauncher -} - -func NewNoopBrowserLauncher(options LaunchOptions) *NoopBrowserLauncher { - return &NoopBrowserLauncher{BrowserLauncher: NewBrowserLauncher(options)} -} - -func (l *NoopBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, error) { - launched := &LaunchedBrowser{Close: func() {}} - l.Launched = launched - return launched, nil -} diff --git a/go/modcdp/launcher/NoopBrowserLauncher_test.go b/go/modcdp/launcher/NoopBrowserLauncher_test.go deleted file mode 100644 index db2a528a..00000000 --- a/go/modcdp/launcher/NoopBrowserLauncher_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package launcher - -import "testing" - -func TestNoopBrowserLauncherConstructorLaunchAndConfigMatchTSShape(t *testing.T) { - launcher := NewNoopBrowserLauncher(LaunchOptions{CDPURL: "ws://127.0.0.1:9222/devtools/browser/initial"}) - if launcher.Options.CDPURL != "ws://127.0.0.1:9222/devtools/browser/initial" { - t.Fatalf("Options.CDPURL = %q", launcher.Options.CDPURL) - } - if transportConfig := launcher.GetTransportConfig(); transportConfig["cdp_url"] != "ws://127.0.0.1:9222/devtools/browser/initial" { - t.Fatalf("transport config before launch = %#v", transportConfig) - } - launched, err := launcher.Launch(LaunchOptions{CDPURL: "ws://127.0.0.1:9222/devtools/browser/call"}) - if err != nil { - t.Fatal(err) - } - if launcher.Launched != launched { - t.Fatal("expected launcher to retain launched browser") - } - if launched.CDPURL != "" { - t.Fatalf("launched.CDPURL = %q", launched.CDPURL) - } - if len(launcher.GetServerConfig()) != 0 { - t.Fatalf("server config after launch = %#v", launcher.GetServerConfig()) - } - launched.Close() -} diff --git a/go/modcdp/launcher/RemoteBrowserLauncher.go b/go/modcdp/launcher/RemoteBrowserLauncher.go index 1c7133a2..effe2c54 100644 --- a/go/modcdp/launcher/RemoteBrowserLauncher.go +++ b/go/modcdp/launcher/RemoteBrowserLauncher.go @@ -1,29 +1,30 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/launcher/RemoteBrowserLauncher.ts +// - ./python/modcdp/launcher/RemoteBrowserLauncher.py package launcher import "fmt" type RemoteBrowserLauncher struct { BrowserLauncher - CDPURL string } -func NewRemoteBrowserLauncher(options LaunchOptions, cdpURL string) *RemoteBrowserLauncher { - if cdpURL != "" { - options.CDPURL = cdpURL - } - return &RemoteBrowserLauncher{BrowserLauncher: NewBrowserLauncher(options), CDPURL: cdpURL} +func NewRemoteBrowserLauncher(config LauncherConfig) *RemoteBrowserLauncher { + config.LauncherMode = "remote" + return &RemoteBrowserLauncher{BrowserLauncher: NewBrowserLauncher(config)} } -func (l *RemoteBrowserLauncher) Launch(options LaunchOptions) (*LaunchedBrowser, error) { - cdpURL := firstString(options.CDPURL, l.Options.CDPURL, l.CDPURL) +func (l *RemoteBrowserLauncher) Launch(config LauncherConfig) (*LaunchedBrowser, error) { + cdpURL := firstString(config.LauncherRemoteCDPURL, l.Config.LauncherRemoteCDPURL) if cdpURL == "" { - return nil, fmt.Errorf("launcher.launcher_mode=remote requires upstream.upstream_cdp_url") + return nil, fmt.Errorf("launcher_mode=remote requires launcher_remote_cdp_url.") } - resolvedCDPURL, err := websocketURLFor(cdpURL) + resolvedCDPURL, err := WebsocketURLFor(cdpURL) if err != nil { return nil, err } - // CDPURL is resolved here so downstream transports can dial it directly. + // CDPURL is resolved here so the websocket transport can dial it directly. launched := &LaunchedBrowser{CDPURL: resolvedCDPURL, Close: func() {}} l.Launched = launched return launched, nil diff --git a/go/modcdp/launcher/RemoteBrowserLauncher_test.go b/go/modcdp/launcher/RemoteBrowserLauncher_test.go index 20ad3815..a46b8e2c 100644 --- a/go/modcdp/launcher/RemoteBrowserLauncher_test.go +++ b/go/modcdp/launcher/RemoteBrowserLauncher_test.go @@ -1,53 +1,65 @@ -package launcher +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.RemoteBrowserLauncher.ts +// - ./python/tests/test_RemoteBrowserLauncher.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package launcher_test import ( "fmt" + "strings" "testing" + "time" + + "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/transport" ) -func TestRemoteBrowserLauncherRequiresUpstreamCDPURL(t *testing.T) { - _, err := NewRemoteBrowserLauncher(LaunchOptions{}, "").Launch(LaunchOptions{}) - if err == nil || err.Error() != "launcher.launcher_mode=remote requires upstream.upstream_cdp_url" { +func TestRequiresLauncherRemoteCDPURL(t *testing.T) { + _, err := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{}).Launch(launcher.LauncherConfig{}) + if err == nil || err.Error() != "launcher_mode=remote requires launcher_remote_cdp_url." { t.Fatalf("Launch error = %v", err) } } -func TestRemoteBrowserLauncherConnectsToRealBrowserFromHTTPAndWebSocketCDPEndpoints(t *testing.T) { - port, err := freePort() +func TestConnectsToARealBrowserFromBothHTTPDiscoveryAndWebSocketCDPEndpoints(t *testing.T) { + headless := true + port, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).FreePort() if err != nil { t.Fatal(err) } - local, err := NewLocalBrowserLauncher(LaunchOptions{}).Launch(LaunchOptions{ - Headless: boolPtr(true), - Port: port, + local, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).Launch(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalCDPListenPort: port, }) if err != nil { t.Fatal(err) } defer local.Close() - httpLauncher := NewRemoteBrowserLauncher(LaunchOptions{}, fmt.Sprintf("http://127.0.0.1:%d", port)) - fromHTTP, err := httpLauncher.Launch(LaunchOptions{}) + httpLauncher := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{LauncherRemoteCDPURL: fmt.Sprintf("http://127.0.0.1:%d", port)}) + fromHTTP, err := httpLauncher.Launch(launcher.LauncherConfig{}) if err != nil { t.Fatal(err) } if httpLauncher.Launched != fromHTTP { t.Fatal("expected launcher to retain launched browser") } - httpTransportConfig := httpLauncher.GetTransportConfig() - if httpTransportConfig["cdp_url"] != local.CDPURL { - t.Fatalf("http transport cdp_url = %v, want %s", httpTransportConfig["cdp_url"], local.CDPURL) + httpTransportConfig := httpLauncher.ConfigForUpstream() + if httpTransportConfig["upstream_ws_cdp_url"] != local.CDPURL { + t.Fatalf("http transport cdp_url = %v, want %s", httpTransportConfig["upstream_ws_cdp_url"], local.CDPURL) } if fromHTTP.CDPURL != local.CDPURL { t.Fatalf("fromHTTP.CDPURL = %q, want %q", fromHTTP.CDPURL, local.CDPURL) } - conn := connectBrowserbaseCDP(t, fromHTTP.CDPURL) - defer conn.Close() - expectCDPBrowserSurface(t, conn) + cdp_transport := connectLauncherCDP(t, fromHTTP.CDPURL) + defer cdp_transport.Close() + expectCDPBrowserSurface(t, cdp_transport) fromHTTP.Close() - hostPortLauncher := NewRemoteBrowserLauncher(LaunchOptions{}, fmt.Sprintf("127.0.0.1:%d", port)) - fromHostPort, err := hostPortLauncher.Launch(LaunchOptions{}) + hostPortLauncher := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{LauncherRemoteCDPURL: fmt.Sprintf("127.0.0.1:%d", port)}) + fromHostPort, err := hostPortLauncher.Launch(launcher.LauncherConfig{}) if err != nil { t.Fatal(err) } @@ -56,41 +68,93 @@ func TestRemoteBrowserLauncherConnectsToRealBrowserFromHTTPAndWebSocketCDPEndpoi } fromHostPort.Close() - optionsLauncher := NewRemoteBrowserLauncher(LaunchOptions{CDPURL: local.CDPURL}, "") - fromOptions, err := optionsLauncher.Launch(LaunchOptions{}) + configLauncher := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{LauncherRemoteCDPURL: local.CDPURL}) + fromConfig, err := configLauncher.Launch(launcher.LauncherConfig{}) if err != nil { t.Fatal(err) } - if fromOptions.CDPURL != local.CDPURL { - t.Fatalf("fromOptions.CDPURL = %q, want %q", fromOptions.CDPURL, local.CDPURL) + if fromConfig.CDPURL != local.CDPURL { + t.Fatalf("fromConfig.CDPURL = %q, want %q", fromConfig.CDPURL, local.CDPURL) } - fromOptions.Close() + fromConfig.Close() - wsLauncher := NewRemoteBrowserLauncher(LaunchOptions{}, "") - fromWS, err := wsLauncher.Launch(LaunchOptions{CDPURL: local.CDPURL}) + wsLauncher := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{}) + fromWS, err := wsLauncher.Launch(launcher.LauncherConfig{LauncherRemoteCDPURL: local.CDPURL}) if err != nil { t.Fatal(err) } if wsLauncher.Launched != fromWS { t.Fatal("expected ws launcher to retain launched browser") } - wsTransportConfig := wsLauncher.GetTransportConfig() - if wsTransportConfig["cdp_url"] != local.CDPURL { - t.Fatalf("ws transport cdp_url = %v, want %s", wsTransportConfig["cdp_url"], local.CDPURL) + wsTransportConfig := wsLauncher.ConfigForUpstream() + if wsTransportConfig["upstream_ws_cdp_url"] != local.CDPURL { + t.Fatalf("ws transport cdp_url = %v, want %s", wsTransportConfig["upstream_ws_cdp_url"], local.CDPURL) } if fromWS.CDPURL != local.CDPURL { t.Fatalf("fromWS.CDPURL = %q", fromWS.CDPURL) } - expectCDPBrowserSurface(t, conn) + expectCDPBrowserSurface(t, cdp_transport) fromWS.Close() +} + +func TestLetsLaunchConfigOverrideConstructorCDPURL(t *testing.T) { + headless := true + firstPort, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).FreePort() + if err != nil { + t.Fatal(err) + } + secondPort, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).FreePort() + if err != nil { + t.Fatal(err) + } + first, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).Launch(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalCDPListenPort: firstPort, + }) + if err != nil { + t.Fatal(err) + } + defer first.Close() + second, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).Launch(launcher.LauncherConfig{ + LauncherLocalHeadless: &headless, + LauncherLocalCDPListenPort: secondPort, + }) + if err != nil { + t.Fatal(err) + } + defer second.Close() + + launched, err := launcher.NewRemoteBrowserLauncher(launcher.LauncherConfig{LauncherRemoteCDPURL: first.CDPURL}).Launch(launcher.LauncherConfig{ + LauncherRemoteCDPURL: fmt.Sprintf("127.0.0.1:%d", second.CDPListenPort), + }) + if err != nil { + t.Fatal(err) + } + defer launched.Close() + if launched.CDPURL != second.CDPURL { + t.Fatalf("launched.CDPURL = %q, want %q", launched.CDPURL, second.CDPURL) + } +} + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep the setup semantics above 1:1 with translated tests; helpers here only call real transport classes and real CDP endpoints. +func connectLauncherCDP(t *testing.T, rawURL string) *transport.WSUpstreamTransport { + t.Helper() + cdp_transport := transport.NewWSUpstreamTransport(transport.UpstreamTransportConfig{UpstreamWSCDPURL: rawURL}) + if err := cdp_transport.Connect(); err != nil { + t.Fatal(err) + } + return cdp_transport +} - overrideLauncher := NewRemoteBrowserLauncher(LaunchOptions{CDPURL: "127.0.0.1:1"}, "127.0.0.1:2") - fromCallTimeOverride, err := overrideLauncher.Launch(LaunchOptions{CDPURL: local.CDPURL}) +func expectCDPBrowserSurface(t *testing.T, cdp_transport *transport.WSUpstreamTransport) { + t.Helper() + result, err := cdp_transport.Send("Browser.getVersion", map[string]any{}, "", 10*time.Second) if err != nil { t.Fatal(err) } - if fromCallTimeOverride.CDPURL != local.CDPURL { - t.Fatalf("fromCallTimeOverride.CDPURL = %q, want %q", fromCallTimeOverride.CDPURL, local.CDPURL) + product, _ := result["product"].(string) + if !strings.Contains(product, "Chrome") && !strings.Contains(product, "Chromium") { + t.Fatalf("Browser.getVersion result = %#v", result) } - fromCallTimeOverride.Close() } diff --git a/go/modcdp/modcdp.go b/go/modcdp/modcdp.go index e744b010..d56b416a 100644 --- a/go/modcdp/modcdp.go +++ b/go/modcdp/modcdp.go @@ -1,88 +1,116 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/index.ts +// - ./python/modcdp/__init__.py package modcdp import ( "github.com/browserbase/modcdp/go/modcdp/client" "github.com/browserbase/modcdp/go/modcdp/injector" "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/translate" "github.com/browserbase/modcdp/go/modcdp/transport" + "github.com/browserbase/modcdp/go/modcdp/types" ) type ModCDPClient = client.ModCDPClient -type Options = client.Options +type Config = client.Config type LauncherConfig = client.LauncherConfig -type UpstreamConfig = client.UpstreamConfig -type InjectorConfig = client.InjectorConfig type ClientConfig = client.ClientConfig type ServerConfig = client.ServerConfig +type RouterConfig = client.RouterConfig type CustomCommand = client.CustomCommand type CustomEvent = client.CustomEvent type CustomMiddleware = client.CustomMiddleware -type CDPEvent = client.CDPEvent -type LaunchOptions = launcher.LaunchOptions +type CDPTypes = client.CDPTypes type LaunchedBrowser = launcher.LaunchedBrowser type BrowserLauncher = launcher.BrowserLauncher type LocalBrowserLauncher = launcher.LocalBrowserLauncher type RemoteBrowserLauncher = launcher.RemoteBrowserLauncher -type BrowserbaseBrowserLauncher = launcher.BrowserbaseBrowserLauncher -type NoopBrowserLauncher = launcher.NoopBrowserLauncher -type ExtensionInjectorConfig = client.ExtensionInjectorConfig +type BBBrowserLauncher = launcher.BBBrowserLauncher +type NoneBrowserLauncher = launcher.NoneBrowserLauncher +type InjectorConfig = client.InjectorConfig type ExtensionInjectionResult = client.ExtensionInjectionResult type ExtensionInjector = injector.ExtensionInjector -type DiscoveredExtensionInjector = injector.DiscoveredExtensionInjector -type BBBrowserExtensionInjector = injector.BBBrowserExtensionInjector -type LocalBrowserLaunchExtensionInjector = injector.LocalBrowserLaunchExtensionInjector -type ExtensionsLoadUnpackedInjector = injector.ExtensionsLoadUnpackedInjector -type BorrowedExtensionInjector = injector.BorrowedExtensionInjector +type PreparedExtension = injector.PreparedExtension +type DiscoverExtensionInjector = injector.DiscoverExtensionInjector +type BBExtensionInjector = injector.BBExtensionInjector +type CLIExtensionInjector = injector.CLIExtensionInjector +type CDPExtensionInjector = injector.CDPExtensionInjector type UpstreamMode = transport.UpstreamMode -type UpstreamEndpointKind = transport.UpstreamEndpointKind +type UpstreamTransportConfig = transport.UpstreamTransportConfig type UpstreamTransport = transport.UpstreamTransport -type WebSocketUpstreamTransport = transport.WebSocketUpstreamTransport -type WebSocketUpstreamTransportOptions = transport.WebSocketUpstreamTransportOptions -type PipeUpstreamTransport = transport.PipeUpstreamTransport -type PipeUpstreamTransportOptions = transport.PipeUpstreamTransportOptions -type ReverseWebSocketUpstreamTransport = transport.ReverseWebSocketUpstreamTransport -type ReverseWebSocketUpstreamTransportOptions = transport.ReverseWebSocketUpstreamTransportOptions -type NativeMessagingUpstreamTransport = transport.NativeMessagingUpstreamTransport -type NativeMessagingUpstreamTransportOptions = transport.NativeMessagingUpstreamTransportOptions -type NatsUpstreamTransport = transport.NatsUpstreamTransport -type NatsUpstreamTransportOptions = transport.NatsUpstreamTransportOptions +type WSUpstreamTransport = transport.WSUpstreamTransport +type HostPort = transport.HostPort type AutoSessionRouter = client.AutoSessionRouter -type TargetTargetCreatedEvent = client.TargetTargetCreatedEvent -type TargetSetDiscoverTargetsParams = client.TargetSetDiscoverTargetsParams -type TargetCreateTargetParams = client.TargetCreateTargetParams -type TargetActivateTargetParams = client.TargetActivateTargetParams -type TargetTargetID = client.TargetTargetID +type CdpCommandParams = types.CdpCommandParams +type CdpCommandResult = types.CdpCommandResult +type CdpEventParams = types.CdpEventParams +type RuntimeBindingCalledEvent = types.RuntimeBindingCalledEvent +type TargetAttachedToTargetEvent = types.TargetAttachedToTargetEvent +type ModCDPRoutes = types.ModCDPRoutes +type ModCDPEvaluateParams = types.ModCDPEvaluateParams +type ModCDPAddCustomCommandParams = types.ModCDPAddCustomCommandParams +type ModCDPAddCustomEventObjectParams = types.ModCDPAddCustomEventObjectParams +type ModCDPAddMiddlewareParams = types.ModCDPAddMiddlewareParams +type ModCDPPingParams = types.ModCDPPingParams +type ModCDPPongEvent = types.ModCDPPongEvent +type ModCDPPingLatency = types.ModCDPPingLatency +type ModCDPGetTopologyParams = types.ModCDPGetTopologyParams +type ModCDPTopologyFrame = types.ModCDPTopologyFrame +type ModCDPTopologyDomRoot = types.ModCDPTopologyDomRoot +type ModCDPTopologyTarget = types.ModCDPTopologyTarget +type ModCDPTopologyExecutionContext = types.ModCDPTopologyExecutionContext +type ModCDPTopology = types.ModCDPTopology +type ModCDPGetTopologyResponse = types.ModCDPGetTopologyResponse +type ModCDPConfigureParams = types.ModCDPConfigureParams +type ModCDPCommandParams = types.ModCDPCommandParams +type ModCDPCommandResult = types.ModCDPCommandResult +type ModCDPEvaluateResponse = types.ModCDPEvaluateResponse +type ModCDPConfigureResponse = types.ModCDPConfigureResponse +type ModCDPAddCustomCommandResponse = types.ModCDPAddCustomCommandResponse +type ModCDPAddCustomEventResponse = types.ModCDPAddCustomEventResponse +type ModCDPAddMiddlewareResponse = types.ModCDPAddMiddlewareResponse +type ModCDPPingResponse = types.ModCDPPingResponse +type ModCDPBindingPayload = types.ModCDPBindingPayload +type CdpDebuggeeCommandParams = types.CdpDebuggeeCommandParams +type ProtocolParams = types.ProtocolParams +type ProtocolResult = types.ProtocolResult +type ProtocolPayload = types.ProtocolPayload +type CdpError = types.CdpError +type CdpCommandMessage = types.CdpCommandMessage +type CdpResponseMessage = types.CdpResponseMessage +type CdpEventMessage = types.CdpEventMessage +type TranslatedStep = types.TranslatedStep +type TranslatedCommand = types.TranslatedCommand +type UnwrappedModCDPEvent = types.UnwrappedModCDPEvent var New = client.New +var NewCDPTypes = client.NewCDPTypes var Bool = client.Bool var NewLocalBrowserLauncher = launcher.NewLocalBrowserLauncher var NewRemoteBrowserLauncher = launcher.NewRemoteBrowserLauncher -var NewBrowserbaseBrowserLauncher = launcher.NewBrowserbaseBrowserLauncher -var NewNoopBrowserLauncher = launcher.NewNoopBrowserLauncher +var NewBBBrowserLauncher = launcher.NewBBBrowserLauncher +var NewNoneBrowserLauncher = launcher.NewNoneBrowserLauncher +var ResolveCdpWebSocketUrl = launcher.WebsocketURLFor var NewExtensionInjector = injector.NewExtensionInjector -var NewDiscoveredExtensionInjector = injector.NewDiscoveredExtensionInjector -var NewBBBrowserExtensionInjector = injector.NewBBBrowserExtensionInjector -var NewLocalBrowserLaunchExtensionInjector = injector.NewLocalBrowserLaunchExtensionInjector -var NewExtensionsLoadUnpackedInjector = injector.NewExtensionsLoadUnpackedInjector -var NewBorrowedExtensionInjector = injector.NewBorrowedExtensionInjector -var NewWebSocketUpstreamTransport = transport.NewWebSocketUpstreamTransport -var NewPipeUpstreamTransport = transport.NewPipeUpstreamTransport -var NewReverseWebSocketUpstreamTransport = transport.NewReverseWebSocketUpstreamTransport -var NewNativeMessagingUpstreamTransport = transport.NewNativeMessagingUpstreamTransport -var NewNatsUpstreamTransport = transport.NewNatsUpstreamTransport +var NewDiscoverExtensionInjector = injector.NewDiscoverExtensionInjector +var NewBBExtensionInjector = injector.NewBBExtensionInjector +var NewCLIExtensionInjector = injector.NewCLIExtensionInjector +var NewCDPExtensionInjector = injector.NewCDPExtensionInjector +var DefaultModCDPExtensionPath = injector.DefaultModCDPExtensionPath +var PrepareUnpackedExtension = injector.PrepareUnpackedExtension +var ExtensionIDFromManifestKey = injector.ExtensionIDFromManifestKey +var NewUpstreamTransport = transport.NewUpstreamTransport +var NewWSUpstreamTransport = transport.NewWSUpstreamTransport var NewAutoSessionRouter = client.NewAutoSessionRouter +var ParseHostPort = transport.ParseHostPort +var WrapCommandIfNeeded = translate.WrapCommandIfNeeded +var UnwrapResponseIfNeeded = translate.UnwrapResponseIfNeeded +var UnwrapEventIfNeeded = translate.UnwrapEventIfNeeded +var EncodeBindingPayload = translate.EncodeBindingPayload const UpstreamModeWS = transport.UpstreamModeWS -const UpstreamModePipe = transport.UpstreamModePipe -const UpstreamModeNativeMessaging = transport.UpstreamModeNativeMessaging -const UpstreamModeReverseWS = transport.UpstreamModeReverseWS -const UpstreamModeNATS = transport.UpstreamModeNATS -const UpstreamEndpointKindRawCDP = transport.UpstreamEndpointKindRawCDP -const UpstreamEndpointKindModCDPServer = transport.UpstreamEndpointKindModCDPServer const DefaultModCDPExtensionID = injector.DefaultModCDPExtensionID -const DefaultUpstreamReverseWSBind = transport.DefaultUpstreamReverseWSBind -const DefaultUpstreamReverseWSWaitTimeoutMS = transport.DefaultUpstreamReverseWSWaitTimeoutMS -const DefaultUpstreamNATSWaitTimeoutMS = transport.DefaultUpstreamNATSWaitTimeoutMS -const DefaultUpstreamNativeMessagingWaitTimeoutMS = transport.DefaultUpstreamNativeMessagingWaitTimeoutMS var DefaultModCDPServiceWorkerURLSuffixes = injector.DefaultModCDPServiceWorkerURLSuffixes diff --git a/go/modcdp/modcdp_test.go b/go/modcdp/modcdp_test.go index 8b39fad1..dff537e0 100644 --- a/go/modcdp/modcdp_test.go +++ b/go/modcdp/modcdp_test.go @@ -1,49 +1,41 @@ +// MODCDP_GO_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// Go package re-export compile checks are Go-only and have no TS/Python test sibling. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package modcdp import "testing" func TestRootExportsConcreteLaunchersInjectorsAndTransports(t *testing.T) { - if NewLocalBrowserLauncher(LaunchOptions{}) == nil { + if NewLocalBrowserLauncher(LauncherConfig{}) == nil { t.Fatal("NewLocalBrowserLauncher returned nil") } - if NewRemoteBrowserLauncher(LaunchOptions{}, "ws://127.0.0.1:9222/devtools/browser/test") == nil { + if NewRemoteBrowserLauncher(LauncherConfig{LauncherRemoteCDPURL: "ws://127.0.0.1:9222/devtools/browser/test"}) == nil { t.Fatal("NewRemoteBrowserLauncher returned nil") } - if NewBrowserbaseBrowserLauncher(LaunchOptions{}) == nil { - t.Fatal("NewBrowserbaseBrowserLauncher returned nil") + if NewBBBrowserLauncher(LauncherConfig{}) == nil { + t.Fatal("NewBBBrowserLauncher returned nil") } - if NewNoopBrowserLauncher(LaunchOptions{}) == nil { - t.Fatal("NewNoopBrowserLauncher returned nil") + if NewNoneBrowserLauncher(LauncherConfig{}) == nil { + t.Fatal("NewNoneBrowserLauncher returned nil") } - extensionInjector := NewExtensionInjector(ExtensionInjectorConfig{}) - discoveredInjector := NewDiscoveredExtensionInjector(ExtensionInjectorConfig{}) - bbInjector := NewBBBrowserExtensionInjector(ExtensionInjectorConfig{}) - localLaunchInjector := NewLocalBrowserLaunchExtensionInjector(ExtensionInjectorConfig{}) - loadUnpackedInjector := NewExtensionsLoadUnpackedInjector(ExtensionInjectorConfig{}) - borrowedInjector := NewBorrowedExtensionInjector(ExtensionInjectorConfig{}) - _ = []any{extensionInjector, discoveredInjector, bbInjector, localLaunchInjector, loadUnpackedInjector, borrowedInjector} + extensionInjector := NewExtensionInjector(InjectorConfig{}) + discoveredInjector := NewDiscoverExtensionInjector(InjectorConfig{}) + bbInjector := NewBBExtensionInjector(InjectorConfig{}) + localLaunchInjector := NewCLIExtensionInjector(InjectorConfig{}) + loadUnpackedInjector := NewCDPExtensionInjector(InjectorConfig{}) + _ = []any{extensionInjector, discoveredInjector, bbInjector, localLaunchInjector, loadUnpackedInjector} - if NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{}) == nil { - t.Fatal("NewWebSocketUpstreamTransport returned nil") + if NewUpstreamTransport(UpstreamTransportConfig{}).Config.UpstreamMode != string(UpstreamModeWS) { + t.Fatal("NewUpstreamTransport did not hydrate default ws mode") } - if NewPipeUpstreamTransport(PipeUpstreamTransportOptions{}) == nil { - t.Fatal("NewPipeUpstreamTransport returned nil") - } - if NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{}) == nil { - t.Fatal("NewReverseWebSocketUpstreamTransport returned nil") - } - if NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{}) == nil { - t.Fatal("NewNativeMessagingUpstreamTransport returned nil") - } - if NewNatsUpstreamTransport(NatsUpstreamTransportOptions{}) == nil { - t.Fatal("NewNatsUpstreamTransport returned nil") + if NewWSUpstreamTransport(UpstreamTransportConfig{}) == nil { + t.Fatal("NewWSUpstreamTransport returned nil") } - if UpstreamModeWS != "ws" || UpstreamModePipe != "pipe" || UpstreamModeNativeMessaging != "nativemessaging" || UpstreamModeReverseWS != "reversews" || UpstreamModeNATS != "nats" { + if UpstreamModeWS != "ws" { t.Fatal("upstream mode constants drifted") } - if UpstreamEndpointKindRawCDP != "raw_cdp" || UpstreamEndpointKindModCDPServer != "modcdp_server" { - t.Fatal("upstream endpoint kind constants drifted") - } } diff --git a/go/modcdp/router/AutoSessionRouter.go b/go/modcdp/router/AutoSessionRouter.go index 525500c3..3ad97eaa 100644 --- a/go/modcdp/router/AutoSessionRouter.go +++ b/go/modcdp/router/AutoSessionRouter.go @@ -1,64 +1,284 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/router/AutoSessionRouter.ts +// - ./python/modcdp/router/AutoSessionRouter.py package router import ( + "crypto/rand" + "encoding/hex" "fmt" + "strings" "sync" "time" + + "github.com/browserbase/modcdp/go/modcdp/translate" + "github.com/browserbase/modcdp/go/modcdp/transport" + "github.com/browserbase/modcdp/go/modcdp/types" ) -type AutoSessionRouterSend func(method string, params map[string]any, sessionID string) (map[string]any, error) +var targetAutoAttachParams = map[string]any{"autoAttach": true, "waitForDebuggerOnStart": false, "flatten": true} +var browserLevelDomains = map[string]bool{"Browser": true, "Target": true, "SystemInfo": true} -const maxDetachedSessionGuards = 1024 +type ProtocolTypes interface { + NativeCommandSchema(method string) map[string]any +} type AutoSessionRouter struct { - TargetSessions map[string]string - SessionTargets map[string]map[string]any - ExecutionContexts map[string]int - send AutoSessionRouterSend - defaultExecutionContextTimeoutMS func() int - executionContextWaiters map[string][]chan executionContextResult - detachedSessions map[string]bool - detachedSessionOrder []string - mu sync.Mutex + Config types.ModCDPRouterConfig + upstream *transport.UpstreamTransport + types ProtocolTypes + SessionId_from_targetId map[string]string + TargetId_from_sessionId map[string]string + Targets map[string]map[string]any + Contexts map[string]map[string]any + execution_context_waiters map[string][]executionContextWaiter + subscription_cleanups []func() + started bool + mu sync.Mutex } type executionContextResult struct { contextID int + context map[string]any err error } -func NewAutoSessionRouter(send AutoSessionRouterSend, defaultExecutionContextTimeoutMS func() int) *AutoSessionRouter { +type executionContextWaiter struct { + done chan executionContextResult + matches func(map[string]any) bool +} + +func NewAutoSessionRouter(upstream *transport.UpstreamTransport, protocolTypes ProtocolTypes, config types.ModCDPRouterConfig) *AutoSessionRouter { + if config.RouterRoutes == nil { + config.RouterRoutes = translate.DefaultClientRoutes() + } else { + merged := translate.DefaultClientRoutes() + for key, value := range config.RouterRoutes { + merged[key] = value + } + config.RouterRoutes = merged + } + if config.LoopbackExecutionContextTimeoutMS == 0 { + config.LoopbackExecutionContextTimeoutMS = 10_000 + } return &AutoSessionRouter{ - TargetSessions: map[string]string{}, - SessionTargets: map[string]map[string]any{}, - ExecutionContexts: map[string]int{}, - send: send, - defaultExecutionContextTimeoutMS: defaultExecutionContextTimeoutMS, - executionContextWaiters: map[string][]chan executionContextResult{}, - detachedSessions: map[string]bool{}, - detachedSessionOrder: []string{}, + Config: config, + upstream: upstream, + types: protocolTypes, + SessionId_from_targetId: map[string]string{}, + TargetId_from_sessionId: map[string]string{}, + Targets: map[string]map[string]any{}, + Contexts: map[string]map[string]any{}, + execution_context_waiters: map[string][]executionContextWaiter{}, + subscription_cleanups: []func(){}, } } -func (r *AutoSessionRouter) SessionIDForTarget(targetID string) string { +func (r *AutoSessionRouter) Start() error { r.mu.Lock() - defer r.mu.Unlock() - return r.TargetSessions[targetID] + if r.started { + r.mu.Unlock() + return nil + } + r.subscription_cleanups = r.listen() + r.started = true + r.mu.Unlock() + if _, err := r.upstream.Send("Target.setAutoAttach", targetAutoAttachParams, ""); err != nil { + r.Stop() + return err + } + if _, err := r.upstream.Send("Target.setDiscoverTargets", map[string]any{"discover": true}, ""); err != nil { + r.Stop() + return err + } + return nil +} + +func (r *AutoSessionRouter) Stop() { + r.mu.Lock() + cleanups := r.subscription_cleanups + r.subscription_cleanups = []func(){} + r.started = false + r.mu.Unlock() + for _, cleanup := range cleanups { + cleanup() + } +} + +func (r *AutoSessionRouter) ToJSON() map[string]any { + r.mu.Lock() + sessions := len(r.SessionId_from_targetId) + targets := len(r.Targets) + contexts := len(r.Contexts) + waiters := len(r.execution_context_waiters) + started := r.started + routerRoutes := cloneStringMap(r.Config.RouterRoutes) + timeoutMS := r.Config.LoopbackExecutionContextTimeoutMS + r.mu.Unlock() + return types.ModCDPToJSON(r, types.ModCDPJSONConfig{ + Config: map[string]any{ + "router_routes": routerRoutes, + "loopback_execution_context_timeout_ms": timeoutMS, + }, + State: map[string]any{ + "started": started, + "sessions": sessions, + "targets": targets, + "contexts": contexts, + "execution_context_waiters": waiters, + }, + }) } -func (r *AutoSessionRouter) AttachToTarget(targetID string) string { - if sessionID := r.SessionIDForTarget(targetID); sessionID != "" { - return sessionID +func (r *AutoSessionRouter) Send(method string, params map[string]any, requestedSessionID string) (map[string]any, error) { + if r.types == nil || r.types.NativeCommandSchema(method) == nil { + return nil, fmt.Errorf("AutoSessionRouter cannot route unknown CDP command %s.", method) + } + domain := method + for index, char := range method { + if char == '.' { + domain = method[:index] + break + } + } + if requestedSessionID != "" { + targetID := r.TargetId_from_sessionId[requestedSessionID] + if targetID == "" { + return nil, fmt.Errorf("No target is recorded for sessionId=%s.", requestedSessionID) + } + routedParams := cloneMap(params) + if method == "Runtime.callFunctionOn" { + var err error + routedParams, err = r.callFunctionOnParamsForRoute(params, targetID, requestedSessionID) + if err != nil { + return nil, err + } + } + return r.upstream.Send(method, routedParams, requestedSessionID) + } + if browserLevelDomains[domain] { + return r.upstream.Send(method, params, "") } - result, err := r.send("Target.attachToTarget", map[string]any{"targetId": targetID, "flatten": true}, "") + targetID, err := r.resolveTargetID(params) if err != nil { - return "" + return nil, err } - sessionID, _ := result["sessionId"].(string) - return sessionID + routeTargetID, sessionID, err := r.EnsureRouteForTarget(targetID) + if err != nil { + return nil, err + } + routedParams := cloneMap(params) + if method == "Runtime.callFunctionOn" { + routedParams, err = r.callFunctionOnParamsForRoute(params, routeTargetID, sessionID) + if err != nil { + return nil, err + } + } + return r.upstream.Send(method, routedParams, sessionID) } -func (r *AutoSessionRouter) RecordProtocolEvent(method string, data any, sessionID string) { +func (r *AutoSessionRouter) AttachToTarget(targetID string) (string, error) { + r.mu.Lock() + sessionID := r.SessionId_from_targetId[targetID] + r.mu.Unlock() + if sessionID != "" { + return sessionID, nil + } + attachedSessionID, err := r.upstream.AttachToTarget(targetID) + if err != nil { + return "", err + } + if attachedSessionID != "" { + r.mu.Lock() + r.recordTargetSession(targetID, attachedSessionID, r.Targets[targetID]) + r.mu.Unlock() + } + return attachedSessionID, nil +} + +func (r *AutoSessionRouter) EnsureSessionForTarget(targetID string) (string, error) { + _, sessionID, err := r.EnsureRouteForTarget(targetID) + if err != nil { + return "", err + } + if sessionID == "" { + return "", fmt.Errorf("Upstream attached targetId=%s without a CDP session id.", targetID) + } + return sessionID, nil +} + +func (r *AutoSessionRouter) EnsureRouteForTarget(targetID string) (string, string, error) { + resolvedTargetID := targetID + var err error + if resolvedTargetID == "" { + resolvedTargetID, err = r.resolveTargetID(map[string]any{}) + if err != nil { + return "", "", err + } + } + if resolvedTargetID != "" { + sessionID := r.SessionId_from_targetId[resolvedTargetID] + if sessionID != "" { + return resolvedTargetID, sessionID, nil + } + target := r.Targets[resolvedTargetID] + if target != nil { + if _, hasSessionID := target["sessionId"]; hasSessionID && target["sessionId"] == nil { + return resolvedTargetID, "", nil + } + } + } + if resolvedTargetID == "" { + resolvedTargetID, err = r.upstream.CreateTarget("about:blank#modcdp") + if err != nil { + return "", "", err + } + } + sessionID, err := r.AttachToTarget(resolvedTargetID) + if err != nil { + return "", "", err + } + if sessionID == "" { + r.recordTargetSessionlessAttachment(resolvedTargetID) + return resolvedTargetID, "", nil + } + return resolvedTargetID, sessionID, nil +} + +func (r *AutoSessionRouter) listen() []func() { + return []func(){ + r.upstream.On("Target.attachedToTarget", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Target.attachedToTarget", event, targetID, sessionID) + }), + r.upstream.On("Target.detachedFromTarget", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Target.detachedFromTarget", event, targetID, sessionID) + }), + r.upstream.On("Target.targetInfoChanged", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Target.targetInfoChanged", event, targetID, sessionID) + }), + r.upstream.On("Target.targetDestroyed", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Target.targetDestroyed", event, targetID, sessionID) + }), + r.upstream.On("Runtime.executionContextCreated", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Runtime.executionContextCreated", event, targetID, sessionID) + }), + r.upstream.On("Runtime.executionContextDestroyed", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Runtime.executionContextDestroyed", event, targetID, sessionID) + }), + r.upstream.On("Runtime.executionContextsCleared", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Runtime.executionContextsCleared", event, targetID, sessionID) + }), + r.upstream.On("Page.frameNavigated", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Page.frameNavigated", event, targetID, sessionID) + }), + r.upstream.On("Page.frameDetached", func(event map[string]any, targetID string, sessionID string) { + r.recordProtocolEvent("Page.frameDetached", event, targetID, sessionID) + }), + } +} + +func (r *AutoSessionRouter) recordProtocolEvent(method string, data any, eventTargetID string, sessionID string) { eventData, _ := data.(map[string]any) if eventData == nil { eventData = map[string]any{} @@ -73,16 +293,58 @@ func (r *AutoSessionRouter) RecordProtocolEvent(method string, data any, session targetID, _ := targetInfo["targetId"].(string) if attachedSessionID != "" && targetID != "" { r.mu.Lock() - r.clearDetachedSessionLocked(attachedSessionID) - r.TargetSessions[targetID] = attachedSessionID - r.SessionTargets[attachedSessionID] = targetInfo + r.recordTargetSession(targetID, attachedSessionID, targetInfo) r.mu.Unlock() } + case "Target.targetInfoChanged": + targetInfo, _ := eventData["targetInfo"].(map[string]any) + if targetInfo != nil { + r.mu.Lock() + r.recordTarget(targetInfo) + r.mu.Unlock() + } + case "Target.targetDestroyed": + targetID, _ := eventData["targetId"].(string) + if targetID != "" { + r.forgetTarget(targetID) + } case "Runtime.executionContextCreated": context, _ := eventData["context"].(map[string]any) - contextID, ok := intFromAny(context["id"]) + _, ok := intFromAny(context["id"]) + if (sessionID != "" || eventTargetID != "") && ok { + r.recordExecutionContext(eventTargetID, sessionID, context) + } + case "Runtime.executionContextDestroyed": + contextID, ok := intFromAny(eventData["executionContextId"]) if sessionID != "" && ok { - r.recordExecutionContext(sessionID, contextID) + r.forgetExecutionContextByID(sessionID, contextID) + } + case "Runtime.executionContextsCleared": + if sessionID != "" { + r.forgetExecutionContextsForRoute(sessionID) + } + case "Page.frameNavigated": + frame, _ := eventData["frame"].(map[string]any) + frameID, _ := frame["id"].(string) + if frameID != "" { + r.mu.Lock() + targetID := eventTargetID + if targetID == "" { + targetID = r.TargetId_from_sessionId[sessionID] + } + r.mu.Unlock() + r.forgetExecutionContextsForFrame(sessionID, targetID, frameID) + } + case "Page.frameDetached": + frameID, _ := eventData["frameId"].(string) + if frameID != "" { + r.mu.Lock() + targetID := eventTargetID + if targetID == "" { + targetID = r.TargetId_from_sessionId[sessionID] + } + r.mu.Unlock() + r.forgetExecutionContextsForFrame(sessionID, targetID, frameID) } case "Target.detachedFromTarget": detachedSessionID, _ := eventData["sessionId"].(string) @@ -96,97 +358,738 @@ func (r *AutoSessionRouter) RecordProtocolEvent(method string, data any, session } func (r *AutoSessionRouter) WaitForExecutionContext(sessionID string, timeoutMS int) (int, error) { - if timeoutMS == 0 { - timeoutMS = r.defaultExecutionContextTimeoutMS() - } if sessionID == "" { return 0, fmt.Errorf("cannot wait for a Runtime execution context without a session") } + context, err := r.waitForExecutionContextMatching(func(context map[string]any) bool { + contextSessionID, _ := context["sessionId"].(string) + return contextSessionID == sessionID + }, sessionID, timeoutMS) + if err != nil { + return 0, err + } + contextID, _ := intFromAny(context["id"]) + return contextID, nil +} + +func (r *AutoSessionRouter) EnsureExecutionContext(frame map[string]string, selector map[string]string) (map[string]any, error) { + if selector == nil { + selector = map[string]string{"world": "main"} + } + if selector["world"] == "" { + selector["world"] = "main" + } + frameID := frame["frameId"] + targetID := frame["targetId"] + routeTargetID, sessionID, err := r.EnsureRouteForTarget(targetID) + if err != nil { + return nil, err + } + existing := r.findExecutionContext(routeTargetID, sessionID, frameID, selector) + if existing != nil { + return existing, nil + } + if _, err := r.upstream.Send("Runtime.enable", map[string]any{}, sessionID); err != nil { + return nil, err + } + if selector["world"] == "isolated" || selector["world"] == "piercer" { + params := map[string]any{"frameId": frameID, "grantUniveralAccess": true} + if selector["world"] == "piercer" { + params["worldName"] = "__modcdp_piercer__" + } else if selector["worldName"] != "" { + params["worldName"] = selector["worldName"] + } + created, err := r.upstream.Send("Page.createIsolatedWorld", params, sessionID) + if err != nil { + return nil, err + } + executionContextID, _ := intFromAny(created["executionContextId"]) + createdContext := r.findExecutionContext(routeTargetID, sessionID, frameID, selector) + if createdContext != nil { + createdContextID, _ := intFromAny(createdContext["id"]) + if createdContextID == executionContextID { + return createdContext, nil + } + } + world := "isolated" + if selector["world"] == "piercer" { + world = "piercer" + } else if selector["worldName"] != "" { + world = selector["worldName"] + } + context := map[string]any{"id": executionContextID, "sessionId": sessionID, "targetId": routeTargetID, "frameId": frameID, "world": world} + if selector["worldName"] != "" { + context["name"] = selector["worldName"] + } + r.Contexts[contextKey(routeTargetID, sessionID, executionContextID, "")] = context + return context, nil + } + return r.waitForExecutionContextMatching(func(context map[string]any) bool { + return context["targetId"] == routeTargetID && context["sessionId"] == sessionID && context["frameId"] == frameID && context["world"] == selector["world"] + }, firstNonEmptyString(sessionID, routeTargetID), 0) +} + +func (r *AutoSessionRouter) GetTopology(params map[string]any) (map[string]any, error) { + if params == nil { + params = map[string]any{} + } + objectGroup := fmt.Sprintf("modcdp-topology-%d-%s", time.Now().UnixMilli(), randomHexSuffix(8)) + targetResult, err := r.upstream.Send("Target.getTargets", map[string]any{}, "") + if err != nil { + return nil, err + } + rawTargetInfos, _ := targetResult["targetInfos"].([]any) + targetInfos := []map[string]any{} + for _, rawTargetInfo := range rawTargetInfos { + targetInfo, _ := rawTargetInfo.(map[string]any) + if targetInfo == nil { + continue + } + targetInfos = append(targetInfos, targetInfo) + r.recordTarget(targetInfo) + } + rootTarget := r.resolveRootTarget(params, targetInfos) + if rootTarget == nil { + return nil, fmt.Errorf("Mod.getTopology could not resolve a page target.") + } + frames := map[string]map[string]any{} + rootTargetID, _ := rootTarget["targetId"].(string) + _, rootSessionID, err := r.enableTarget(rootTargetID) + if err != nil { + return nil, err + } + rootTreeResult, err := r.upstream.Send("Page.getFrameTree", map[string]any{}, rootSessionID) + if err != nil { + return nil, err + } + rootTree, _ := rootTreeResult["frameTree"].(map[string]any) + if rootTree == nil { + return nil, fmt.Errorf("Page.getFrameTree returned no frameTree.") + } + rootFrameID, err := r.recordFrameTree(rootTree, rootTargetID, "", frames) + if err != nil { + return nil, err + } + + for _, target := range targetInfos { + parentFrameID, _ := target["parentFrameId"].(string) + targetID, _ := target["targetId"].(string) + if target["type"] != "iframe" || parentFrameID == "" || frames[targetID] != nil { + continue + } + _, sessionID, err := r.enableTarget(targetID) + if err != nil { + return nil, err + } + frameTreeResult, err := r.upstream.Send("Page.getFrameTree", map[string]any{}, sessionID) + if err != nil { + return nil, err + } + frameTree, _ := frameTreeResult["frameTree"].(map[string]any) + if frameTree != nil { + if _, err := r.recordFrameTree(frameTree, targetID, parentFrameID, frames); err != nil { + return nil, err + } + } + } + + for frameID, frame := range frames { + parentFrameID, _ := frame["parentFrameId"].(string) + if parentFrameID == "" { + continue + } + parent := frames[parentFrameID] + if parent == nil { + continue + } + parentTargetID, _ := parent["targetId"].(string) + _, parentSessionID, err := r.EnsureRouteForTarget(parentTargetID) + if err != nil { + return nil, err + } + owner, err := r.upstream.Send("DOM.getFrameOwner", map[string]any{"frameId": frameID}, parentSessionID) + if err != nil { + return nil, err + } + if backendNodeID, ok := intFromAny(owner["backendNodeId"]); ok { + frame["outerBackendNodeId"] = backendNodeID + } + } + + contexts := map[string]map[string]any{} + roots := map[string]map[string]any{} + for frameID, frame := range frames { + frameTargetID, _ := frame["targetId"].(string) + context, err := r.EnsureExecutionContext(map[string]string{"frameId": frameID, "targetId": frameTargetID}, map[string]string{"world": "piercer"}) + if err != nil { + return nil, err + } + contextTargetID, _ := context["targetId"].(string) + contextSessionID, _ := context["sessionId"].(string) + contextID, _ := intFromAny(context["id"]) + contextUniqueID, _ := context["uniqueId"].(string) + contexts[contextKey(contextTargetID, contextSessionID, contextID, contextUniqueID)] = context + evaluateParams := map[string]any{"expression": "document.documentElement", "objectGroup": objectGroup} + if contextUniqueID != "" { + evaluateParams["uniqueContextId"] = contextUniqueID + } else { + evaluateParams["contextId"] = contextID + } + rootObject, err := r.upstream.Send("Runtime.evaluate", evaluateParams, contextSessionID) + if err != nil { + return nil, err + } + result, _ := rootObject["result"].(map[string]any) + objectID, _ := result["objectId"].(string) + if objectID == "" { + return nil, fmt.Errorf("Mod.getTopology could not resolve document root for frameId=%s.", frameID) + } + described, err := r.upstream.Send("DOM.describeNode", map[string]any{"objectId": objectID}, contextSessionID) + if err != nil { + return nil, err + } + node, _ := described["node"].(map[string]any) + root := map[string]any{ + "kind": "document", + "frameId": frameID, + "outerBackendNodeId": frame["outerBackendNodeId"], + "innerBackendNodeId": nil, + "executionContextId": contextID, + } + if node != nil { + if backendNodeID, ok := intFromAny(node["backendNodeId"]); ok { + root["innerBackendNodeId"] = backendNodeID + } + } + if contextUniqueID != "" { + root["uniqueContextId"] = contextUniqueID + } + roots[objectID] = root + } + + targetIDs := map[string]bool{} + for _, frame := range frames { + targetID, _ := frame["targetId"].(string) + if targetID != "" { + targetIDs[targetID] = true + } + } + for targetID := range targetIDs { + _, sessionID, err := r.EnsureRouteForTarget(targetID) + if err != nil { + return nil, err + } + document, err := r.upstream.Send("DOM.getDocument", map[string]any{"depth": -1, "pierce": true}, sessionID) + if err != nil { + return nil, err + } + root, _ := document["root"].(map[string]any) + if root != nil { + if err := r.recordShadowRoots(root, frames, roots, objectGroup, "", nil); err != nil { + return nil, err + } + } + } + + for _, context := range r.Contexts { + contextTargetID, _ := context["targetId"].(string) + if !targetIDs[contextTargetID] { + continue + } + contextSessionID, _ := context["sessionId"].(string) + contextID, _ := intFromAny(context["id"]) + contextUniqueID, _ := context["uniqueId"].(string) + contexts[contextKey(contextTargetID, contextSessionID, contextID, contextUniqueID)] = context + } + targets := map[string]map[string]any{} + for targetID, target := range r.Targets { + for _, targetInfo := range targetInfos { + if targetInfo["targetId"] == targetID { + targets[targetID] = target + break + } + } + } + return map[string]any{ + "objectGroup": objectGroup, + "rootFrameId": rootFrameID, + "frames": frames, + "roots": roots, + "targets": targets, + "contexts": contexts, + }, nil +} + +func (r *AutoSessionRouter) resolveRootTarget(params map[string]any, targetInfos []map[string]any) map[string]any { + requestedTargetID, _ := params["rootTargetId"].(string) + if requestedTargetID == "" { + requestedTargetID, _ = params["targetId"].(string) + } + if requestedTargetID != "" { + for _, target := range targetInfos { + if target["targetId"] == requestedTargetID { + return target + } + } + return nil + } + for _, target := range targetInfos { + targetURL, _ := target["url"].(string) + if target["type"] == "page" && !strings.HasPrefix(targetURL, "devtools://") { + return target + } + } + return nil +} + +func (r *AutoSessionRouter) enableTarget(targetID string) (string, string, error) { + routeTargetID, sessionID, err := r.EnsureRouteForTarget(targetID) + if err != nil { + return "", "", err + } + for _, command := range []struct { + method string + params map[string]any + }{ + {method: "Page.enable", params: map[string]any{}}, + {method: "DOM.enable", params: map[string]any{}}, + {method: "Runtime.enable", params: map[string]any{}}, + {method: "Target.setAutoAttach", params: targetAutoAttachParams}, + } { + if _, err := r.upstream.Send(command.method, command.params, sessionID); err != nil && command.method != "Target.setAutoAttach" { + return "", "", err + } + } + return routeTargetID, sessionID, nil +} + +func (r *AutoSessionRouter) recordFrameTree(tree map[string]any, targetID string, parentFrameID string, frames map[string]map[string]any) (string, error) { + frame, _ := tree["frame"].(map[string]any) + if frame == nil { + return "", fmt.Errorf("frame tree entry is missing frame.") + } + frameID, _ := frame["id"].(string) + if frameID == "" { + return "", fmt.Errorf("frame tree entry is missing frame.id.") + } + childParentFrameID := parentFrameID + if currentParentFrameID, _ := frame["parentId"].(string); currentParentFrameID != "" { + childParentFrameID = currentParentFrameID + } + frames[frameID] = map[string]any{ + "targetId": targetID, + "url": frame["url"], + "parentFrameId": childParentFrameID, + } + childFrames, _ := tree["childFrames"].([]any) + for _, rawChild := range childFrames { + child, _ := rawChild.(map[string]any) + if child == nil { + continue + } + if _, err := r.recordFrameTree(child, targetID, frameID, frames); err != nil { + return "", err + } + } + return frameID, nil +} + +func (r *AutoSessionRouter) recordShadowRoots(node map[string]any, frames map[string]map[string]any, roots map[string]map[string]any, objectGroup string, frameID string, outerBackendNodeID any) error { + currentFrameID := frameID + if nodeFrameID, _ := node["frameId"].(string); nodeFrameID != "" { + currentFrameID = nodeFrameID + } + shadowRoots, _ := node["shadowRoots"].([]any) + for _, rawShadowRoot := range shadowRoots { + shadowRoot, _ := rawShadowRoot.(map[string]any) + if shadowRoot == nil { + continue + } + if currentFrameID != "" { + frame := frames[currentFrameID] + if frame != nil { + frameTargetID, _ := frame["targetId"].(string) + context := r.findExecutionContext(frameTargetID, "", currentFrameID, map[string]string{"world": "piercer"}) + backendNodeID, backendNodeIDOK := intFromAny(shadowRoot["backendNodeId"]) + if context != nil && backendNodeIDOK { + contextSessionID, _ := context["sessionId"].(string) + contextID, _ := intFromAny(context["id"]) + resolved, err := r.upstream.Send( + "DOM.resolveNode", + map[string]any{"backendNodeId": backendNodeID, "executionContextId": contextID, "objectGroup": objectGroup}, + contextSessionID, + ) + if err != nil { + return err + } + remoteObject, _ := resolved["object"].(map[string]any) + objectID, _ := remoteObject["objectId"].(string) + if objectID != "" { + contextUniqueID, _ := context["uniqueId"].(string) + rootOuterBackendNodeID := outerBackendNodeID + if rootOuterBackendNodeID == nil { + rootOuterBackendNodeID = node["backendNodeId"] + } + root := map[string]any{ + "kind": "shadow", + "frameId": currentFrameID, + "outerBackendNodeId": rootOuterBackendNodeID, + "innerBackendNodeId": backendNodeID, + "mode": shadowRoot["shadowRootType"], + "executionContextId": contextID, + } + if contextUniqueID != "" { + root["uniqueContextId"] = contextUniqueID + } + roots[objectID] = root + } + } + } + } + if err := r.recordShadowRoots(shadowRoot, frames, roots, objectGroup, currentFrameID, node["backendNodeId"]); err != nil { + return err + } + } + children, _ := node["children"].([]any) + for _, rawChild := range children { + child, _ := rawChild.(map[string]any) + if child == nil { + continue + } + if err := r.recordShadowRoots(child, frames, roots, objectGroup, currentFrameID, outerBackendNodeID); err != nil { + return err + } + } + contentDocument, _ := node["contentDocument"].(map[string]any) + if contentDocument != nil { + contentFrameID := currentFrameID + if documentFrameID, _ := contentDocument["frameId"].(string); documentFrameID != "" { + contentFrameID = documentFrameID + } + return r.recordShadowRoots(contentDocument, frames, roots, objectGroup, contentFrameID, outerBackendNodeID) + } + return nil +} + +func (r *AutoSessionRouter) recordTarget(targetInfo map[string]any) { + targetID, _ := targetInfo["targetId"].(string) + if targetID == "" { + return + } + sessionID := r.SessionId_from_targetId[targetID] + existing := r.Targets[targetID] + target := cloneMap(targetInfo) + if sessionID != "" { + target["sessionId"] = sessionID + } else if existing != nil { + if _, hasSessionID := existing["sessionId"]; hasSessionID && existing["sessionId"] == nil { + target["sessionId"] = nil + } + } + r.Targets[targetID] = target +} + +func (r *AutoSessionRouter) recordTargetSession(targetID string, sessionID string, targetInfo map[string]any) { + r.SessionId_from_targetId[targetID] = sessionID + r.TargetId_from_sessionId[sessionID] = targetID + target := cloneMap(targetInfo) + if len(target) == 0 { + target = cloneMap(r.Targets[targetID]) + } + if len(target) == 0 { + target = map[string]any{"targetId": targetID, "type": "page"} + } + target["targetId"] = targetID + target["sessionId"] = sessionID + r.Targets[targetID] = target +} + +func (r *AutoSessionRouter) recordTargetSessionlessAttachment(targetID string) { + existing := cloneMap(r.Targets[targetID]) + if len(existing) == 0 { + existing = map[string]any{"targetId": targetID, "type": "page"} + } + existing["sessionId"] = nil + r.Targets[targetID] = existing +} + +func (r *AutoSessionRouter) recordExecutionContext(eventTargetID string, sessionID string, context map[string]any) { r.mu.Lock() - if contextID, ok := r.ExecutionContexts[sessionID]; ok { + targetID := eventTargetID + if targetID == "" { + targetID = r.TargetId_from_sessionId[sessionID] + } + if targetID == "" { r.mu.Unlock() - return contextID, nil + return + } + contextID, _ := intFromAny(context["id"]) + auxData, _ := context["auxData"].(map[string]any) + frameID, _ := auxData["frameId"].(string) + contextName, _ := context["name"].(string) + auxType, _ := auxData["type"].(string) + world := "" + if contextName == "__modcdp_piercer__" { + world = "piercer" + } else if auxType == "default" { + world = "main" + } else if contextName != "" { + world = contextName + } else if auxType != "" { + world = auxType + } else { + world = "isolated" + } + topologyContext := cloneMap(context) + topologyContext["id"] = contextID + topologyContext["sessionId"] = sessionID + topologyContext["targetId"] = targetID + topologyContext["frameId"] = frameID + topologyContext["world"] = world + uniqueID, _ := context["uniqueId"].(string) + r.Contexts[contextKey(targetID, sessionID, contextID, uniqueID)] = topologyContext + waiterKey := sessionID + if waiterKey == "" { + waiterKey = targetID + } + waiters := r.execution_context_waiters[waiterKey] + matchedWaiters := []executionContextWaiter{} + remainingWaiters := []executionContextWaiter{} + for _, waiter := range waiters { + if waiter.matches(topologyContext) { + matchedWaiters = append(matchedWaiters, waiter) + } else { + remainingWaiters = append(remainingWaiters, waiter) + } + } + if len(remainingWaiters) == 0 { + delete(r.execution_context_waiters, waiterKey) + } else { + r.execution_context_waiters[waiterKey] = remainingWaiters } - waiter := make(chan executionContextResult, 1) - r.executionContextWaiters[sessionID] = append(r.executionContextWaiters[sessionID], waiter) r.mu.Unlock() + for _, waiter := range matchedWaiters { + waiter.done <- executionContextResult{contextID: contextID, context: topologyContext} + } +} +func (r *AutoSessionRouter) forgetTarget(targetID string) { + r.mu.Lock() + sessionID := r.SessionId_from_targetId[targetID] + delete(r.Targets, targetID) + r.mu.Unlock() + if sessionID != "" { + r.forgetSession(sessionID) + } + r.forgetExecutionContextsForRoute(targetID) +} + +func (r *AutoSessionRouter) forgetSession(sessionID string) { + r.mu.Lock() + targetID := r.TargetId_from_sessionId[sessionID] + delete(r.TargetId_from_sessionId, sessionID) + if targetID != "" { + delete(r.SessionId_from_targetId, targetID) + } + for contextKey, context := range r.Contexts { + if context["sessionId"] == sessionID || context["targetId"] == sessionID { + delete(r.Contexts, contextKey) + } + } + waiters := r.execution_context_waiters[sessionID] + delete(r.execution_context_waiters, sessionID) + r.mu.Unlock() + err := fmt.Errorf("Runtime execution context wait cancelled because session %s detached.", sessionID) + for _, waiter := range waiters { + waiter.done <- executionContextResult{err: err} + } +} + +func (r *AutoSessionRouter) forgetExecutionContextByID(routeKey string, contextID int) { + r.mu.Lock() + defer r.mu.Unlock() + for contextKey, context := range r.Contexts { + currentContextID, ok := intFromAny(context["id"]) + if !ok || currentContextID != contextID { + continue + } + if context["sessionId"] == routeKey || context["targetId"] == routeKey { + delete(r.Contexts, contextKey) + } + } +} + +func (r *AutoSessionRouter) forgetExecutionContextsForRoute(routeKey string) { + r.mu.Lock() + defer r.mu.Unlock() + for contextKey, context := range r.Contexts { + if context["sessionId"] == routeKey || context["targetId"] == routeKey { + delete(r.Contexts, contextKey) + } + } +} + +func (r *AutoSessionRouter) forgetExecutionContextsForFrame(sessionID string, targetID string, frameID string) { + r.mu.Lock() + defer r.mu.Unlock() + for contextKey, context := range r.Contexts { + if context["frameId"] != frameID { + continue + } + if sessionID != "" && context["sessionId"] == sessionID { + delete(r.Contexts, contextKey) + } else if targetID != "" && context["targetId"] == targetID { + delete(r.Contexts, contextKey) + } + } +} + +func (r *AutoSessionRouter) callFunctionOnParamsForRoute(params map[string]any, targetID string, sessionID string) (map[string]any, error) { + callParams := cloneMap(params) + if callParams["executionContextId"] != nil || callParams["uniqueContextId"] != nil || callParams["objectId"] != nil { + return callParams, nil + } + context, err := r.waitForExecutionContextMatching(func(currentContext map[string]any) bool { + return currentContext["targetId"] == targetID && (sessionID == "" || currentContext["sessionId"] == sessionID) + }, firstNonEmptyString(sessionID, targetID), 0) + if err != nil { + return nil, err + } + callParams["executionContextId"] = context["id"] + return callParams, nil +} + +func (r *AutoSessionRouter) findExecutionContext(targetID string, sessionID string, frameID string, selector map[string]string) map[string]any { + for _, context := range r.Contexts { + if context["targetId"] != targetID || context["frameId"] != frameID { + continue + } + if sessionID != "" && context["sessionId"] != sessionID { + continue + } + if selector["world"] == "piercer" && context["world"] == "piercer" { + return context + } + if selector["world"] == "isolated" && context["name"] == selector["worldName"] { + return context + } + if selector["world"] == "main" && context["world"] == "main" { + return context + } + if context["world"] == selector["world"] { + return context + } + } + return nil +} + +func (r *AutoSessionRouter) waitForExecutionContextMatching(matches func(map[string]any) bool, waiterKey string, timeoutMS int) (map[string]any, error) { + if timeoutMS == 0 { + timeoutMS = r.Config.LoopbackExecutionContextTimeoutMS + } + r.mu.Lock() + for _, context := range r.Contexts { + if matches(context) { + r.mu.Unlock() + return context, nil + } + } + if waiterKey == "" { + r.mu.Unlock() + return nil, fmt.Errorf("cannot wait for a Runtime execution context without a route") + } + waiter := executionContextWaiter{done: make(chan executionContextResult, 1), matches: matches} + r.execution_context_waiters[waiterKey] = append(r.execution_context_waiters[waiterKey], waiter) + r.mu.Unlock() select { - case result := <-waiter: - return result.contextID, result.err + case result := <-waiter.done: + return result.context, result.err case <-time.After(time.Duration(timeoutMS) * time.Millisecond): r.mu.Lock() - waiters := r.executionContextWaiters[sessionID] + waiters := r.execution_context_waiters[waiterKey] filtered := waiters[:0] for _, candidate := range waiters { - if candidate != waiter { + if candidate.done != waiter.done { filtered = append(filtered, candidate) } } if len(filtered) == 0 { - delete(r.executionContextWaiters, sessionID) + delete(r.execution_context_waiters, waiterKey) } else { - r.executionContextWaiters[sessionID] = filtered + r.execution_context_waiters[waiterKey] = filtered } r.mu.Unlock() - return 0, fmt.Errorf("timed out waiting for Runtime.executionContextCreated for session %s", sessionID) + return nil, fmt.Errorf("timed out waiting for Runtime.executionContextCreated for route %s", waiterKey) } } -func (r *AutoSessionRouter) recordExecutionContext(sessionID string, contextID int) { - r.mu.Lock() - if r.detachedSessions[sessionID] { - r.mu.Unlock() - return +func (r *AutoSessionRouter) resolveTargetID(params map[string]any) (string, error) { + explicitTargetID := r.upstream.ResolveTargetID(params) + if explicitTargetID != "" { + return explicitTargetID, nil } - r.ExecutionContexts[sessionID] = contextID - waiters := r.executionContextWaiters[sessionID] - delete(r.executionContextWaiters, sessionID) - r.mu.Unlock() - for _, waiter := range waiters { - waiter <- executionContextResult{contextID: contextID} + targetInfos, err := r.upstream.GetTargets() + if err != nil { + return "", err + } + for _, targetInfo := range targetInfos { + r.recordTarget(targetInfo) } + for _, targetInfo := range targetInfos { + if targetInfo["type"] != "page" { + continue + } + targetURL, _ := targetInfo["url"].(string) + if len(targetURL) >= len("devtools://") && targetURL[:len("devtools://")] == "devtools://" { + continue + } + targetID, _ := targetInfo["targetId"].(string) + return targetID, nil + } + return "", nil } -func (r *AutoSessionRouter) forgetSession(sessionID string) { - r.mu.Lock() - targetInfo := r.SessionTargets[sessionID] - delete(r.SessionTargets, sessionID) - if targetID, _ := targetInfo["targetId"].(string); targetID != "" { - delete(r.TargetSessions, targetID) - } - delete(r.ExecutionContexts, sessionID) - r.markDetachedSessionLocked(sessionID) - waiters := r.executionContextWaiters[sessionID] - delete(r.executionContextWaiters, sessionID) - r.mu.Unlock() - err := fmt.Errorf("Runtime execution context wait cancelled because session %s detached", sessionID) - for _, waiter := range waiters { - waiter <- executionContextResult{err: err} +func contextKey(targetID string, sessionID string, contextID int, uniqueID string) string { + if uniqueID != "" { + return uniqueID + } + if sessionID != "" { + return fmt.Sprintf("%s:%d", sessionID, contextID) } + return fmt.Sprintf("%s:%d", targetID, contextID) } -func (r *AutoSessionRouter) markDetachedSessionLocked(sessionID string) { - if !r.detachedSessions[sessionID] { - r.detachedSessionOrder = append(r.detachedSessionOrder, sessionID) +func firstNonEmptyString(values ...string) string { + for _, value := range values { + if value != "" { + return value + } } - r.detachedSessions[sessionID] = true - for len(r.detachedSessions) > maxDetachedSessionGuards && len(r.detachedSessionOrder) > 0 { - oldestSessionID := r.detachedSessionOrder[0] - r.detachedSessionOrder = r.detachedSessionOrder[1:] - delete(r.detachedSessions, oldestSessionID) + return "" +} + +func cloneMap(input map[string]any) map[string]any { + output := map[string]any{} + for key, value := range input { + output[key] = value } + return output } -func (r *AutoSessionRouter) clearDetachedSessionLocked(sessionID string) { - delete(r.detachedSessions, sessionID) - filtered := r.detachedSessionOrder[:0] - for _, candidateSessionID := range r.detachedSessionOrder { - if candidateSessionID != sessionID { - filtered = append(filtered, candidateSessionID) - } +func cloneStringMap(input map[string]string) map[string]string { + output := map[string]string{} + for key, value := range input { + output[key] = value + } + return output +} + +func randomHexSuffix(bytesLen int) string { + buf := make([]byte, bytesLen) + if _, err := rand.Read(buf); err == nil { + return hex.EncodeToString(buf) } - r.detachedSessionOrder = filtered + return fmt.Sprintf("%d", time.Now().UnixNano()) } func intFromAny(value any) (int, bool) { diff --git a/go/modcdp/router/AutoSessionRouter_test.go b/go/modcdp/router/AutoSessionRouter_test.go index 1ba7fe2b..5d4dc28b 100644 --- a/go/modcdp/router/AutoSessionRouter_test.go +++ b/go/modcdp/router/AutoSessionRouter_test.go @@ -1,234 +1,88 @@ -package router +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.AutoSessionRouter.ts +// - ./python/tests/test_AutoSessionRouter.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package router_test import ( - "context" - "encoding/json" "fmt" - "strings" - "sync" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" "testing" "time" - "github.com/browserbase/modcdp/go/modcdp/launcher" - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" + modcdp "github.com/browserbase/modcdp/go/modcdp/client" ) -func TestAutoSessionRouterRejectsPendingExecutionContextWaitersWhenSessionDetaches(t *testing.T) { - router := NewAutoSessionRouter(func(string, map[string]any, string) (map[string]any, error) { - return map[string]any{}, nil - }, func() int { return 5000 }) - - result := make(chan error, 1) - go func() { - _, err := router.WaitForExecutionContext("detached-session", 5000) - result <- err - }() - waitForString(t, func() string { - router.mu.Lock() - defer router.mu.Unlock() - if len(router.executionContextWaiters["detached-session"]) > 0 { - return "waiting" - } - return "" - }) - router.RecordProtocolEvent("Target.attachedToTarget", map[string]any{ - "sessionId": "detached-session", - "targetInfo": map[string]any{"targetId": "target-1", "type": "page"}, - }, "") - router.RecordProtocolEvent("Target.detachedFromTarget", map[string]any{"sessionId": "detached-session"}, "") - router.RecordProtocolEvent("Runtime.executionContextCreated", map[string]any{"context": map[string]any{"id": 42}}, "detached-session") - - select { - case err := <-result: - if err == nil || !strings.Contains(err.Error(), "Runtime execution context wait cancelled because session detached-session detached") { - t.Fatalf("wait error = %v", err) - } - case <-time.After(time.Second): - t.Fatal("timed out waiting for detach error") - } - if sessionID := router.SessionIDForTarget("target-1"); sessionID != "" { - t.Fatalf("session id after detach = %q", sessionID) - } - if _, ok := router.ExecutionContexts["detached-session"]; ok { - t.Fatal("stale execution context was recorded after detach") - } -} - -func TestAutoSessionRouterBoundsDetachedSessionGuardsAndClearsThemWhenSessionReattaches(t *testing.T) { - router := NewAutoSessionRouter(func(string, map[string]any, string) (map[string]any, error) { - return map[string]any{}, nil - }, func() int { return 5000 }) - - for index := 0; index < 1034; index++ { - router.RecordProtocolEvent("Target.detachedFromTarget", map[string]any{"sessionId": fmt.Sprintf("detached-session-%d", index)}, "") - } - - router.mu.Lock() - detachedCount := len(router.detachedSessions) - detachedOrderCount := len(router.detachedSessionOrder) - router.mu.Unlock() - if detachedCount > maxDetachedSessionGuards { - t.Fatalf("detached session guard count = %d, want <= %d", detachedCount, maxDetachedSessionGuards) - } - if detachedOrderCount > maxDetachedSessionGuards { - t.Fatalf("detached session guard order count = %d, want <= %d", detachedOrderCount, maxDetachedSessionGuards) - } - - recentSessionID := "detached-session-1033" - router.RecordProtocolEvent("Runtime.executionContextCreated", map[string]any{"context": map[string]any{"id": 42}}, recentSessionID) - if _, ok := router.ExecutionContexts[recentSessionID]; ok { - t.Fatal("stale execution context was recorded for detached session") - } - - router.RecordProtocolEvent("Target.attachedToTarget", map[string]any{ - "sessionId": recentSessionID, - "targetInfo": map[string]any{"targetId": "target-reattached", "type": "page"}, - }, "") - router.RecordProtocolEvent("Runtime.executionContextCreated", map[string]any{"context": map[string]any{"id": 43}}, recentSessionID) - - if sessionID := router.SessionIDForTarget("target-reattached"); sessionID != recentSessionID { - t.Fatalf("session id = %q, want %q", sessionID, recentSessionID) - } - if contextID := router.ExecutionContexts[recentSessionID]; contextID != 43 { - t.Fatalf("context id = %d, want 43", contextID) - } - router.mu.Lock() - defer router.mu.Unlock() - if router.detachedSessions[recentSessionID] { - t.Fatal("reattached session was still marked detached") - } - for _, detachedSessionID := range router.detachedSessionOrder { - if detachedSessionID == recentSessionID { - t.Fatal("reattached session stayed in detached session order") - } - } -} - -func TestAutoSessionRouterTracksRealTargetSessionsAndExecutionContexts(t *testing.T) { +func TestAutoSessionRouterTracksRealTargetSessionsAndExecutionContextsFromLiveCDPEvents(t *testing.T) { headless := true - chrome, err := launcher.NewLocalBrowserLauncher(launcher.LaunchOptions{ - Headless: &headless, - }).Launch(launcher.LaunchOptions{}) + extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) if err != nil { t.Fatal(err) } - defer chrome.Close() - conn, _, _, err := ws.Dial(context.Background(), chrome.CDPURL) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - type pendingResponse struct { - ch chan map[string]any - } - nextID := int64(0) - pending := map[int64]pendingResponse{} - done := make(chan struct{}) - var writeMu sync.Mutex - var pendingMu sync.Mutex - var router *AutoSessionRouter - send := func(method string, params map[string]any, sessionID string) (map[string]any, error) { - pendingMu.Lock() - nextID += 1 - id := nextID - response := pendingResponse{ch: make(chan map[string]any, 1)} - pending[id] = response - pendingMu.Unlock() - message := map[string]any{"id": id, "method": method, "params": params} - if sessionID != "" { - message["sessionId"] = sessionID - } - body, _ := json.Marshal(message) - writeMu.Lock() - err := wsutil.WriteClientText(conn, body) - writeMu.Unlock() - if err != nil { - return nil, err - } - select { - case received := <-response.ch: - if rawError, ok := received["error"]; ok { - return nil, fmt.Errorf("%v", rawError) - } - result, _ := received["result"].(map[string]any) - if result == nil { - result = map[string]any{} - } - return result, nil - case <-time.After(10 * time.Second): - return nil, fmt.Errorf("%s timed out", method) - } - } - router = NewAutoSessionRouter(send, func() int { return 30000 }) - - go func() { - for { - select { - case <-done: - return - default: - } - data, err := wsutil.ReadServerText(conn) - if err != nil { - return - } - var message map[string]any - if err := json.Unmarshal(data, &message); err != nil { - return - } - if id, ok := int64FromAny(message["id"]); ok { - pendingMu.Lock() - response, found := pending[id] - delete(pending, id) - pendingMu.Unlock() - if found { - response.ch <- message - } - continue - } - method, _ := message["method"].(string) - sessionID, _ := message["sessionId"].(string) - router.RecordProtocolEvent(method, message["params"], sessionID) - } - }() + cdp := modcdp.New(modcdp.Config{ + Launcher: modcdp.LauncherConfig{ + LauncherMode: "local", + LauncherLocalHeadless: &headless, + LauncherLocalExecutablePath: loadExtensionTestBrowserPath(t), + }, + Upstream: modcdp.UpstreamTransportConfig{UpstreamMode: "ws"}, + Injector: modcdp.InjectorConfig{ + InjectorMode: "cli", + InjectorCLIExtensionPath: extensionPath, + InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, + InjectorTrustServiceWorkerTarget: true, + }, + Router: modcdp.RouterConfig{RouterRoutes: map[string]string{ + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }}, + }) + defer cdp.Close() var targetID string + var pendingTargetID string defer func() { if targetID != "" { - _, _ = send("Target.closeTarget", map[string]any{"targetId": targetID}, "") + closeTarget(cdp, targetID) + } + if pendingTargetID != "" { + closeTarget(cdp, pendingTargetID) } - close(done) }() - if _, err := send("Target.setAutoAttach", map[string]any{"autoAttach": true, "waitForDebuggerOnStart": false, "flatten": true}, ""); err != nil { - t.Fatal(err) - } - if _, err := send("Target.setDiscoverTargets", map[string]any{"discover": true}, ""); err != nil { + if err := cdp.Connect(); err != nil { t.Fatal(err) } - created, err := send("Target.createTarget", map[string]any{"url": "about:blank#modcdp-auto-session-router"}, "") + created, err := cdp.Target.CreateTarget(modcdp.TargetCreateTargetParams{URL: "about:blank#modcdp-auto-session-router"}) if err != nil { t.Fatal(err) } - targetID, _ = created["targetId"].(string) - sessionID := waitForString(t, func() string { return router.SessionIDForTarget(targetID) }) + targetID = string(created.TargetID) + sessionID := waitForString(t, func() string { return cdp.Router.SessionId_from_targetId[targetID] }) + contextResult := make(chan int, 1) contextError := make(chan error, 1) go func() { - contextID, err := router.WaitForExecutionContext(sessionID, 30000) + contextID, err := cdp.Router.WaitForExecutionContext(sessionID, 30000) if err != nil { contextError <- err return } contextResult <- contextID }() - if _, err := send("Runtime.enable", map[string]any{}, sessionID); err != nil { + if _, err := cdp.Send("Runtime.enable", map[string]any{}, sessionID); err != nil { t.Fatal(err) } + var contextID int select { - case contextID := <-contextResult: + case contextID = <-contextResult: if contextID == 0 { t.Fatal("context id was zero") } @@ -237,40 +91,227 @@ func TestAutoSessionRouterTracksRealTargetSessionsAndExecutionContexts(t *testin case <-time.After(35 * time.Second): t.Fatal("timed out waiting for execution context") } - if _, err := send("Target.detachFromTarget", map[string]any{"sessionId": sessionID}, ""); err != nil { + foundContext := false + for _, context := range cdp.Router.Contexts { + if context["sessionId"] == sessionID && context["id"] == contextID { + foundContext = true + break + } + } + if !foundContext { + t.Fatalf("context id %d for session %s was not recorded", contextID, sessionID) + } + topologyOne, err := cdp.Router.GetTopology(nil) + if err != nil { + t.Fatal(err) + } + objectGroupOne, _ := topologyOne["objectGroup"].(string) + if !regexp.MustCompile(`^modcdp-topology-\d+-[0-9a-f]+$`).MatchString(objectGroupOne) { + t.Fatalf("topology objectGroup = %q", objectGroupOne) + } + topologyTwo, err := cdp.Router.GetTopology(nil) + if err != nil { + t.Fatal(err) + } + objectGroupTwo, _ := topologyTwo["objectGroup"].(string) + if objectGroupOne == objectGroupTwo { + t.Fatalf("topology objectGroup was reused: %q", objectGroupOne) + } + if _, _, err := cdp.Router.EnsureRouteForTarget("missing-target-id"); err == nil { + t.Fatal("EnsureRouteForTarget should return the attach error for an unknown target") + } + + detachTarget(t, cdp, sessionID) + expectEventually(t, func() error { + if cdp.Router.SessionId_from_targetId[targetID] != "" { + return fmt.Errorf("session still recorded") + } + return nil + }) + for _, context := range cdp.Router.Contexts { + if context["sessionId"] == sessionID { + t.Fatal("execution context remained after detach") + } + } + closeTarget(cdp, targetID) + targetID = "" + + pendingCreated, err := cdp.Target.CreateTarget(modcdp.TargetCreateTargetParams{URL: "about:blank#modcdp-auto-session-router-pending-context"}) + if err != nil { t.Fatal(err) } - waitForString(t, func() string { - if router.SessionIDForTarget(targetID) == "" { - return "detached" + pendingTargetID = string(pendingCreated.TargetID) + pendingSessionID := waitForString(t, func() string { return cdp.Router.SessionId_from_targetId[pendingTargetID] }) + pendingContextError := make(chan error, 1) + go func() { + _, err := cdp.Router.WaitForExecutionContext(pendingSessionID, 30000) + pendingContextError <- err + }() + detachTarget(t, cdp, pendingSessionID) + select { + case err := <-pendingContextError: + expected := "Runtime execution context wait cancelled because session " + pendingSessionID + " detached." + if err == nil || err.Error() != expected { + t.Fatalf("wait error = %v", err) + } + case <-time.After(35 * time.Second): + t.Fatal("timed out waiting for detach error") + } + expectEventually(t, func() error { + if cdp.Router.SessionId_from_targetId[pendingTargetID] != "" { + return fmt.Errorf("pending session still recorded") } - return "" + return nil }) + closeTarget(cdp, pendingTargetID) + pendingTargetID = "" +} + +func detachTarget(t *testing.T, cdp *modcdp.ModCDPClient, sessionID string) { + t.Helper() + value := modcdp.TargetSessionID(sessionID) + if _, err := cdp.Target.DetachFromTarget(modcdp.TargetDetachFromTargetParams{SessionIDValue: &value}); err != nil { + t.Fatal(err) + } +} + +func closeTarget(cdp *modcdp.ModCDPClient, targetID string) { + value := modcdp.TargetTargetID(targetID) + _, _ = cdp.Target.CloseTarget(modcdp.TargetCloseTargetParams{TargetID: value}) } func waitForString(t *testing.T, fn func() string) string { t.Helper() - deadline := time.Now().Add(5 * time.Second) + deadline := time.Now().Add(10 * time.Second) for time.Now().Before(deadline) { value := fn() if value != "" { return value } - time.Sleep(50 * time.Millisecond) + time.Sleep(100 * time.Millisecond) } t.Fatal("timed out waiting for string") return "" } -func int64FromAny(value any) (int64, bool) { - switch typed := value.(type) { - case int64: - return typed, true - case int: - return int64(typed), true - case float64: - return int64(typed), true +func expectEventually(t *testing.T, assertion func() error) { + t.Helper() + deadline := time.Now().Add(10 * time.Second) + var lastError error + for time.Now().Before(deadline) { + if err := assertion(); err != nil { + lastError = err + time.Sleep(100 * time.Millisecond) + continue + } + return + } + if lastError != nil { + t.Fatal(lastError) + } + t.Fatal("timed out waiting for assertion") +} + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +func loadExtensionTestBrowserPath(t *testing.T) string { + t.Helper() + for _, candidate := range []string{os.Getenv("CHROME_PATH"), linuxChromiumPath()} { + if candidate == "" { + continue + } + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + home, err := os.UserHomeDir() + if err != nil || home == "" { + home = "." + } + localAppData := os.Getenv("LOCALAPPDATA") + if localAppData == "" { + localAppData = filepath.Join(home, "AppData", "Local") + } + var patterns []string + switch runtime.GOOS { + case "darwin": + patterns = []string{ + filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), + filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Chromium.app", "Contents", "MacOS", "Chromium"), + filepath.Join(home, "Library", "Caches", "puppeteer", "chrome", "mac*-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), + } + case "windows": + patterns = []string{ + filepath.Join(localAppData, "ms-playwright", "chromium-*", "chrome-win*", "chrome.exe"), + filepath.Join(home, ".cache", "puppeteer", "chrome", "win*-*", "chrome.exe"), + } default: - return 0, false + patterns = []string{ + filepath.Join(home, ".cache", "ms-playwright", "chromium-*", "chrome-linux*", "chrome"), + filepath.Join("/opt", "pw-browsers", "chromium-*", "chrome-linux*", "chrome"), + filepath.Join(home, ".cache", "puppeteer", "chrome", "linux-*", "chrome-linux*", "chrome"), + } + } + var candidates []string + for _, pattern := range patterns { + matches, err := filepath.Glob(pattern) + if err == nil { + candidates = append(candidates, matches...) + } + } + candidates = newestChromeForTestingFirst(candidates) + if len(candidates) > 0 { + return candidates[0] + } + t.Fatal("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + return "" +} + +func linuxChromiumPath() string { + if runtime.GOOS == "linux" { + return "/usr/bin/chromium" + } + return "" +} + +func newestChromeForTestingFirst(candidates []string) []string { + seen := map[string]bool{} + deduped := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + if candidate == "" || seen[candidate] { + continue + } + seen[candidate] = true + deduped = append(deduped, candidate) + } + sort.Slice(deduped, func(i, j int) bool { + leftVersion, leftMtime := browserPathScore(deduped[i]) + rightVersion, rightMtime := browserPathScore(deduped[j]) + if leftVersion != rightVersion { + return leftVersion > rightVersion + } + if leftMtime != rightMtime { + return leftMtime > rightMtime + } + return deduped[i] < deduped[j] + }) + return deduped +} + +func browserPathScore(candidate string) (int, int64) { + version := 0 + for _, match := range regexp.MustCompile(`\d+`).FindAllString(candidate, -1) { + value := 0 + for _, digit := range match { + value = value*10 + int(digit-'0') + } + if value > version { + version = value + } + } + info, err := os.Stat(candidate) + if err != nil { + return version, 0 } + return version, info.ModTime().UnixNano() } diff --git a/go/modcdp/translate/translate.go b/go/modcdp/translate/translate.go index 12bde56d..79c89d4e 100644 --- a/go/modcdp/translate/translate.go +++ b/go/modcdp/translate/translate.go @@ -1,10 +1,15 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/translate/translate.ts +// - ./python/modcdp/translate/translate.py package translate import ( "encoding/json" "fmt" "strings" - "time" + + "github.com/browserbase/modcdp/go/modcdp/types" ) const UpstreamEventBindingName = "__ModCDP_event_from_upstream__" @@ -43,30 +48,6 @@ func RouteFor(method string, routes map[string]string) string { return "direct_cdp" } -type rawStep struct { - Method string - Params map[string]any - Unwrap string - SessionID string -} - -type rawCommand struct { - Route string - Target string - Steps []rawStep -} - -type RawStep = rawStep -type RawCommand = rawCommand - -var routeFor = RouteFor -var wrapCommandIfNeeded = WrapCommandIfNeeded -var unwrapResponseIfNeeded = UnwrapResponseIfNeeded -var unwrapEventIfNeeded = UnwrapEventIfNeeded - -const upstreamEventBindingName = UpstreamEventBindingName -const customEventBindingName = CustomEventBindingName - func stringValue(value any) string { if typed, ok := value.(string); ok { return typed @@ -82,121 +63,35 @@ func callFunctionParams(functionDeclaration string) map[string]any { } } -func wrapModCDPEvaluate(params map[string]any, sessionID string) map[string]any { - expr, _ := params["expression"].(string) - userParams := params["params"] - if userParams == nil { - userParams = map[string]any{} - } - cdpSessionID, _ := params["cdpSessionId"].(string) - if cdpSessionID == "" { - cdpSessionID = sessionID - } - up, _ := json.Marshal(userParams) - sid, _ := json.Marshal(cdpSessionID) - return callFunctionParams(fmt.Sprintf( - `async function() { const params = %s; const cdp = globalThis.ModCDP.attachToSession(%s); const ModCDP = globalThis.ModCDP; const chrome = globalThis.chrome; const value = (%s); return typeof value === 'function' ? await value(params) : value; }`, - string(up), string(sid), expr, - )) -} - -func wrapModCDPAddCustomCommand(params map[string]any) map[string]any { - name, _ := json.Marshal(params["name"]) - expr, _ := params["expression"].(string) - exprJSON, _ := json.Marshal(expr) - return callFunctionParams(fmt.Sprintf( - `function() { return globalThis.ModCDP.addCustomCommand({ name: %s, params_schema: null, result_schema: null, expression: %s, handler: async (params, cdpSessionId, method) => { const cdp = globalThis.ModCDP.attachToSession(cdpSessionId); const ModCDP = globalThis.ModCDP; const chrome = globalThis.chrome; const handler = (%s); return await handler(params || {}, method); }, }); }`, - string(name), string(exprJSON), expr, - )) -} - -func wrapModCDPAddCustomEvent(params map[string]any) map[string]any { - rawName, _ := params["name"].(string) - name, _ := json.Marshal(rawName) - return callFunctionParams(fmt.Sprintf( - `function() { return globalThis.ModCDP.addCustomEvent({ name: %s, event_schema: null }); }`, - string(name), - )) -} - -func wrapModCDPAddMiddleware(params map[string]any) map[string]any { - name := params["name"] - if name == nil { - name = "*" - } - rawExpr, _ := params["expression"].(string) - nameJSON, _ := json.Marshal(name) - phaseJSON, _ := json.Marshal(params["phase"]) - exprJSON, _ := json.Marshal(rawExpr) - return callFunctionParams(fmt.Sprintf( - `function() { return globalThis.ModCDP.addMiddleware({ name: %s, phase: %s, expression: %s, handler: async (payload, next, context = {}) => { const cdp = globalThis.ModCDP.attachToSession(context.cdpSessionId ?? null); const ModCDP = globalThis.ModCDP; const chrome = globalThis.chrome; const middleware = (%s); return await middleware(payload, next, context); }, }); }`, - string(nameJSON), string(phaseJSON), string(exprJSON), rawExpr, - )) -} - -func wrapCustomCommand(method string, params map[string]any, sessionID string) map[string]any { +func wrapCustomCommand(method string, params map[string]any, sessionID any) map[string]any { p, _ := json.Marshal(params) runtimeParams := callFunctionParams(`async function(method, paramsJson, cdpSessionId) { return JSON.stringify(await globalThis.ModCDP.handleCommand(method, JSON.parse(paramsJson), cdpSessionId)); }`) runtimeParams["arguments"] = []map[string]any{{"value": method}, {"value": string(p)}, {"value": sessionID}} return runtimeParams } -func wrapServiceWorkerCommand(method string, params map[string]any, sessionID string, targetSessionID string) []rawStep { +func wrapServiceWorkerCommand(method string, params map[string]any, sessionID string) []types.TranslatedStep { if params == nil { params = map[string]any{} } - if targetSessionID == "" { - targetSessionID = sessionID - } - if method == "Mod.ping" { - if _, ok := params["sent_at"]; !ok { - next := map[string]any{} - for key, value := range params { - next[key] = value - } - next["sent_at"] = time.Now().UnixMilli() - params = next - } - } - - if method == "Mod.addCustomEvent" { - return []rawStep{ - {Method: "Runtime.callFunctionOn", Params: wrapModCDPAddCustomEvent(params), Unwrap: "runtime"}, - } - } - runtimeParams := map[string]any{} - unwrap := "runtime" - switch method { - case "Mod.evaluate": - runtimeParams = wrapModCDPEvaluate(params, targetSessionID) - case "Mod.addCustomCommand": - runtimeParams = wrapModCDPAddCustomCommand(params) - case "Mod.addMiddleware": - runtimeParams = wrapModCDPAddMiddleware(params) - default: - cdpSessionID, _ := params["cdpSessionId"].(string) - if cdpSessionID == "" { - cdpSessionID = targetSessionID - } - runtimeParams = wrapCustomCommand(method, params, cdpSessionID) - unwrap = "runtime_json" + var cdpSessionID any + if paramsSessionID, _ := params["cdpSessionId"].(string); paramsSessionID != "" { + cdpSessionID = paramsSessionID + } else if sessionID != "" { + cdpSessionID = sessionID } - return []rawStep{{Method: "Runtime.callFunctionOn", Params: runtimeParams, Unwrap: unwrap}} + return []types.TranslatedStep{{Method: "Runtime.callFunctionOn", Params: wrapCustomCommand(method, params, cdpSessionID), Unwrap: "runtime_json"}} } -func WrapCommandIfNeeded(method string, params map[string]any, routes map[string]string, sessionID string, targetSessionID ...string) (rawCommand, error) { - targetSession := "" - if len(targetSessionID) > 0 { - targetSession = targetSessionID[0] - } +func WrapCommandIfNeeded(method string, params map[string]any, routes map[string]string, sessionID string) (types.TranslatedCommand, error) { route := RouteFor(method, routes) if route == "direct_cdp" { - return rawCommand{Route: route, Target: "direct_cdp", Steps: []rawStep{{Method: method, Params: params, SessionID: targetSession}}}, nil + return types.TranslatedCommand{Route: route, Target: "direct_cdp", Steps: []types.TranslatedStep{{Method: method, Params: params, SessionID: sessionID}}}, nil } if route == "service_worker" { - return rawCommand{Route: route, Target: "service_worker", Steps: wrapServiceWorkerCommand(method, params, sessionID, targetSession)}, nil + return types.TranslatedCommand{Route: route, Target: "service_worker", Steps: wrapServiceWorkerCommand(method, params, sessionID)}, nil } - return rawCommand{}, fmt.Errorf("unsupported client route %q for %s", route, method) + return types.TranslatedCommand{}, fmt.Errorf("unsupported client route %q for %s", route, method) } func UnwrapResponseIfNeeded(result map[string]any, unwrap string) (any, error) { @@ -234,31 +129,55 @@ func UnwrapResponseIfNeeded(result map[string]any, unwrap string) (any, error) { return value, nil } -func UnwrapEventIfNeeded(method string, params map[string]any, sessionID string, ourSessionID string) (string, any, bool) { +func UnwrapEventIfNeeded(method string, params map[string]any, sessionID string, ourSessionID string) (*types.UnwrappedModCDPEvent, bool) { if method != "Runtime.bindingCalled" { - return "", nil, false + return nil, false } name, _ := params["name"].(string) payloadStr, _ := params["payload"].(string) var payload map[string]any if err := json.Unmarshal([]byte(payloadStr), &payload); err != nil || payload == nil { - return "", nil, false + return nil, false } isUpstreamEventBinding := name == UpstreamEventBindingName isCustomEventBinding := name == CustomEventBindingName if !isUpstreamEventBinding && !isCustomEventBinding { - return "", nil, false + return nil, false } payloadEvent, _ := payload["event"].(string) if payloadEvent == "" { - return "", nil, false + return nil, false } resolvedEvent := payloadEvent if resolvedEvent == UpstreamEventBindingName || resolvedEvent == CustomEventBindingName { - return "", nil, false + return nil, false + } + sourceSessionID := sessionID + if payloadSessionID, ok := payload["cdpSessionId"].(string); ok { + sourceSessionID = payloadSessionID + } + var sourceSessionIDPtr *string + if sourceSessionID != "" { + sourceSessionIDPtr = &sourceSessionID } if data, ok := payload["data"]; ok { - return resolvedEvent, data, true + return &types.UnwrappedModCDPEvent{Event: resolvedEvent, Data: data, SessionID: sourceSessionIDPtr}, true } - return resolvedEvent, payload, true + return &types.UnwrappedModCDPEvent{Event: resolvedEvent, Data: payload, SessionID: sourceSessionIDPtr}, true +} + +func EncodeBindingPayload(payload types.ModCDPBindingPayload) (string, error) { + encoded, err := json.Marshal(struct { + Event string `json:"event"` + Data any `json:"data"` + CDPSessionID *string `json:"cdpSessionId"` + }{ + Event: payload.Event, + Data: payload.Data, + CDPSessionID: payload.CDPSessionID, + }) + if err != nil { + return "", err + } + return string(encoded), nil } diff --git a/go/modcdp/translate/translate_test.go b/go/modcdp/translate/translate_test.go index df05a5b9..07e8c2cc 100644 --- a/go/modcdp/translate/translate_test.go +++ b/go/modcdp/translate/translate_test.go @@ -1,20 +1,31 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.translate.ts +// - ./python/tests/test_translate.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. package translate import ( "encoding/json" "strings" "testing" + + "github.com/browserbase/modcdp/go/modcdp/types" ) func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t *testing.T) { - if routeFor("Browser.getVersion", map[string]string{"Browser.*": "direct_cdp", "*.*": "service_worker"}) != "direct_cdp" { + if RouteFor("Browser.getVersion", map[string]string{"Browser.*": "direct_cdp", "*.*": "service_worker"}) != "direct_cdp" { t.Fatal("Browser.getVersion route mismatch") } - if routeFor("Target.getTargets", map[string]string{"Browser.*": "direct_cdp", "*.*": "service_worker"}) != "service_worker" { + if RouteFor("Target.getTargets", map[string]string{"Browser.*": "direct_cdp", "*.*": "service_worker"}) != "service_worker" { t.Fatal("Target.getTargets route mismatch") } + if RouteFor("Browser.getVersion", nil) != "direct_cdp" { + t.Fatal("Browser.getVersion default route mismatch") + } - direct, err := wrapCommandIfNeeded("Browser.getVersion", map[string]any{}, map[string]string{"*.*": "direct_cdp"}, "") + direct, err := WrapCommandIfNeeded("Browser.getVersion", map[string]any{}, map[string]string{"*.*": "direct_cdp"}, "") if err != nil { t.Fatal(err) } @@ -22,7 +33,7 @@ func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t t.Fatalf("direct = %#v", direct) } - wrapped, err := wrapCommandIfNeeded( + wrapped, err := WrapCommandIfNeeded( "Mod.evaluate", map[string]any{"expression": "({ ok: true })", "params": map[string]any{"value": 1}}, DefaultClientRoutes(), @@ -37,14 +48,25 @@ func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t if wrapped.Steps[0].Method != "Runtime.callFunctionOn" { t.Fatalf("wrapped step = %#v", wrapped.Steps[0]) } - if !strings.Contains(stringValue(wrapped.Steps[0].Params["functionDeclaration"]), `attachToSession("session-1")`) { + if !strings.Contains(stringValue(wrapped.Steps[0].Params["functionDeclaration"]), "globalThis.ModCDP.handleCommand") { t.Fatalf("functionDeclaration = %s", wrapped.Steps[0].Params["functionDeclaration"]) } - if wrapped.Steps[0].Unwrap != "runtime" { + wrappedArguments := wrapped.Steps[0].Params["arguments"].([]map[string]any) + var wrappedPayload map[string]any + if err := json.Unmarshal([]byte(wrappedArguments[1]["value"].(string)), &wrappedPayload); err != nil { + t.Fatal(err) + } + if wrappedPayload["expression"] != "({ ok: true })" || wrappedPayload["params"].(map[string]any)["value"].(float64) != 1 { + t.Fatalf("wrapped payload = %#v", wrappedPayload) + } + if wrappedArguments[2]["value"] != "session-1" { + t.Fatalf("session argument = %#v", wrappedArguments[2]) + } + if wrapped.Steps[0].Unwrap != "runtime_json" { t.Fatalf("unwrap = %q", wrapped.Steps[0].Unwrap) } - configured, err := wrapCommandIfNeeded("Mod.configure", map[string]any{"server": map[string]any{"server_routes": map[string]any{"*.*": "loopback_cdp"}}}, DefaultClientRoutes(), "session-1") + configured, err := WrapCommandIfNeeded("Mod.configure", map[string]any{"router": map[string]any{"router_routes": map[string]any{"*.*": "loopback_cdp"}}}, DefaultClientRoutes(), "session-1") if err != nil { t.Fatal(err) } @@ -52,7 +74,20 @@ func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t t.Fatalf("configure unwrap = %q", configured.Steps[0].Unwrap) } - custom, err := wrapCommandIfNeeded( + ping, err := WrapCommandIfNeeded("Mod.ping", map[string]any{}, DefaultClientRoutes(), "") + if err != nil { + t.Fatal(err) + } + pingArguments := ping.Steps[0].Params["arguments"].([]map[string]any) + var pingPayload map[string]any + if err := json.Unmarshal([]byte(pingArguments[1]["value"].(string)), &pingPayload); err != nil { + t.Fatal(err) + } + if len(pingPayload) != 0 { + t.Fatalf("ping params = %#v", pingPayload) + } + + custom, err := WrapCommandIfNeeded( "Custom.echo", map[string]any{"secret": strings.Repeat("x", 100), "nested": map[string]any{"ok": true}}, DefaultClientRoutes(), @@ -83,14 +118,28 @@ func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t t.Fatalf("session argument = %#v", customArguments[2]) } - unwrapped, err := unwrapResponseIfNeeded(map[string]any{"result": map[string]any{"type": "object", "value": map[string]any{"ok": true}}}, "runtime") + customWithSession, err := WrapCommandIfNeeded( + "Custom.echo", + map[string]any{"secret": "targeted"}, + DefaultClientRoutes(), + "target-session-1", + ) + if err != nil { + t.Fatal(err) + } + customWithSessionArguments := customWithSession.Steps[0].Params["arguments"].([]map[string]any) + if customWithSessionArguments[2]["value"] != "target-session-1" { + t.Fatalf("target session argument = %#v", customWithSessionArguments[2]) + } + + unwrapped, err := UnwrapResponseIfNeeded(map[string]any{"result": map[string]any{"type": "object", "value": map[string]any{"ok": true}}}, "runtime") if err != nil { t.Fatal(err) } if unwrapped.(map[string]any)["ok"] != true { t.Fatalf("unwrapped = %#v", unwrapped) } - raw, err := unwrapResponseIfNeeded(map[string]any{"product": "Chrome/1"}, "") + raw, err := UnwrapResponseIfNeeded(map[string]any{"product": "Chrome/1"}, "") if err != nil { t.Fatal(err) } @@ -98,21 +147,25 @@ func TestTranslateRoutesWrapsAndUnwrapsModCDPProtocolMessagesDeterministically(t t.Fatalf("raw = %#v", raw) } - payload, _ := json.Marshal(map[string]any{ - "event": "Custom.ready", - "data": map[string]any{"ready": true}, - "cdpSessionId": "session-2", + payloadSessionID := "session-2" + payload, err := EncodeBindingPayload(types.ModCDPBindingPayload{ + Event: "Custom.ready", + Data: map[string]any{"ready": true}, + CDPSessionID: &payloadSessionID, }) - event, data, ok := unwrapEventIfNeeded( + if err != nil { + t.Fatal(err) + } + unwrappedEvent, ok := UnwrapEventIfNeeded( "Runtime.bindingCalled", - map[string]any{"name": customEventBindingName, "payload": string(payload)}, + map[string]any{"name": CustomEventBindingName, "payload": payload}, "session-1", "session-1", ) - if !ok || event != "Custom.ready" || data.(map[string]any)["ready"] != true { - t.Fatalf("event=%q data=%#v ok=%v", event, data, ok) + if !ok || unwrappedEvent.Event != "Custom.ready" || unwrappedEvent.Data.(map[string]any)["ready"] != true || unwrappedEvent.SessionID == nil || *unwrappedEvent.SessionID != "session-2" { + t.Fatalf("unwrappedEvent=%#v ok=%v", unwrappedEvent, ok) } - if _, _, ok := unwrapEventIfNeeded("Runtime.consoleAPICalled", map[string]any{"name": customEventBindingName, "payload": string(payload)}, "", ""); ok { + if _, ok := UnwrapEventIfNeeded("Runtime.consoleAPICalled", map[string]any{"name": CustomEventBindingName, "payload": payload}, "", ""); ok { t.Fatal("expected console event to ignore binding payload") } } diff --git a/go/modcdp/transport/NativeMessagingUpstreamTransport.go b/go/modcdp/transport/NativeMessagingUpstreamTransport.go deleted file mode 100644 index 71d5d405..00000000 --- a/go/modcdp/transport/NativeMessagingUpstreamTransport.go +++ /dev/null @@ -1,509 +0,0 @@ -package transport - -import ( - "bufio" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "net" - "os" - osexec "os/exec" - "path/filepath" - "runtime" - "strings" - "sync" - "time" -) - -const DefaultUpstreamNativeMessagingHostName = "com.modcdp.bridge" -const DefaultUpstreamNativeMessagingWaitTimeoutMS = 10_000 - -const nativeHostConfigEnv = "MODCDP_NATIVE_HOST_CONFIG" - -func init() { - if configPath := os.Getenv(nativeHostConfigEnv); configPath != "" { - if err := runNativeMessagingHost(configPath); err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - } -} - -type NativeMessagingUpstreamTransport struct { - UpstreamTransport - UpstreamNativeMessagingManifest string - UpstreamNativeMessagingManifests []string - IncludeDefaultUpstreamNativeMessagingManifests bool - UpstreamNativeMessagingHostName string - ExtensionID string - WaitTimeoutMS int - URL string - Listener net.Listener - Conn net.Conn - BoundPort int - CDPURL string - UserDataDir string - writeMu sync.Mutex - peerCh chan struct{} - peerOnce sync.Once - closeCh chan struct{} - stateMu sync.Mutex - closed bool - generation int64 -} - -type NativeMessagingUpstreamTransportOptions struct { - UpstreamNativeMessagingManifest string `json:"upstream_nativemessaging_manifest,omitempty"` - UpstreamNativeMessagingManifests []string `json:"upstream_nativemessaging_manifests,omitempty"` - UpstreamNativeMessagingHostName string `json:"upstream_nativemessaging_host_name,omitempty"` - InjectorExtensionID string `json:"injector_extension_id,omitempty"` - UpstreamNativeMessagingWaitTimeoutMS int `json:"upstream_nativemessaging_wait_timeout_ms,omitempty"` -} - -func NewNativeMessagingUpstreamTransport(options NativeMessagingUpstreamTransportOptions) *NativeMessagingUpstreamTransport { - nativeHostName := firstNonEmptyString(options.UpstreamNativeMessagingHostName, DefaultUpstreamNativeMessagingHostName) - extensionID := firstNonEmptyString(options.InjectorExtensionID, DefaultModCDPExtensionID) - waitTimeoutMS := options.UpstreamNativeMessagingWaitTimeoutMS - if waitTimeoutMS == 0 { - waitTimeoutMS = DefaultUpstreamNativeMessagingWaitTimeoutMS - } - return &NativeMessagingUpstreamTransport{ - UpstreamNativeMessagingManifest: options.UpstreamNativeMessagingManifest, - UpstreamNativeMessagingManifests: append([]string{}, options.UpstreamNativeMessagingManifests...), - IncludeDefaultUpstreamNativeMessagingManifests: options.UpstreamNativeMessagingManifest == "" && len(options.UpstreamNativeMessagingManifests) == 0, - UpstreamNativeMessagingHostName: nativeHostName, - ExtensionID: extensionID, - WaitTimeoutMS: waitTimeoutMS, - peerCh: make(chan struct{}), - closeCh: make(chan struct{}), - } -} - -func (t *NativeMessagingUpstreamTransport) Update(config map[string]any) { - if config == nil { - return - } - shouldInstallNativeHost := false - if value, ok := config["upstream_nativemessaging_manifest"]; ok { - t.UpstreamNativeMessagingManifest, _ = value.(string) - shouldInstallNativeHost = true - } - if value, ok := config["upstream_nativemessaging_manifests"]; ok { - t.UpstreamNativeMessagingManifests = nil - if paths, ok := value.([]string); ok { - t.UpstreamNativeMessagingManifests = append([]string{}, paths...) - } else if rawPaths, ok := value.([]any); ok { - for _, rawPath := range rawPaths { - if path, ok := rawPath.(string); ok && path != "" { - t.UpstreamNativeMessagingManifests = append(t.UpstreamNativeMessagingManifests, path) - } - } - } - shouldInstallNativeHost = true - } - t.IncludeDefaultUpstreamNativeMessagingManifests = t.UpstreamNativeMessagingManifest == "" && len(t.UpstreamNativeMessagingManifests) == 0 - if nativeHostName, _ := config["upstream_nativemessaging_host_name"].(string); nativeHostName != "" { - t.UpstreamNativeMessagingHostName = nativeHostName - shouldInstallNativeHost = true - } - if waitTimeoutMS, ok := intFromConfig(config["upstream_nativemessaging_wait_timeout_ms"]); ok { - t.WaitTimeoutMS = waitTimeoutMS - } - if extensionID, _ := config["injector_extension_id"].(string); extensionID != "" { - t.ExtensionID = extensionID - shouldInstallNativeHost = true - } - if userDataDir, _ := config["user_data_dir"].(string); userDataDir != "" && userDataDir != t.UserDataDir { - t.setProfileUpstreamNativeMessagingManifests(userDataDir) - t.UserDataDir = userDataDir - shouldInstallNativeHost = true - } - if shouldInstallNativeHost && t.BoundPort != 0 { - _ = t.installNativeHost(t.BoundPort) - } - if cdpURL, _ := config["cdp_url"].(string); cdpURL != "" { - t.CDPURL = cdpURL - } -} - -func (t *NativeMessagingUpstreamTransport) GetServerConfig() map[string]any { - if t.CDPURL == "" { - return map[string]any{} - } - return map[string]any{"server_loopback_cdp_url": t.CDPURL} -} - -func (t *NativeMessagingUpstreamTransport) GetInjectorConfig() ExtensionInjectorConfig { - return ExtensionInjectorConfig{UpstreamNativeMessagingHostName: t.UpstreamNativeMessagingHostName} -} - -func (t *NativeMessagingUpstreamTransport) Connect() error { - t.stateMu.Lock() - t.closed = false - t.closeCh = make(chan struct{}) - t.stateMu.Unlock() - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return err - } - t.Listener = listener - t.BoundPort = listener.Addr().(*net.TCPAddr).Port - t.URL = fmt.Sprintf("native://%s@127.0.0.1:%d", t.UpstreamNativeMessagingHostName, t.BoundPort) - if err := t.installNativeHost(t.BoundPort); err != nil { - _ = listener.Close() - t.Listener = nil - return err - } - go t.acceptLoop() - return nil -} - -func (t *NativeMessagingUpstreamTransport) Send(message map[string]any) error { - t.writeMu.Lock() - defer t.writeMu.Unlock() - conn := t.Conn - if conn == nil { - return fmt.Errorf("no native messaging peer is connected for %s", t.UpstreamNativeMessagingHostName) - } - return writeLengthPrefixedJSON(conn, message) -} - -func (t *NativeMessagingUpstreamTransport) WaitForPeer() error { - t.writeMu.Lock() - connected := t.Conn != nil - t.writeMu.Unlock() - if connected { - return nil - } - t.stateMu.Lock() - closeCh := t.closeCh - peerCh := t.peerCh - t.stateMu.Unlock() - select { - case <-peerCh: - return nil - case <-closeCh: - return fmt.Errorf("native messaging transport for %s closed before a peer connected", t.UpstreamNativeMessagingHostName) - case <-time.After(time.Duration(t.WaitTimeoutMS) * time.Millisecond): - return fmt.Errorf("timed out waiting %dms for native messaging host %s", t.WaitTimeoutMS, t.UpstreamNativeMessagingHostName) - } -} - -func (t *NativeMessagingUpstreamTransport) PeerGeneration() int64 { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.generation -} - -func (t *NativeMessagingUpstreamTransport) Close() error { - t.stateMu.Lock() - t.closed = true - closeCh := t.closeCh - t.closeCh = make(chan struct{}) - t.peerCh = make(chan struct{}) - t.peerOnce = sync.Once{} - t.stateMu.Unlock() - close(closeCh) - t.writeMu.Lock() - if t.Conn != nil { - _ = t.Conn.Close() - t.Conn = nil - } - t.writeMu.Unlock() - if t.Listener != nil { - _ = t.Listener.Close() - t.Listener = nil - } - return nil -} - -func (t *NativeMessagingUpstreamTransport) Closed() bool { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.closed -} - -func (t *NativeMessagingUpstreamTransport) acceptLoop() { - for { - conn, err := t.Listener.Accept() - if err != nil { - if !t.Closed() { - t.emitClose(err) - } - return - } - go t.accept(conn) - } -} - -func (t *NativeMessagingUpstreamTransport) accept(conn net.Conn) { - t.writeMu.Lock() - if t.Conn != nil && t.Conn != conn { - _ = t.Conn.Close() - } - t.Conn = conn - t.writeMu.Unlock() - t.stateMu.Lock() - t.generation++ - t.peerOnce.Do(func() { close(t.peerCh) }) - t.stateMu.Unlock() - for { - message, err := readLengthPrefixedJSON(conn) - if err != nil { - t.writeMu.Lock() - if t.Conn == conn { - t.Conn = nil - t.writeMu.Unlock() - t.resetPeerWait() - } else { - t.writeMu.Unlock() - } - if !t.Closed() { - t.emitClose(err) - } - return - } - if messageType, _ := message["type"].(string); messageType == "modcdp.native.hello" { - continue - } - t.emitRecv(message) - } -} - -func (t *NativeMessagingUpstreamTransport) resetPeerWait() { - t.stateMu.Lock() - defer t.stateMu.Unlock() - t.peerCh = make(chan struct{}) - t.peerOnce = sync.Once{} -} - -func (t *NativeMessagingUpstreamTransport) installNativeHost(port int) error { - hostDir := filepath.Join(userHomeDir(), ".modcdp", "native-messaging") - if err := os.MkdirAll(hostDir, 0o755); err != nil { - return err - } - configPath := filepath.Join(hostDir, t.UpstreamNativeMessagingHostName+".config.json") - hostExecutablePath := filepath.Join(hostDir, t.UpstreamNativeMessagingHostName+".sh") - if runtime.GOOS == "windows" { - hostExecutablePath = filepath.Join(hostDir, t.UpstreamNativeMessagingHostName+".cmd") - } - exePath, err := os.Executable() - if err != nil { - return err - } - configBody, err := json.MarshalIndent(map[string]any{"host": "127.0.0.1", "port": port}, "", " ") - if err != nil { - return err - } - if err := os.WriteFile(configPath, append(configBody, '\n'), 0o644); err != nil { - return err - } - wrapper := fmt.Sprintf("#!/bin/sh\n%s=%s exec %s\n", nativeHostConfigEnv, shellQuote(configPath), shellQuote(exePath)) - if runtime.GOOS == "windows" { - wrapper = fmt.Sprintf("@echo off\r\nset %s=%s\r\n%s\r\n", nativeHostConfigEnv, configPath, cmdQuote(exePath)) - } - if err := os.WriteFile(hostExecutablePath, []byte(wrapper), 0o755); err != nil { - return err - } - - manifestPaths := []string{} - if t.UpstreamNativeMessagingManifest != "" { - manifestPaths = append(manifestPaths, t.UpstreamNativeMessagingManifest) - } - manifestPaths = append(manifestPaths, t.UpstreamNativeMessagingManifests...) - if t.IncludeDefaultUpstreamNativeMessagingManifests || len(manifestPaths) == 0 { - manifestPaths = append(manifestPaths, defaultUpstreamNativeMessagingManifestPaths(t.UpstreamNativeMessagingHostName)...) - } - manifestBody, err := json.MarshalIndent(map[string]any{ - "name": t.UpstreamNativeMessagingHostName, - "description": "ModCDP Native Messaging bridge", - "path": hostExecutablePath, - "type": "stdio", - "allowed_origins": []string{"chrome-extension://" + t.ExtensionID + "/"}, - }, "", " ") - if err != nil { - return err - } - for _, manifestPath := range manifestPaths { - if manifestPath == "" { - continue - } - if err := os.MkdirAll(filepath.Dir(manifestPath), 0o755); err != nil { - return err - } - if err := os.WriteFile(manifestPath, append(manifestBody, '\n'), 0o644); err != nil { - return err - } - } - if runtime.GOOS == "windows" && len(manifestPaths) > 0 { - if err := registerWindowsNativeMessagingHost(t.UpstreamNativeMessagingHostName, manifestPaths[0]); err != nil { - return err - } - } - return nil -} - -func (t *NativeMessagingUpstreamTransport) setProfileUpstreamNativeMessagingManifests(userDataDir string) { - previousProfileUpstreamNativeMessagingManifests := map[string]bool{} - if t.UserDataDir != "" { - previousProfileUpstreamNativeMessagingManifests[filepath.Join(t.UserDataDir, "NativeMessagingHosts", t.UpstreamNativeMessagingHostName+".json")] = true - previousProfileUpstreamNativeMessagingManifests[filepath.Join(t.UserDataDir, "Default", "NativeMessagingHosts", t.UpstreamNativeMessagingHostName+".json")] = true - } - profileUpstreamNativeMessagingManifests := []string{ - filepath.Join(userDataDir, "NativeMessagingHosts", t.UpstreamNativeMessagingHostName+".json"), - filepath.Join(userDataDir, "Default", "NativeMessagingHosts", t.UpstreamNativeMessagingHostName+".json"), - } - nextProfileUpstreamNativeMessagingManifests := map[string]bool{} - for _, manifestPath := range profileUpstreamNativeMessagingManifests { - nextProfileUpstreamNativeMessagingManifests[manifestPath] = true - } - filteredUpstreamNativeMessagingManifests := []string{} - for _, manifestPath := range t.UpstreamNativeMessagingManifests { - if previousProfileUpstreamNativeMessagingManifests[manifestPath] || nextProfileUpstreamNativeMessagingManifests[manifestPath] { - continue - } - filteredUpstreamNativeMessagingManifests = append(filteredUpstreamNativeMessagingManifests, manifestPath) - } - t.UpstreamNativeMessagingManifests = append(profileUpstreamNativeMessagingManifests, filteredUpstreamNativeMessagingManifests...) -} - -func defaultUpstreamNativeMessagingManifestPaths(nativeHostName string) []string { - home := userHomeDir() - if runtime.GOOS == "darwin" { - return []string{ - filepath.Join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, "Library/Application Support/Google/Chrome Canary/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, "Library/Application Support/Google/ChromeForTesting/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, "Library/Application Support/Google/Chrome for Testing/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, "Library/Application Support/Google/Chrome SxS/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, "Library/Application Support/Chromium/NativeMessagingHosts", nativeHostName+".json"), - } - } - if runtime.GOOS == "linux" { - return []string{ - filepath.Join(home, ".config/google-chrome/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, ".config/google-chrome-for-testing/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, ".config/chromium/NativeMessagingHosts", nativeHostName+".json"), - filepath.Join(home, ".config/chromium-browser/NativeMessagingHosts", nativeHostName+".json"), - } - } - if runtime.GOOS == "windows" { - return []string{filepath.Join(home, ".modcdp", "native-messaging", nativeHostName+".json")} - } - return nil -} - -func registerWindowsNativeMessagingHost(nativeHostName string, manifestPath string) error { - return osexec.Command( - "reg", - "add", - `HKCU\Software\Google\Chrome\NativeMessagingHosts\`+nativeHostName, - "/ve", - "/t", - "REG_SZ", - "/d", - manifestPath, - "/f", - ).Run() -} - -func runNativeMessagingHost(configPath string) error { - configBytes, err := os.ReadFile(configPath) - if err != nil { - return err - } - var config struct { - Host string `json:"host"` - Port int `json:"port"` - } - if err := json.Unmarshal(configBytes, &config); err != nil { - return err - } - conn, err := net.Dial("tcp", net.JoinHostPort(config.Host, fmt.Sprint(config.Port))) - if err != nil { - return err - } - defer conn.Close() - if err := writeLengthPrefixedJSON(conn, map[string]any{"type": "modcdp.native.hello", "role": "native-host", "version": 1}); err != nil { - return err - } - - errCh := make(chan error, 2) - go func() { - reader := bufio.NewReader(os.Stdin) - for { - message, err := readLengthPrefixedJSON(reader) - if err != nil { - errCh <- nil - return - } - if err := writeLengthPrefixedJSON(conn, message); err != nil { - errCh <- err - return - } - } - }() - go func() { - for { - message, err := readLengthPrefixedJSON(conn) - if err != nil { - errCh <- nil - return - } - if err := writeLengthPrefixedJSON(os.Stdout, message); err != nil { - errCh <- err - return - } - } - }() - return <-errCh -} - -func writeLengthPrefixedJSON(writer io.Writer, message map[string]any) error { - body, err := json.Marshal(message) - if err != nil { - return err - } - var header [4]byte - binary.LittleEndian.PutUint32(header[:], uint32(len(body))) - if _, err := writer.Write(header[:]); err != nil { - return err - } - _, err = writer.Write(body) - return err -} - -func readLengthPrefixedJSON(reader io.Reader) (map[string]any, error) { - var header [4]byte - if _, err := io.ReadFull(reader, header[:]); err != nil { - return nil, err - } - length := binary.LittleEndian.Uint32(header[:]) - body := make([]byte, length) - if _, err := io.ReadFull(reader, body); err != nil { - return nil, err - } - var message map[string]any - if err := json.Unmarshal(body, &message); err != nil { - return nil, err - } - return message, nil -} - -func shellQuote(value string) string { - return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'" -} - -func cmdQuote(value string) string { - return `"` + strings.ReplaceAll(value, `"`, `""`) + `"` -} - -func userHomeDir() string { - home, err := os.UserHomeDir() - if err != nil { - return "" - } - return home -} diff --git a/go/modcdp/transport/NativeMessagingUpstreamTransport_test.go b/go/modcdp/transport/NativeMessagingUpstreamTransport_test.go deleted file mode 100644 index daa108ef..00000000 --- a/go/modcdp/transport/NativeMessagingUpstreamTransport_test.go +++ /dev/null @@ -1,302 +0,0 @@ -package transport_test - -import ( - "encoding/json" - "fmt" - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/transport" - "net" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "testing" - "time" -) - -func TestNativeMessagingUpstreamTransportConfigOwnsManifestHostWaitTimeoutLoopbackAndInjectorConfig(t *testing.T) { - encoded, err := json.Marshal(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingManifest: "/tmp/modcdp-native-host.json", - UpstreamNativeMessagingManifests: []string{"/tmp/modcdp-native-host-extra.json"}, - UpstreamNativeMessagingHostName: "com.modcdp.test", - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - UpstreamNativeMessagingWaitTimeoutMS: 10, - }) - if err != nil { - t.Fatal(err) - } - if raw := string(encoded); raw != `{"upstream_nativemessaging_manifest":"/tmp/modcdp-native-host.json","upstream_nativemessaging_manifests":["/tmp/modcdp-native-host-extra.json"],"upstream_nativemessaging_host_name":"com.modcdp.test","injector_extension_id":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","upstream_nativemessaging_wait_timeout_ms":10}` { - t.Fatalf("NativeMessagingUpstreamTransportOptions JSON = %s", raw) - } - - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingManifest: "/tmp/modcdp-native-host.json", - UpstreamNativeMessagingManifests: []string{"/tmp/modcdp-native-host-extra.json"}, - UpstreamNativeMessagingHostName: "com.modcdp.test", - InjectorExtensionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - UpstreamNativeMessagingWaitTimeoutMS: 10, - }) - transport.Update(map[string]any{ - "cdp_url": "ws://127.0.0.1:9222/devtools/browser/test", - "upstream_nativemessaging_manifests": []string{}, - "upstream_nativemessaging_host_name": "com.modcdp.updated", - "upstream_nativemessaging_wait_timeout_ms": 5, - }) - if transport.GetInjectorConfig().UpstreamNativeMessagingHostName != "com.modcdp.updated" { - t.Fatalf("updated injector config = %#v", transport.GetInjectorConfig()) - } - if transport.GetServerConfig()["server_loopback_cdp_url"] != "ws://127.0.0.1:9222/devtools/browser/test" { - t.Fatalf("server config = %#v", transport.GetServerConfig()) - } - if transport.UpstreamNativeMessagingManifest != "/tmp/modcdp-native-host.json" { - t.Fatalf("UpstreamNativeMessagingManifest = %q", transport.UpstreamNativeMessagingManifest) - } - if transport.ExtensionID != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" { - t.Fatalf("ExtensionID = %q", transport.ExtensionID) - } - if transport.IncludeDefaultUpstreamNativeMessagingManifests { - t.Fatal("IncludeDefaultUpstreamNativeMessagingManifests should stay false while UpstreamNativeMessagingManifest is set") - } - transport.Update(map[string]any{"upstream_nativemessaging_manifest": ""}) - if !transport.IncludeDefaultUpstreamNativeMessagingManifests { - t.Fatal("IncludeDefaultUpstreamNativeMessagingManifests should be true after clearing UpstreamNativeMessagingManifest and UpstreamNativeMessagingManifests") - } - transport.Update(map[string]any{"user_data_dir": "/tmp/modcdp-profile-one"}) - transport.Update(map[string]any{"user_data_dir": "/tmp/modcdp-profile-one"}) - transport.Update(map[string]any{"user_data_dir": "/tmp/modcdp-profile-two"}) - expectedUpstreamNativeMessagingManifests := []string{ - filepath.Join("/tmp/modcdp-profile-two", "NativeMessagingHosts", "com.modcdp.updated.json"), - filepath.Join("/tmp/modcdp-profile-two", "Default", "NativeMessagingHosts", "com.modcdp.updated.json"), - } - if strings.Join(transport.UpstreamNativeMessagingManifests, "\n") != strings.Join(expectedUpstreamNativeMessagingManifests, "\n") { - t.Fatalf("UpstreamNativeMessagingManifests = %#v", transport.UpstreamNativeMessagingManifests) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms for native messaging host com.modcdp.updated") { - t.Fatalf("WaitForPeer error = %v", err) - } -} - -func TestNativeMessagingUpstreamTransportSendBeforePeerErrorsImmediately(t *testing.T) { - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingHostName: "com.modcdp.send.before.peer", - UpstreamNativeMessagingWaitTimeoutMS: 5_000, - }) - started := time.Now() - err := transport.Send(map[string]any{"id": 1, "method": "Browser.getVersion"}) - if err == nil || !strings.Contains(err.Error(), "no native messaging peer is connected for com.modcdp.send.before.peer") { - t.Fatalf("Send error = %v", err) - } - if elapsed := time.Since(started); elapsed > 250*time.Millisecond { - t.Fatalf("Send waited for peer: elapsed = %s", elapsed) - } -} - -func TestNativeMessagingUpstreamTransportCloseResetsPeerWaitState(t *testing.T) { - nativeHostName := fmt.Sprintf("com.modcdp.close.reset.go.%d", os.Getpid()) - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingHostName: nativeHostName, - UpstreamNativeMessagingWaitTimeoutMS: 5, - }) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", transport.BoundPort)) - if err != nil { - t.Fatal(err) - } - - defer conn.Close() - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before close = %v", err) - } - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms for native messaging host "+nativeHostName) { - t.Fatalf("WaitForPeer after close = %v", err) - } - if !transport.Closed() { - t.Fatalf("closed after Close = %v", transport.Closed()) - } -} - -func TestNativeMessagingUpstreamTransportWaitsAgainAfterPeerDisconnects(t *testing.T) { - nativeHostName := fmt.Sprintf("com.modcdp.disconnect.reset.go.%d", os.Getpid()) - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingHostName: nativeHostName, - UpstreamNativeMessagingWaitTimeoutMS: 5, - }) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", transport.BoundPort)) - if err != nil { - t.Fatal(err) - } - - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before peer disconnect = %v", err) - } - if err := conn.Close(); err != nil { - t.Fatal(err) - } - waitForNativePeerDisconnect(t, transport) - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms for native messaging host "+nativeHostName) { - t.Fatalf("WaitForPeer after peer disconnect = %v", err) - } -} - -func TestNativeMessagingUpstreamTransportAcceptsReplacementPeerAfterDisconnect(t *testing.T) { - nativeHostName := fmt.Sprintf("com.modcdp.replacement.go.%d", os.Getpid()) - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingHostName: nativeHostName, - UpstreamNativeMessagingWaitTimeoutMS: 500, - }) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - firstConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", transport.BoundPort)) - if err != nil { - t.Fatal(err) - } - - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before peer disconnect = %v", err) - } - if err := firstConn.Close(); err != nil { - t.Fatal(err) - } - waitForNativePeerDisconnect(t, transport) - secondConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", transport.BoundPort)) - if err != nil { - t.Fatal(err) - } - defer secondConn.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer after replacement peer = %v", err) - } -} - -func TestNativeMessagingUpstreamTransportCloseRejectsPendingPeerWaits(t *testing.T) { - transport := NewNativeMessagingUpstreamTransport(NativeMessagingUpstreamTransportOptions{ - UpstreamNativeMessagingHostName: "com.modcdp.close", - UpstreamNativeMessagingWaitTimeoutMS: 5_000, - }) - done := make(chan error, 1) - go func() { - done <- transport.WaitForPeer() - }() - time.Sleep(50 * time.Millisecond) - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - select { - case err := <-done: - if err == nil || !strings.Contains(err.Error(), "native messaging transport for com.modcdp.close closed before a peer connected") { - t.Fatalf("WaitForPeer close error = %v", err) - } - case <-time.After(time.Second): - t.Fatal("WaitForPeer did not return after Close") - } -} - -func waitForNativePeerDisconnect(t *testing.T, transport *NativeMessagingUpstreamTransport) { - t.Helper() - deadline := time.Now().Add(2 * time.Second) - for time.Now().Before(deadline) { - err := transport.Send(map[string]any{"id": 99, "method": "Browser.getVersion"}) - if err != nil && strings.Contains(err.Error(), "no native messaging peer is connected") { - return - } - time.Sleep(20 * time.Millisecond) - } - t.Fatal("timed out waiting for native peer disconnect") -} - -func TestNativeMessagingUpstreamTransportInstallsLaunchProfileManifestAndConnectsToRealExtension(t *testing.T) { - nativeHostName := "com.modcdp.bridge" - headless := runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - profileDir, err := os.MkdirTemp("", "modcdp.native.") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(profileDir) - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", - LauncherOptions: modcdp.LaunchOptions{ - Headless: boolPtr(headless), - UserDataDir: profileDir, - CleanupUserDataDir: boolPtr(true), - // Native messaging is browser -> client only. After explicit CHROME_PATH - // and CI /usr/bin/chromium, this test uses Chrome for Testing because - // Canary rejects --load-extension in this local test path. - ExecutablePath: reverseWSTestBrowserPath(t), - }, - }, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "nativemessaging", UpstreamNativeMessagingHostName: nativeHostName}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "auto", - InjectorExtensionPath: extensionPath, - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - Server: &modcdp.ServerConfig{ServerRoutes: map[string]string{"*.*": "loopback_cdp"}}, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["upstream_endpoint_kind"] != UpstreamEndpointKindModCDPServer { - t.Fatalf("upstream_endpoint_kind = %v", cdp.ConnectTiming["upstream_endpoint_kind"]) - } - transport, ok := cdp.Transport().(*NativeMessagingUpstreamTransport) - if !ok { - t.Fatalf("transport = %T", cdp.Transport()) - } - if !regexp.MustCompile(`^native://` + regexp.QuoteMeta(nativeHostName) + `@127\.0\.0\.1:\d+$`).MatchString(transport.URL) { - t.Fatalf("transport.URL = %q", transport.URL) - } - if cdp.LaunchedBrowser() == nil { - t.Fatal("expected launched browser") - } - manifestPath := filepath.Join( - cdp.LaunchedBrowser().ProfileDir, - "NativeMessagingHosts", - nativeHostName+".json", - ) - if _, err := os.Stat(manifestPath); err != nil { - t.Fatalf("native messaging profile manifest was not installed at %s", manifestPath) - } - result, err := cdp.Send("Browser.getVersion", map[string]any{}) - if err != nil { - t.Fatal(err) - } - version, ok := result.(map[string]any) - if !ok { - t.Fatalf("Browser.getVersion result = %#v", result) - } - if _, ok := version["product"].(string); !ok { - t.Fatalf("Browser.getVersion product = %#v", version["product"]) - } - time.Sleep(1500 * time.Millisecond) - secondResult, err := cdp.Send("Browser.getVersion", map[string]any{}) - if err != nil { - t.Fatal(err) - } - secondVersion, ok := secondResult.(map[string]any) - if !ok { - t.Fatalf("second Browser.getVersion result = %#v", secondResult) - } - if _, ok := secondVersion["product"].(string); !ok { - t.Fatalf("second Browser.getVersion product = %#v", secondVersion["product"]) - } -} diff --git a/go/modcdp/transport/NatsUpstreamTransport.go b/go/modcdp/transport/NatsUpstreamTransport.go deleted file mode 100644 index 3e11e86b..00000000 --- a/go/modcdp/transport/NatsUpstreamTransport.go +++ /dev/null @@ -1,404 +0,0 @@ -package transport - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "net" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -const DefaultUpstreamNATSURL = "ws://127.0.0.1:4223" -const DefaultUpstreamNATSSubjectPrefix = "modcdp.default" -const DefaultUpstreamNATSWaitTimeoutMS = 10_000 - -type NatsUpstreamTransport struct { - UpstreamTransport - URL string - UpstreamNATSSubjectPrefix string - UpstreamNATSRole string - WaitTimeoutMS int - Conn net.Conn - IsWebSocket bool - buffer string - connected bool - closed bool - writeMu sync.Mutex - bufferMu sync.Mutex - peerCh chan struct{} - peerOnce sync.Once - closeCh chan struct{} - stateMu sync.Mutex -} - -type NatsUpstreamTransportOptions struct { - UpstreamNATSURL string `json:"upstream_nats_url,omitempty"` - UpstreamNATSSubjectPrefix string `json:"upstream_nats_subject_prefix,omitempty"` - UpstreamNATSRole string `json:"upstream_nats_role,omitempty"` - UpstreamNATSWaitTimeoutMS int `json:"upstream_nats_wait_timeout_ms,omitempty"` -} - -func NewNatsUpstreamTransport(options NatsUpstreamTransportOptions) *NatsUpstreamTransport { - normalizedURL, subjectPrefix := normalizeUpstreamNATSURL(firstNonEmptyString(options.UpstreamNATSURL, DefaultUpstreamNATSURL), options.UpstreamNATSSubjectPrefix) - role := firstNonEmptyString(options.UpstreamNATSRole, "client") - waitTimeoutMS := options.UpstreamNATSWaitTimeoutMS - if waitTimeoutMS == 0 { - waitTimeoutMS = DefaultUpstreamNATSWaitTimeoutMS - } - return &NatsUpstreamTransport{ - URL: normalizedURL, - UpstreamNATSSubjectPrefix: subjectPrefix, - UpstreamNATSRole: role, - WaitTimeoutMS: waitTimeoutMS, - peerCh: make(chan struct{}), - closeCh: make(chan struct{}), - } -} - -func (t *NatsUpstreamTransport) Update(config map[string]any) { - if config == nil { - return - } - natsURL, _ := config["upstream_nats_url"].(string) - subjectPrefix, _ := config["upstream_nats_subject_prefix"].(string) - if natsURL != "" || subjectPrefix != "" { - t.URL, t.UpstreamNATSSubjectPrefix = normalizeUpstreamNATSURL(firstNonEmptyString(natsURL, t.URL), firstNonEmptyString(subjectPrefix, t.UpstreamNATSSubjectPrefix)) - } - if role, _ := config["upstream_nats_role"].(string); role == "client" || role == "browser" { - t.UpstreamNATSRole = role - } - if waitTimeoutMS, ok := intFromConfig(config["upstream_nats_wait_timeout_ms"]); ok { - t.WaitTimeoutMS = waitTimeoutMS - } -} - -func (t *NatsUpstreamTransport) GetInjectorConfig() ExtensionInjectorConfig { - return ExtensionInjectorConfig{UpstreamNATSURL: t.URL, UpstreamNATSSubjectPrefix: t.UpstreamNATSSubjectPrefix} -} - -func (t *NatsUpstreamTransport) Connect() error { - if t.Connected() { - return nil - } - t.stateMu.Lock() - t.closed = false - t.closeCh = make(chan struct{}) - closeCh := t.closeCh - t.stateMu.Unlock() - if !validUpstreamNATSSubjectPrefix(t.UpstreamNATSSubjectPrefix) { - return fmt.Errorf("invalid NATS subject prefix %q", t.UpstreamNATSSubjectPrefix) - } - parsed, err := url.Parse(t.URL) - if err != nil { - return err - } - switch parsed.Scheme { - case "ws", "wss": - conn, _, _, err := ws.Dial(context.Background(), t.URL) - if err != nil { - return err - } - t.writeMu.Lock() - t.Conn = conn - t.IsWebSocket = true - t.writeMu.Unlock() - if err := t.writeProtocol("CONNECT " + mustJSON(connectNATSOptions()) + "\r\nPING\r\n"); err != nil { - t.cleanupFailedConnect(conn) - return err - } - go t.readWebSocketLoop(conn, closeCh) - case "nats", "tls": - host := parsed.Hostname() - if host == "" { - host = "127.0.0.1" - } - port := parsed.Port() - if port == "" { - port = "4222" - } - var conn net.Conn - if parsed.Scheme == "tls" { - conn, err = tls.Dial("tcp", net.JoinHostPort(host, port), &tls.Config{ServerName: host}) - } else { - conn, err = net.Dial("tcp", net.JoinHostPort(host, port)) - } - if err != nil { - return err - } - t.writeMu.Lock() - t.Conn = conn - t.writeMu.Unlock() - if err := t.writeProtocol("CONNECT " + mustJSON(connectNATSOptions()) + "\r\nPING\r\n"); err != nil { - t.cleanupFailedConnect(conn) - return err - } - go t.readTCPLoop(conn, closeCh) - default: - return fmt.Errorf("upstream.upstream_mode=nats requires ws://, wss://, nats://, or tls:// URL, got %s", t.URL) - } - if err := t.subscribe(); err != nil { - t.cleanupFailedConnect(t.currentConn()) - return err - } - if err := t.publish(t.outgoingSubject(), map[string]any{"type": "modcdp.nats.hello", "role": t.UpstreamNATSRole, "version": 1}); err != nil { - t.cleanupFailedConnect(t.currentConn()) - return err - } - t.stateMu.Lock() - t.connected = true - t.closed = false - t.stateMu.Unlock() - return nil -} - -func (t *NatsUpstreamTransport) Send(message map[string]any) error { - if !t.Connected() || t.currentConn() == nil { - return fmt.Errorf("NATS transport is not connected") - } - return t.publish(t.outgoingSubject(), map[string]any{"type": "modcdp.nats.message", "message": message}) -} - -func (t *NatsUpstreamTransport) WaitForPeer() error { - t.stateMu.Lock() - closeCh := t.closeCh - peerCh := t.peerCh - t.stateMu.Unlock() - select { - case <-peerCh: - return nil - case <-closeCh: - return fmt.Errorf("NATS transport for %s closed before a peer connected", t.UpstreamNATSSubjectPrefix) - case <-time.After(time.Duration(t.WaitTimeoutMS) * time.Millisecond): - return fmt.Errorf("timed out waiting %dms for NATS ModCDP peer", t.WaitTimeoutMS) - } -} - -func (t *NatsUpstreamTransport) Close() error { - t.stateMu.Lock() - t.closed = true - t.connected = false - closeCh := t.closeCh - t.closeCh = make(chan struct{}) - t.peerCh = make(chan struct{}) - t.peerOnce = sync.Once{} - t.stateMu.Unlock() - close(closeCh) - t.bufferMu.Lock() - t.buffer = "" - t.bufferMu.Unlock() - t.writeMu.Lock() - if t.Conn != nil { - _ = t.Conn.Close() - t.Conn = nil - } - t.writeMu.Unlock() - return nil -} - -func (t *NatsUpstreamTransport) Connected() bool { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.connected -} - -func (t *NatsUpstreamTransport) Closed() bool { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.closed -} - -func (t *NatsUpstreamTransport) currentConn() net.Conn { - t.writeMu.Lock() - defer t.writeMu.Unlock() - return t.Conn -} - -func (t *NatsUpstreamTransport) cleanupFailedConnect(conn net.Conn) { - if conn != nil { - _ = conn.Close() - } - t.writeMu.Lock() - if t.Conn == conn { - t.Conn = nil - } - t.writeMu.Unlock() - t.stateMu.Lock() - t.connected = false - t.stateMu.Unlock() -} - -func natsClosed(closeCh chan struct{}) bool { - select { - case <-closeCh: - return true - default: - return false - } -} - -func (t *NatsUpstreamTransport) subscribe() error { - return t.writeProtocol("SUB " + t.incomingSubject() + " 1\r\n") -} - -func (t *NatsUpstreamTransport) publish(subject string, message map[string]any) error { - body, err := json.Marshal(message) - if err != nil { - return err - } - return t.writeProtocol(fmt.Sprintf("PUB %s %d\r\n%s\r\n", subject, len(body), string(body))) -} - -func (t *NatsUpstreamTransport) writeProtocol(data string) error { - t.writeMu.Lock() - defer t.writeMu.Unlock() - conn := t.Conn - if conn == nil { - return fmt.Errorf("NATS transport is not connected") - } - if t.IsWebSocket { - return wsutil.WriteClientText(conn, []byte(data)) - } - _, err := conn.Write([]byte(data)) - return err -} - -func (t *NatsUpstreamTransport) incomingSubject() string { - if t.UpstreamNATSRole == "client" { - return t.UpstreamNATSSubjectPrefix + ".browser_to_client" - } - return t.UpstreamNATSSubjectPrefix + ".client_to_browser" -} - -func (t *NatsUpstreamTransport) outgoingSubject() string { - if t.UpstreamNATSRole == "client" { - return t.UpstreamNATSSubjectPrefix + ".client_to_browser" - } - return t.UpstreamNATSSubjectPrefix + ".browser_to_client" -} - -func (t *NatsUpstreamTransport) readWebSocketLoop(conn net.Conn, closeCh chan struct{}) { - for !natsClosed(closeCh) { - data, _, err := wsutil.ReadServerData(conn) - if err != nil { - if !natsClosed(closeCh) { - t.emitClose(err) - } - return - } - t.bufferMu.Lock() - t.buffer = t.consumeProtocol(t.buffer + string(data)) - t.bufferMu.Unlock() - } -} - -func (t *NatsUpstreamTransport) readTCPLoop(conn net.Conn, closeCh chan struct{}) { - chunk := make([]byte, 65536) - for !natsClosed(closeCh) { - n, err := conn.Read(chunk) - if err != nil { - if !natsClosed(closeCh) { - t.emitClose(err) - } - return - } - t.bufferMu.Lock() - t.buffer = t.consumeProtocol(t.buffer + string(chunk[:n])) - t.bufferMu.Unlock() - } -} - -func (t *NatsUpstreamTransport) consumeProtocol(buffer string) string { - for { - lineEnd := strings.Index(buffer, "\r\n") - if lineEnd < 0 { - return buffer - } - line := buffer[:lineEnd] - upper := strings.ToUpper(line) - if strings.HasPrefix(upper, "MSG ") { - parts := strings.Fields(line) - size, err := strconv.Atoi(parts[len(parts)-1]) - payloadStart := lineEnd + 2 - payloadEnd := payloadStart + size - if err != nil || len(buffer) < payloadEnd+2 { - return buffer - } - payload := buffer[payloadStart:payloadEnd] - buffer = buffer[payloadEnd+2:] - t.handlePayload(payload) - continue - } - buffer = buffer[lineEnd+2:] - if upper == "PING" { - _ = t.writeProtocol("PONG\r\n") - } else if strings.HasPrefix(upper, "-ERR") { - t.emitClose(fmt.Errorf("NATS error: %s", line)) - } - } -} - -func (t *NatsUpstreamTransport) handlePayload(payload string) { - var parsed any - if err := json.Unmarshal([]byte(payload), &parsed); err != nil { - return - } - record, _ := parsed.(map[string]any) - if record["type"] == "modcdp.nats.hello" { - t.stateMu.Lock() - t.peerOnce.Do(func() { close(t.peerCh) }) - t.stateMu.Unlock() - return - } - message := parsed - if record["type"] == "modcdp.nats.message" { - message = record["message"] - } - if cdpMessage, ok := message.(map[string]any); ok { - t.emitRecv(cdpMessage) - } -} - -func (t *NatsUpstreamTransport) HandlePayload(payload string) { - t.handlePayload(payload) -} - -func connectNATSOptions() map[string]any { - return map[string]any{"verbose": false, "pedantic": false, "lang": "modcdp", "version": "1", "protocol": 1} -} - -func normalizeUpstreamNATSURL(rawURL string, subjectPrefix string) (string, string) { - parsed, err := url.Parse(rawURL) - if err != nil { - return rawURL, sanitizeUpstreamNATSSubjectPrefix(firstNonEmptyString(subjectPrefix, DefaultUpstreamNATSSubjectPrefix)) - } - query := parsed.Query() - subject := firstNonEmptyString(subjectPrefix, query.Get("upstream_nats_subject_prefix"), DefaultUpstreamNATSSubjectPrefix) - query.Del("upstream_nats_subject_prefix") - parsed.RawQuery = query.Encode() - if parsed.Path == "" && (parsed.Scheme == "ws" || parsed.Scheme == "wss") { - parsed.Path = "/" - } - return parsed.String(), sanitizeUpstreamNATSSubjectPrefix(subject) -} - -func sanitizeUpstreamNATSSubjectPrefix(value string) string { - return strings.TrimSpace(value) -} - -func validUpstreamNATSSubjectPrefix(value string) bool { - subject := strings.TrimSpace(value) - return subject != "" && !strings.ContainsAny(subject, " \t\r\n*>") -} - -func mustJSON(value any) string { - body, _ := json.Marshal(value) - return string(body) -} diff --git a/go/modcdp/transport/NatsUpstreamTransport_test.go b/go/modcdp/transport/NatsUpstreamTransport_test.go deleted file mode 100644 index 4a1bc507..00000000 --- a/go/modcdp/transport/NatsUpstreamTransport_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package transport_test - -import ( - "context" - "encoding/json" - "fmt" - . "github.com/browserbase/modcdp/go/modcdp/transport" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/gobwas/ws" -) - -func TestNatsUpstreamTransportConfigOwnsURLUpstreamNATSSubjectPrefixWaitTimeoutAndInjectorConfig(t *testing.T) { - encoded, err := json.Marshal(NatsUpstreamTransportOptions{ - UpstreamNATSURL: "ws://127.0.0.1:4223", - UpstreamNATSSubjectPrefix: "modcdp.one", - UpstreamNATSRole: "client", - UpstreamNATSWaitTimeoutMS: 10, - }) - if err != nil { - t.Fatal(err) - } - if raw := string(encoded); raw != `{"upstream_nats_url":"ws://127.0.0.1:4223","upstream_nats_subject_prefix":"modcdp.one","upstream_nats_role":"client","upstream_nats_wait_timeout_ms":10}` { - t.Fatalf("NatsUpstreamTransportOptions JSON = %s", raw) - } - - transport := NewNatsUpstreamTransport(NatsUpstreamTransportOptions{ - UpstreamNATSURL: "ws://127.0.0.1:4223", - UpstreamNATSSubjectPrefix: "modcdp.one", - }) - if transport.URL != "ws://127.0.0.1:4223/" { - t.Fatalf("URL = %q", transport.URL) - } - if transport.UpstreamNATSSubjectPrefix != "modcdp.one" { - t.Fatalf("UpstreamNATSSubjectPrefix = %q", transport.UpstreamNATSSubjectPrefix) - } - injectorConfig := transport.GetInjectorConfig() - if injectorConfig.UpstreamNATSURL != "ws://127.0.0.1:4223/" || injectorConfig.UpstreamNATSSubjectPrefix != "modcdp.one" { - t.Fatalf("injector config = %#v", injectorConfig) - } - transport.Update(map[string]any{ - "upstream_nats_url": "nats://127.0.0.1:4222", - "upstream_nats_subject_prefix": "modcdp.two", - "upstream_nats_role": "browser", - "upstream_nats_wait_timeout_ms": 5, - }) - if transport.URL != "nats://127.0.0.1:4222" { - t.Fatalf("URL after update = %q", transport.URL) - } - if transport.UpstreamNATSSubjectPrefix != "modcdp.two" { - t.Fatalf("UpstreamNATSSubjectPrefix after update = %q", transport.UpstreamNATSSubjectPrefix) - } - if transport.UpstreamNATSRole != "browser" { - t.Fatalf("UpstreamNATSRole after update = %q", transport.UpstreamNATSRole) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms for NATS ModCDP peer") { - t.Fatalf("WaitForPeer error = %v", err) - } -} - -func TestNatsUpstreamTransportCloseResetsPeerWaitState(t *testing.T) { - transport := NewNatsUpstreamTransport(NatsUpstreamTransportOptions{UpstreamNATSWaitTimeoutMS: 5}) - - transport.HandlePayload(`{"type":"modcdp.nats.hello","role":"browser","version":1}`) - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before close = %v", err) - } - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms for NATS ModCDP peer") { - t.Fatalf("WaitForPeer after close = %v", err) - } - if transport.Closed() != true { - t.Fatalf("closed after Close = %v", transport.Closed()) - } -} - -func TestNatsUpstreamTransportCloseRejectsPendingPeerWaits(t *testing.T) { - transport := NewNatsUpstreamTransport(NatsUpstreamTransportOptions{ - UpstreamNATSURL: "ws://127.0.0.1:4223", - UpstreamNATSSubjectPrefix: "modcdp.close", - UpstreamNATSWaitTimeoutMS: 5_000, - }) - done := make(chan error, 1) - go func() { - done <- transport.WaitForPeer() - }() - time.Sleep(50 * time.Millisecond) - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - select { - case err := <-done: - if err == nil || !strings.Contains(err.Error(), "NATS transport for modcdp.close closed before a peer connected") { - t.Fatalf("WaitForPeer close error = %v", err) - } - case <-time.After(time.Second): - t.Fatal("WaitForPeer did not return after Close") - } -} - -func TestNatsUpstreamTransportReconnectsAfterCloseAgainstRealNATSServer(t *testing.T) { - nats := startNATSServer(t) - defer nats.close() - transport := NewNatsUpstreamTransport(NatsUpstreamTransportOptions{ - UpstreamNATSURL: nats.url, - UpstreamNATSSubjectPrefix: fmt.Sprintf("modcdp.reconnect.%d", time.Now().UnixMilli()), - }) - defer transport.Close() - - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - if !transport.Connected() { - t.Fatal("expected transport to be connected") - } - if err := transport.Close(); err != nil { - t.Fatal(err) - } - if transport.Connected() { - t.Fatal("expected transport to be disconnected after Close") - } - if !transport.Closed() { - t.Fatal("expected transport to be closed after Close") - } - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - if !transport.Connected() { - t.Fatal("expected transport to reconnect") - } - if transport.Closed() { - t.Fatal("expected transport.Closed() to reset after reconnect") - } -} - -type natsTestServer struct { - url string - close func() -} - -func startNATSServer(t *testing.T) natsTestServer { - t.Helper() - websocketPort, err := freePort() - if err != nil { - t.Fatal(err) - } - clientPort, err := freePort() - if err != nil { - t.Fatal(err) - } - dir := t.TempDir() - configPath := filepath.Join(dir, "nats.conf") - config := strings.Join([]string{ - `host: "127.0.0.1"`, - fmt.Sprintf("port: %d", clientPort), - "websocket {", - ` host: "127.0.0.1"`, - fmt.Sprintf(" port: %d", websocketPort), - " no_tls: true", - "}", - "", - }, "\n") - if err := os.WriteFile(configPath, []byte(config), 0o644); err != nil { - t.Fatal(err) - } - binaryPath := natsServerBinaryPath(t) - cmd := exec.Command(binaryPath, "-c", configPath) - cmd.Stdout = nil - cmd.Stderr = nil - if err := cmd.Start(); err != nil { - t.Fatal(err) - } - url := fmt.Sprintf("ws://127.0.0.1:%d", websocketPort) - closeServer := func() { - if cmd.Process == nil { - return - } - _ = cmd.Process.Kill() - _, _ = cmd.Process.Wait() - } - t.Cleanup(closeServer) - if err := waitForNATSWebSocket(url, 10*time.Second); err != nil { - closeServer() - t.Fatal(err) - } - return natsTestServer{url: url, close: closeServer} -} - -func natsServerBinaryPath(t *testing.T) string { - t.Helper() - cmd := exec.Command( - "pnpm", - "exec", - "node", - "--input-type=module", - "-e", - "import { getBinaryPath } from '@eplightning/nats-server'; console.log(await getBinaryPath())", - ) - cmd.Dir = filepath.Clean(filepath.Join("..", "..")) - body, err := cmd.Output() - if err != nil { - t.Fatal(err) - } - return strings.TrimSpace(string(body)) -} - -func waitForNATSWebSocket(rawURL string, timeout time.Duration) error { - deadline := time.Now().Add(timeout) - var lastErr error - for time.Now().Before(deadline) { - conn, _, _, err := ws.Dial(context.Background(), rawURL) - if err == nil { - _ = conn.Close() - return nil - } - lastErr = err - time.Sleep(50 * time.Millisecond) - } - if lastErr != nil { - return lastErr - } - return fmt.Errorf("timed out waiting for %s", rawURL) -} diff --git a/go/modcdp/transport/PipeUpstreamTransport.go b/go/modcdp/transport/PipeUpstreamTransport.go deleted file mode 100644 index 18c0225d..00000000 --- a/go/modcdp/transport/PipeUpstreamTransport.go +++ /dev/null @@ -1,107 +0,0 @@ -package transport - -import ( - "fmt" - "os" - "sync" - - "github.com/browserbase/modcdp/go/modcdp/launcher" -) - -type PipeUpstreamTransport struct { - UpstreamTransport - URL string - PipeRead *os.File - PipeWrite *os.File - writeMu sync.Mutex - stateMu sync.Mutex - closed bool -} - -type PipeUpstreamTransportOptions struct { - PipeRead *os.File `json:"-"` - PipeWrite *os.File `json:"-"` - CDPURL string `json:"cdp_url,omitempty"` -} - -func NewPipeUpstreamTransport(options PipeUpstreamTransportOptions) *PipeUpstreamTransport { - cdpURL := options.CDPURL - if cdpURL == "" { - cdpURL = "pipe://unknown" - } - return &PipeUpstreamTransport{URL: cdpURL, PipeRead: options.PipeRead, PipeWrite: options.PipeWrite} -} - -func (t *PipeUpstreamTransport) Update(config map[string]any) { - if config == nil { - return - } - if pipeRead, _ := config["pipe_read"].(*os.File); pipeRead != nil { - t.PipeRead = pipeRead - } - if pipeWrite, _ := config["pipe_write"].(*os.File); pipeWrite != nil { - t.PipeWrite = pipeWrite - } - if cdpURL, _ := config["cdp_url"].(string); cdpURL != "" { - t.URL = cdpURL - } -} - -func (t *PipeUpstreamTransport) GetLauncherConfig() LaunchOptions { - return LaunchOptions{RemoteDebugging: "pipe"} -} - -func (t *PipeUpstreamTransport) Connect() error { - if t.PipeRead == nil || t.PipeWrite == nil { - return fmt.Errorf("upstream.upstream_mode=pipe requires launcher-provided pipe_read and pipe_write handles") - } - t.setClosed(false) - go t.readLoop() - return nil -} - -func (t *PipeUpstreamTransport) Send(message map[string]any) error { - if t.PipeWrite == nil || t.isClosed() { - return fmt.Errorf("CDP pipe is not connected") - } - t.writeMu.Lock() - defer t.writeMu.Unlock() - return launcher.WritePipeMessage(t.PipeWrite, message) -} - -func (t *PipeUpstreamTransport) Close() error { - t.setClosed(true) - if t.PipeRead != nil { - _ = t.PipeRead.Close() - } - if t.PipeWrite != nil { - _ = t.PipeWrite.Close() - } - return nil -} - -func (t *PipeUpstreamTransport) readLoop() { - for { - message, err := launcher.ReadPipeMessage(t.PipeRead) - if err != nil { - if !t.isClosed() { - t.setClosed(true) - t.emitClose(err) - } - return - } - t.emitRecv(message) - } -} - -func (t *PipeUpstreamTransport) setClosed(closed bool) { - t.stateMu.Lock() - t.closed = closed - t.stateMu.Unlock() -} - -func (t *PipeUpstreamTransport) isClosed() bool { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.closed -} diff --git a/go/modcdp/transport/PipeUpstreamTransport_test.go b/go/modcdp/transport/PipeUpstreamTransport_test.go deleted file mode 100644 index b1fa9a28..00000000 --- a/go/modcdp/transport/PipeUpstreamTransport_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package transport_test - -import ( - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/transport" - "os" - "path/filepath" - "regexp" - "testing" - "time" -) - -func TestPipeUpstreamTransportConstructorUpdateLauncherConfigAndUnconnectedErrorsMatchTransportSurface(t *testing.T) { - transport := NewPipeUpstreamTransport(PipeUpstreamTransportOptions{CDPURL: "pipe://constructor"}) - if transport.URL != "pipe://constructor" { - t.Fatalf("URL = %q", transport.URL) - } - if launcherConfig := transport.GetLauncherConfig(); launcherConfig.RemoteDebugging != "pipe" { - t.Fatalf("launcher config = %#v", launcherConfig) - } - transport.Update(map[string]any{"cdp_url": "pipe://1234"}) - if transport.URL != "pipe://1234" { - t.Fatalf("URL after update = %q", transport.URL) - } - if err := transport.Connect(); err == nil { - t.Fatal("expected Connect to require pipe handles") - } - if err := transport.Send(map[string]any{"id": 1, "method": "Runtime.evaluate"}); err == nil { - t.Fatal("expected Send to require a connected pipe") - } -} - -func TestPipeUpstreamTransportResetsConnectionStateAfterPipeCloses(t *testing.T) { - pipeRead, pipeReadWriter, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - pipeWriteReader, pipeWrite, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - defer pipeRead.Close() - defer pipeReadWriter.Close() - defer pipeWriteReader.Close() - defer pipeWrite.Close() - - transport := NewPipeUpstreamTransport(PipeUpstreamTransportOptions{ - PipeRead: pipeRead, - PipeWrite: pipeWrite, - CDPURL: "pipe://test", - }) - closed := make(chan error, 1) - transport.OnClose(func(err error) { closed <- err }) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - if err := transport.Send(map[string]any{"id": 1, "method": "Runtime.evaluate", "params": map[string]any{"expression": "1"}}); err != nil { - t.Fatal(err) - } - _ = pipeReadWriter.Close() - select { - case <-closed: - case <-time.After(2 * time.Second): - t.Fatal("timed out waiting for pipe close") - } - if err := transport.Send(map[string]any{"id": 2, "method": "Runtime.evaluate", "params": map[string]any{"expression": "1"}}); err == nil { - t.Fatal("expected send to fail after pipe close") - } -} - -func TestPipeUpstreamTransportLaunchesRealBrowserAndUsesPIDScopedPipeURL(t *testing.T) { - extensionPath, err := filepath.Abs("../../../dist/extension") - if err != nil { - t.Fatal(err) - } - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", - LauncherOptions: modcdp.LaunchOptions{ - Headless: boolPtr(true), - }, - }, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "pipe"}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "inject", - InjectorExtensionPath: extensionPath, - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - Server: &modcdp.ServerConfig{ServerRoutes: map[string]string{"*.*": "chrome_debugger"}}, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["upstream_endpoint_kind"] != UpstreamEndpointKindRawCDP { - t.Fatalf("upstream_endpoint_kind = %v", cdp.ConnectTiming["upstream_endpoint_kind"]) - } - if cdp.Transport() == nil { - t.Fatal("expected pipe transport") - } - if !regexp.MustCompile(`^pipe://\d+$`).MatchString(cdp.CDPURL) { - t.Fatalf("CDPURL = %q", cdp.CDPURL) - } - pipeTransport, ok := cdp.Transport().(*PipeUpstreamTransport) - if !ok { - t.Fatalf("transport = %T", cdp.Transport()) - } - if pipeTransport.URL != cdp.CDPURL { - t.Fatalf("pipe transport URL = %q, CDPURL = %q", pipeTransport.URL, cdp.CDPURL) - } - if _, err := cdp.Mod.AddCustomCommand(modcdp.CustomCommand{ - Name: "Custom.runtimeReadyState", - Expression: "async () => await cdp.send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true })", - }); err != nil { - t.Fatal(err) - } - runtime, err := cdp.Send("Custom.runtimeReadyState", nil) - if err != nil { - t.Fatal(err) - } - runtimeMap, ok := runtime.(map[string]any) - if !ok { - t.Fatalf("Custom.runtimeReadyState = %#v", runtime) - } - runtimeResult, _ := runtimeMap["result"].(map[string]any) - if runtimeResult["value"] != "complete" { - t.Fatalf("Runtime.evaluate result = %#v", runtime) - } -} diff --git a/go/modcdp/transport/ReverseWebSocketUpstreamTransport.go b/go/modcdp/transport/ReverseWebSocketUpstreamTransport.go deleted file mode 100644 index d733e258..00000000 --- a/go/modcdp/transport/ReverseWebSocketUpstreamTransport.go +++ /dev/null @@ -1,242 +0,0 @@ -package transport - -import ( - "encoding/json" - "fmt" - "net" - "net/url" - "strings" - "sync" - "time" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -const DefaultUpstreamReverseWSBind = "127.0.0.1:29292" -const DefaultUpstreamReverseWSWaitTimeoutMS = 10_000 - -type ReverseWebSocketUpstreamTransport struct { - UpstreamTransport - Bind string - URL string - WaitTimeoutMS int - Listener net.Listener - Conn net.Conn - writeMu sync.Mutex - peerCh chan struct{} - peerOnce sync.Once - closeCh chan struct{} - stateMu sync.Mutex - PeerInfo map[string]any - generation int64 -} - -type ReverseWebSocketUpstreamTransportOptions struct { - UpstreamReverseWSBind string `json:"upstream_reversews_bind,omitempty"` - UpstreamReverseWSWaitTimeoutMS int `json:"upstream_reversews_wait_timeout_ms,omitempty"` -} - -func NewReverseWebSocketUpstreamTransport(options ReverseWebSocketUpstreamTransportOptions) *ReverseWebSocketUpstreamTransport { - reverseWSBind := options.UpstreamReverseWSBind - if reverseWSBind == "" { - reverseWSBind = DefaultUpstreamReverseWSBind - } - reverseWSWaitTimeoutMS := options.UpstreamReverseWSWaitTimeoutMS - if reverseWSWaitTimeoutMS == 0 { - reverseWSWaitTimeoutMS = DefaultUpstreamReverseWSWaitTimeoutMS - } - t := &ReverseWebSocketUpstreamTransport{WaitTimeoutMS: reverseWSWaitTimeoutMS, peerCh: make(chan struct{}), closeCh: make(chan struct{})} - t.setBind(reverseWSBind) - return t -} - -func (t *ReverseWebSocketUpstreamTransport) setBind(bind string) { - raw := bind - if !strings.Contains(raw, "://") { - raw = "ws://" + raw - } - parsed, err := url.Parse(raw) - if err != nil { - parsed, _ = url.Parse("ws://" + DefaultUpstreamReverseWSBind) - } - host := parsed.Hostname() - if host == "" { - host = "127.0.0.1" - } - port := parsed.Port() - if port == "" { - port = "29292" - } - t.Bind = net.JoinHostPort(host, port) - t.URL = "ws://" + t.Bind -} - -func (t *ReverseWebSocketUpstreamTransport) Update(config map[string]any) { - if config == nil { - return - } - if bind, _ := config["upstream_reversews_bind"].(string); bind != "" { - t.setBind(bind) - } - if waitTimeoutMS, ok := intFromConfig(config["upstream_reversews_wait_timeout_ms"]); ok { - t.WaitTimeoutMS = waitTimeoutMS - } -} - -func (t *ReverseWebSocketUpstreamTransport) Connect() error { - t.stateMu.Lock() - t.closeCh = make(chan struct{}) - t.stateMu.Unlock() - listener, err := net.Listen("tcp", t.Bind) - if err != nil { - return err - } - t.Listener = listener - go t.acceptLoop() - return nil -} - -func (t *ReverseWebSocketUpstreamTransport) Send(message map[string]any) error { - body, err := json.Marshal(message) - if err != nil { - return err - } - t.writeMu.Lock() - defer t.writeMu.Unlock() - conn := t.Conn - if conn == nil { - return fmt.Errorf("no reverse ModCDP extension peer is connected at %s", t.URL) - } - return wsutil.WriteServerText(conn, body) -} - -func (t *ReverseWebSocketUpstreamTransport) GetInjectorConfig() ExtensionInjectorConfig { - return ExtensionInjectorConfig{} -} - -func (t *ReverseWebSocketUpstreamTransport) WaitForPeer() error { - t.writeMu.Lock() - connected := t.Conn != nil - t.writeMu.Unlock() - if connected { - return nil - } - t.stateMu.Lock() - closeCh := t.closeCh - peerCh := t.peerCh - t.stateMu.Unlock() - select { - case <-peerCh: - return nil - case <-closeCh: - return fmt.Errorf("reverse websocket transport at %s closed before a peer connected", t.URL) - case <-time.After(time.Duration(t.WaitTimeoutMS) * time.Millisecond): - return fmt.Errorf("timed out waiting %dms for reverse ModCDP extension connection", t.WaitTimeoutMS) - } -} - -func (t *ReverseWebSocketUpstreamTransport) PeerGeneration() int64 { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.generation -} - -func (t *ReverseWebSocketUpstreamTransport) Close() error { - t.stateMu.Lock() - closeCh := t.closeCh - t.closeCh = make(chan struct{}) - t.peerCh = make(chan struct{}) - t.peerOnce = sync.Once{} - t.stateMu.Unlock() - close(closeCh) - t.writeMu.Lock() - if t.Conn != nil { - _ = t.Conn.Close() - t.Conn = nil - } - t.writeMu.Unlock() - if t.Listener != nil { - _ = t.Listener.Close() - t.Listener = nil - } - t.stateMu.Lock() - t.PeerInfo = nil - t.stateMu.Unlock() - return nil -} - -func (t *ReverseWebSocketUpstreamTransport) acceptLoop() { - for { - conn, err := t.Listener.Accept() - if err != nil { - return - } - go t.accept(conn) - } -} - -func (t *ReverseWebSocketUpstreamTransport) accept(conn net.Conn) { - if _, err := ws.Upgrade(conn); err != nil { - _ = conn.Close() - t.emitClose(err) - return - } - _ = conn.SetReadDeadline(time.Now().Add(time.Duration(t.WaitTimeoutMS) * time.Millisecond)) - helloBytes, err := wsutil.ReadClientText(conn) - _ = conn.SetReadDeadline(time.Time{}) - if err != nil { - _ = conn.Close() - t.emitClose(err) - return - } - var hello map[string]any - if err := json.Unmarshal(helloBytes, &hello); err != nil || hello["type"] != "modcdp.reverse.hello" { - _ = conn.Close() - if err == nil { - err = fmt.Errorf("invalid reverse hello") - } - t.emitClose(err) - return - } - t.writeMu.Lock() - if t.Conn != nil && t.Conn != conn { - _ = t.Conn.Close() - } - t.Conn = conn - t.writeMu.Unlock() - t.stateMu.Lock() - t.PeerInfo = hello - t.generation++ - t.peerOnce.Do(func() { close(t.peerCh) }) - t.stateMu.Unlock() - for { - data, err := wsutil.ReadClientText(conn) - if err != nil { - t.writeMu.Lock() - if t.Conn == conn { - t.Conn = nil - t.writeMu.Unlock() - t.stateMu.Lock() - t.PeerInfo = nil - t.stateMu.Unlock() - t.resetPeerWait() - } else { - t.writeMu.Unlock() - } - t.emitClose(err) - return - } - var message map[string]any - if err := json.Unmarshal(data, &message); err == nil { - t.emitRecv(message) - } - } -} - -func (t *ReverseWebSocketUpstreamTransport) resetPeerWait() { - t.stateMu.Lock() - defer t.stateMu.Unlock() - t.peerCh = make(chan struct{}) - t.peerOnce = sync.Once{} -} diff --git a/go/modcdp/transport/ReverseWebSocketUpstreamTransport_test.go b/go/modcdp/transport/ReverseWebSocketUpstreamTransport_test.go deleted file mode 100644 index a50b6dd0..00000000 --- a/go/modcdp/transport/ReverseWebSocketUpstreamTransport_test.go +++ /dev/null @@ -1,395 +0,0 @@ -package transport_test - -import ( - "context" - "encoding/json" - "fmt" - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/transport" - "os" - "path/filepath" - "reflect" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "testing" - "time" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -func TestReverseWebSocketUpstreamTransportConfigOwnsBindUpdatesAndWaitTimeout(t *testing.T) { - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: "127.0.0.1:29292", UpstreamReverseWSWaitTimeoutMS: 10}) - if transport.URL != "ws://127.0.0.1:29292" { - t.Fatalf("URL = %q", transport.URL) - } - if !reflect.DeepEqual(transport.GetInjectorConfig(), ExtensionInjectorConfig{}) { - t.Fatalf("injector config = %#v", transport.GetInjectorConfig()) - } - transport.Update(map[string]any{"upstream_reversews_bind": "127.0.0.1:29293", "upstream_reversews_wait_timeout_ms": 5}) - if transport.URL != "ws://127.0.0.1:29293" { - t.Fatalf("URL after update = %q", transport.URL) - } - if !reflect.DeepEqual(transport.GetInjectorConfig(), ExtensionInjectorConfig{}) { - t.Fatalf("injector config after update = %#v", transport.GetInjectorConfig()) - } - transport.Update(map[string]any{"upstream_reversews_bind": "http://127.0.0.1:29294"}) - if transport.URL != "ws://127.0.0.1:29294" { - t.Fatalf("URL after http update = %q", transport.URL) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms") { - t.Fatalf("WaitForPeer error = %v", err) - } -} - -func TestReverseWebSocketUpstreamTransportSendBeforePeerErrorsImmediately(t *testing.T) { - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: "127.0.0.1:29292", UpstreamReverseWSWaitTimeoutMS: 5_000}) - started := time.Now() - err := transport.Send(map[string]any{"id": 1, "method": "Browser.getVersion"}) - if err == nil || !strings.Contains(err.Error(), "no reverse ModCDP extension peer is connected at ws://127.0.0.1:29292") { - t.Fatalf("Send error = %v", err) - } - if elapsed := time.Since(started); elapsed > 250*time.Millisecond { - t.Fatalf("Send waited for peer: elapsed = %s", elapsed) - } -} - -func TestReverseWebSocketUpstreamTransportCloseResetsPeerWaitState(t *testing.T) { - port, err := freePort() - if err != nil { - t.Fatal(err) - } - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: fmt.Sprintf("127.0.0.1:%d", port), UpstreamReverseWSWaitTimeoutMS: 5}) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - conn, _, _, err := ws.Dial(context.Background(), transport.URL) - if err != nil { - t.Fatal(err) - } - hello, err := json.Marshal(map[string]any{"type": "modcdp.reverse.hello", "role": "test-peer", "version": 1}) - if err != nil { - t.Fatal(err) - } - if err := wsutil.WriteClientText(conn, hello); err != nil { - t.Fatal(err) - } - - defer conn.Close() - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before close = %v", err) - } - if transport.PeerInfo["role"] != "test-peer" { - t.Fatalf("PeerInfo before close = %#v", transport.PeerInfo) - } - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms") { - t.Fatalf("WaitForPeer after close = %v", err) - } - if transport.PeerInfo != nil { - t.Fatalf("PeerInfo after close = %#v", transport.PeerInfo) - } -} - -func TestReverseWebSocketUpstreamTransportWaitsAgainAfterPeerDisconnects(t *testing.T) { - port, err := freePort() - if err != nil { - t.Fatal(err) - } - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: fmt.Sprintf("127.0.0.1:%d", port), UpstreamReverseWSWaitTimeoutMS: 5}) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - conn, _, _, err := ws.Dial(context.Background(), transport.URL) - if err != nil { - t.Fatal(err) - } - hello, err := json.Marshal(map[string]any{"type": "modcdp.reverse.hello", "role": "test-peer", "version": 1}) - if err != nil { - t.Fatal(err) - } - if err := wsutil.WriteClientText(conn, hello); err != nil { - t.Fatal(err) - } - - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before peer disconnect = %v", err) - } - if err := conn.Close(); err != nil { - t.Fatal(err) - } - waitForReversePeerDisconnect(t, transport) - if err := transport.WaitForPeer(); err == nil || !strings.Contains(err.Error(), "timed out waiting 5ms") { - t.Fatalf("WaitForPeer after peer disconnect = %v", err) - } -} - -func TestReverseWebSocketUpstreamTransportAcceptsReplacementPeerAfterDisconnect(t *testing.T) { - port, err := freePort() - if err != nil { - t.Fatal(err) - } - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: fmt.Sprintf("127.0.0.1:%d", port), UpstreamReverseWSWaitTimeoutMS: 500}) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - conn, _, _, err := ws.Dial(context.Background(), transport.URL) - if err != nil { - t.Fatal(err) - } - hello, err := json.Marshal(map[string]any{"type": "modcdp.reverse.hello", "role": "first-peer", "version": 1}) - if err != nil { - t.Fatal(err) - } - if err := wsutil.WriteClientText(conn, hello); err != nil { - t.Fatal(err) - } - - defer transport.Close() - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer before peer disconnect = %v", err) - } - if err := conn.Close(); err != nil { - t.Fatal(err) - } - waitForReversePeerDisconnect(t, transport) - - replacementConn, _, _, err := ws.Dial(context.Background(), transport.URL) - if err != nil { - t.Fatal(err) - } - defer replacementConn.Close() - replacementHello, err := json.Marshal(map[string]any{"type": "modcdp.reverse.hello", "role": "second-peer", "version": 1}) - if err != nil { - t.Fatal(err) - } - if err := wsutil.WriteClientText(replacementConn, replacementHello); err != nil { - t.Fatal(err) - } - if err := transport.WaitForPeer(); err != nil { - t.Fatalf("WaitForPeer after replacement peer = %v", err) - } - if transport.PeerInfo["role"] != "second-peer" { - t.Fatalf("PeerInfo after replacement peer = %#v", transport.PeerInfo) - } -} - -func TestReverseWebSocketUpstreamTransportCloseRejectsPendingPeerWaits(t *testing.T) { - port, err := freePort() - if err != nil { - t.Fatal(err) - } - transport := NewReverseWebSocketUpstreamTransport(ReverseWebSocketUpstreamTransportOptions{UpstreamReverseWSBind: fmt.Sprintf("127.0.0.1:%d", port), UpstreamReverseWSWaitTimeoutMS: 5_000}) - done := make(chan error, 1) - go func() { - done <- transport.WaitForPeer() - }() - time.Sleep(50 * time.Millisecond) - if err := transport.Close(); err != nil { - t.Fatalf("Close = %v", err) - } - select { - case err := <-done: - expected := fmt.Sprintf("reverse websocket transport at ws://127.0.0.1:%d closed before a peer connected", port) - if err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("WaitForPeer close error = %v", err) - } - case <-time.After(time.Second): - t.Fatal("WaitForPeer did not return after Close") - } -} - -func waitForReversePeerDisconnect(t *testing.T, transport *ReverseWebSocketUpstreamTransport) { - t.Helper() - deadline := time.Now().Add(2 * time.Second) - for time.Now().Before(deadline) { - err := transport.Send(map[string]any{"id": 99, "method": "Browser.getVersion"}) - if err != nil && strings.Contains(err.Error(), "no reverse ModCDP extension peer is connected") { - return - } - time.Sleep(20 * time.Millisecond) - } - t.Fatal("timed out waiting for reverse peer disconnect") -} - -func TestReverseWebSocketUpstreamTransportAcceptsRealExtensionReverseConnectionAndRoutesCDPThroughChromeDebugger(t *testing.T) { - extensionPath, err := filepath.Abs(filepath.Join("..", "..", "..", "dist", "extension")) - if err != nil { - t.Fatal(err) - } - headless := runtime.GOOS == "linux" && os.Getenv("DISPLAY") == "" - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", - LauncherOptions: modcdp.LaunchOptions{ - Headless: boolPtr(headless), - // Reversews is browser -> client only. After explicit CHROME_PATH and - // CI /usr/bin/chromium, these tests use Chrome for Testing because - // Canary rejects --load-extension in this local test path. - ExecutablePath: reverseWSTestBrowserPath(t), - }, - }, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "reversews"}, - Injector: modcdp.InjectorConfig{ - InjectorExtensionPath: extensionPath, - InjectorMode: "auto", - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - InjectorServiceWorkerProbeTimeoutMS: 1000, - }, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["upstream_endpoint_kind"] != UpstreamEndpointKindModCDPServer { - t.Fatalf("upstream_endpoint_kind = %v", cdp.ConnectTiming["upstream_endpoint_kind"]) - } - if cdp.Transport() == nil { - t.Fatal("expected reverse transport to be connected") - } - transport, ok := cdp.Transport().(*ReverseWebSocketUpstreamTransport) - if !ok { - t.Fatalf("transport = %T", cdp.Transport()) - } - if transport.URL != "ws://127.0.0.1:29292" { - t.Fatalf("transport URL = %q", transport.URL) - } - if transport.PeerInfo["extension_id"] != DefaultModCDPExtensionID { - t.Fatalf("extension_id = %#v", transport.PeerInfo["extension_id"]) - } - result, err := cdp.Send("Runtime.evaluate", map[string]any{"expression": "location.href", "returnByValue": true}) - if err != nil { - t.Fatal(err) - } - evaluated, ok := result.(map[string]any) - if !ok { - t.Fatalf("Runtime.evaluate result = %#v", result) - } - evaluatedResult, _ := evaluated["result"].(map[string]any) - if evaluatedResult["value"] != "about:blank" { - t.Fatalf("Runtime.evaluate value = %#v", evaluatedResult["value"]) - } - time.Sleep(1500 * time.Millisecond) - secondResult, err := cdp.Send("Runtime.evaluate", map[string]any{"expression": "document.readyState", "returnByValue": true}) - if err != nil { - t.Fatal(err) - } - secondEvaluated, ok := secondResult.(map[string]any) - if !ok { - t.Fatalf("second Runtime.evaluate result = %#v", secondResult) - } - secondEvaluatedResult, _ := secondEvaluated["result"].(map[string]any) - if secondEvaluatedResult["value"] != "complete" { - t.Fatalf("second Runtime.evaluate value = %#v", secondEvaluatedResult["value"]) - } -} - -func reverseWSTestBrowserPath(t *testing.T) string { - t.Helper() - explicitCandidates := []string{os.Getenv("CHROME_PATH")} - if runtime.GOOS == "linux" { - explicitCandidates = append(explicitCandidates, "/usr/bin/chromium") - } - for _, candidate := range explicitCandidates { - if candidate == "" { - continue - } - if _, err := os.Stat(candidate); err == nil { - return candidate - } - } - home, err := os.UserHomeDir() - if err != nil || home == "" { - home = "." - } - localAppData := os.Getenv("LOCALAPPDATA") - if localAppData == "" { - localAppData = filepath.Join(home, "AppData", "Local") - } - var patterns []string - switch runtime.GOOS { - case "darwin": - patterns = []string{ - filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), - filepath.Join(home, "Library", "Caches", "ms-playwright", "chromium-*", "chrome-mac*", "Chromium.app", "Contents", "MacOS", "Chromium"), - filepath.Join(home, "Library", "Caches", "puppeteer", "chrome", "mac*-*", "chrome-mac*", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"), - } - case "windows": - patterns = []string{ - filepath.Join(localAppData, "ms-playwright", "chromium-*", "chrome-win*", "chrome.exe"), - filepath.Join(home, ".cache", "puppeteer", "chrome", "win*-*", "chrome.exe"), - } - default: - patterns = []string{ - filepath.Join(home, ".cache", "ms-playwright", "chromium-*", "chrome-linux*", "chrome"), - filepath.Join("/opt", "pw-browsers", "chromium-*", "chrome-linux*", "chrome"), - filepath.Join(home, ".cache", "puppeteer", "chrome", "linux-*", "chrome-linux*", "chrome"), - } - } - var candidates []string - for _, pattern := range patterns { - matches, err := filepath.Glob(pattern) - if err != nil { - continue - } - candidates = append(candidates, matches...) - } - candidates = newestChromeForTestingFirst(candidates) - if len(candidates) > 0 { - return candidates[0] - } - t.Fatal("Reversews tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing.") - return "" -} - -func newestChromeForTestingFirst(candidates []string) []string { - seen := map[string]bool{} - deduped := make([]string, 0, len(candidates)) - for _, candidate := range candidates { - if candidate == "" || seen[candidate] { - continue - } - seen[candidate] = true - deduped = append(deduped, candidate) - } - sort.SliceStable(deduped, func(i, j int) bool { - leftVersion := maxPathNumber(deduped[i]) - rightVersion := maxPathNumber(deduped[j]) - if leftVersion != rightVersion { - return leftVersion > rightVersion - } - leftStat, leftErr := os.Stat(deduped[i]) - rightStat, rightErr := os.Stat(deduped[j]) - var leftMtime, rightMtime time.Time - if leftErr == nil { - leftMtime = leftStat.ModTime() - } - if rightErr == nil { - rightMtime = rightStat.ModTime() - } - if !leftMtime.Equal(rightMtime) { - return leftMtime.After(rightMtime) - } - return deduped[i] < deduped[j] - }) - return deduped -} - -func maxPathNumber(value string) int { - maxValue := 0 - for _, raw := range regexp.MustCompile(`\d+`).FindAllString(value, -1) { - number, err := strconv.Atoi(raw) - if err == nil && number > maxValue { - maxValue = number - } - } - return maxValue -} diff --git a/go/modcdp/transport/UpstreamTransport.go b/go/modcdp/transport/UpstreamTransport.go index 6247ab16..f68ec332 100644 --- a/go/modcdp/transport/UpstreamTransport.go +++ b/go/modcdp/transport/UpstreamTransport.go @@ -1,17 +1,26 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/transport/UpstreamTransport.ts +// - ./python/modcdp/transport/UpstreamTransport.py package transport import ( + "encoding/json" "fmt" "net" + "net/url" + "strconv" + "strings" "sync" + "time" "github.com/browserbase/modcdp/go/modcdp/injector" - "github.com/browserbase/modcdp/go/modcdp/launcher" "github.com/browserbase/modcdp/go/modcdp/types" ) -type ExtensionInjectorConfig = types.ExtensionInjectorConfig -type LaunchOptions = types.LaunchOptions +type InjectorConfig = types.InjectorConfig +type LauncherConfig = types.LauncherConfig +type UpstreamTransportConfig = types.UpstreamTransportConfig const DefaultModCDPExtensionID = injector.DefaultModCDPExtensionID @@ -24,10 +33,6 @@ func firstNonEmptyString(values ...string) string { return "" } -func boolPtr(value bool) *bool { - return &value -} - func freePort() (int, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { @@ -37,29 +42,33 @@ func freePort() (int, error) { return listener.Addr().(*net.TCPAddr).Port, nil } -func websocketURLFor(endpoint string) (string, error) { - return launcher.WebsocketURLFor(endpoint) -} - type UpstreamMode string -type UpstreamEndpointKind string +type UpstreamPeerKind string const ( - UpstreamModeWS UpstreamMode = "ws" - UpstreamModePipe UpstreamMode = "pipe" - UpstreamModeNativeMessaging UpstreamMode = "nativemessaging" - UpstreamModeReverseWS UpstreamMode = "reversews" - UpstreamModeNATS UpstreamMode = "nats" - - UpstreamEndpointKindRawCDP UpstreamEndpointKind = "raw_cdp" - UpstreamEndpointKindModCDPServer UpstreamEndpointKind = "modcdp_server" + UpstreamModeWS UpstreamMode = "ws" + + UpstreamPeerKindBrowserCDP UpstreamPeerKind = "browser_cdp" + UpstreamPeerKindModCDPServer UpstreamPeerKind = "modcdp_server" ) +type HostPort struct { + Host string `json:"host"` + Port int `json:"port"` +} + type UpstreamTransport struct { + Config UpstreamTransportConfig + PeerKind UpstreamPeerKind recvListeners []recvListener closeListeners []closeListener + eventListeners map[string][]upstreamEventListener listenerMu sync.Mutex nextListenerID int64 + nextID int64 + pending map[int64]chan map[string]any + pendingMu sync.Mutex + writeCommand func(map[string]any) error } type recvListener struct { @@ -72,7 +81,48 @@ type closeListener struct { fn func(error) } +type upstreamEventListener struct { + id int64 + fn func(map[string]any, string, string) +} + +func NewUpstreamTransport(config UpstreamTransportConfig) UpstreamTransport { + if config.UpstreamMode == "" { + config.UpstreamMode = string(UpstreamModeWS) + } + if config.UpstreamWSConnectErrorSettleTimeoutMS == 0 { + config.UpstreamWSConnectErrorSettleTimeoutMS = 250 + } + if config.UpstreamCDPSendTimeoutMS == 0 { + config.UpstreamCDPSendTimeoutMS = 10_000 + } + return UpstreamTransport{ + Config: config, + PeerKind: UpstreamPeerKindBrowserCDP, + eventListeners: map[string][]upstreamEventListener{}, + pending: map[int64]chan map[string]any{}, + writeCommand: func(map[string]any) error { + return fmt.Errorf("UpstreamTransport.send is not implemented") + }, + } +} + func (e *UpstreamTransport) Update(config map[string]any) { + if config == nil { + return + } + if value, ok := config["upstream_ws_cdp_url"].(string); ok { + e.Config.UpstreamWSCDPURL = value + } + if value, ok := config["upstream_mode"].(string); ok && value != "" { + e.Config.UpstreamMode = value + } + if value, ok := intFromConfig(config["upstream_ws_connect_error_settle_timeout_ms"]); ok { + e.Config.UpstreamWSConnectErrorSettleTimeoutMS = value + } + if value, ok := intFromConfig(config["upstream_cdp_send_timeout_ms"]); ok { + e.Config.UpstreamCDPSendTimeoutMS = value + } } func (e *UpstreamTransport) Connect() error { @@ -83,20 +133,120 @@ func (e *UpstreamTransport) Close() error { return nil } -func (e *UpstreamTransport) Send(message map[string]any) error { - return fmt.Errorf("%T.Send is not implemented", e) +func (e *UpstreamTransport) Send(command any, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error) { + method, ok := command.(string) + if !ok { + message, ok := command.(types.CdpCommandMessage) + if !ok { + return nil, fmt.Errorf("command must be a CDP method name or CdpCommandMessage") + } + payload := map[string]any{ + "id": message.ID, + "method": message.Method, + } + if message.Params != nil { + payload["params"] = message.Params + } + if message.SessionID != "" { + payload["sessionId"] = message.SessionID + } + return map[string]any{}, e.writeCommand(payload) + } + e.pendingMu.Lock() + e.nextID++ + id := e.nextID + done := make(chan map[string]any, 1) + e.pending[id] = done + e.pendingMu.Unlock() + + message := map[string]any{"id": id, "method": method, "params": params} + if sessionID != "" { + message["sessionId"] = sessionID + } + if err := e.writeCommand(message); err != nil { + e.pendingMu.Lock() + delete(e.pending, id) + e.pendingMu.Unlock() + return nil, err + } + effectiveTimeout := time.Duration(e.Config.UpstreamCDPSendTimeoutMS) * time.Millisecond + if len(timeout) > 0 { + effectiveTimeout = timeout[0] + } + if effectiveTimeout <= 0 { + response := <-done + if errObj, ok := response["error"].(map[string]any); ok { + return nil, fmt.Errorf("%s failed: %v", method, errObj["message"]) + } + if result, ok := response["result"].(map[string]any); ok { + return result, nil + } + return map[string]any{}, nil + } + select { + case <-time.After(effectiveTimeout): + e.pendingMu.Lock() + delete(e.pending, id) + e.pendingMu.Unlock() + return nil, fmt.Errorf("%s timed out after %s", method, effectiveTimeout) + case response := <-done: + if errObj, ok := response["error"].(map[string]any); ok { + return nil, fmt.Errorf("%s failed: %v", method, errObj["message"]) + } + if result, ok := response["result"].(map[string]any); ok { + return result, nil + } + return map[string]any{}, nil + } +} + +func (e *UpstreamTransport) ConfigForLauncher() LauncherConfig { + return LauncherConfig{} +} + +func (e *UpstreamTransport) GetTargets() ([]map[string]any, error) { + result, err := e.Send("Target.getTargets", map[string]any{}, "") + if err != nil { + return nil, err + } + targetInfos, _ := result["targetInfos"].([]any) + targets := []map[string]any{} + for _, targetInfo := range targetInfos { + target, _ := targetInfo.(map[string]any) + targets = append(targets, target) + } + return targets, nil +} + +func (e *UpstreamTransport) ResolveTargetID(params map[string]any) string { + targetID, _ := params["targetId"].(string) + return targetID } -func (e *UpstreamTransport) GetInjectorConfig() ExtensionInjectorConfig { - return ExtensionInjectorConfig{} +func (e *UpstreamTransport) CreateTarget(url string) (string, error) { + result, err := e.Send("Target.createTarget", map[string]any{"url": url}, "") + if err != nil { + return "", err + } + targetID, _ := result["targetId"].(string) + if targetID == "" { + return "", fmt.Errorf("Target.createTarget returned no targetId") + } + return targetID, nil } -func (e *UpstreamTransport) GetLauncherConfig() LaunchOptions { - return LaunchOptions{} +func (e *UpstreamTransport) AttachToTarget(targetID string) (string, error) { + result, err := e.Send("Target.attachToTarget", map[string]any{"targetId": targetID, "flatten": true}, "") + if err != nil { + return "", err + } + sessionID, _ := result["sessionId"].(string) + return sessionID, nil } -func (e *UpstreamTransport) GetServerConfig() map[string]any { - return map[string]any{} +func (e *UpstreamTransport) DetachFromTarget(sessionID string) error { + _, err := e.Send("Target.detachFromTarget", map[string]any{"sessionId": sessionID}, "") + return err } func (e *UpstreamTransport) OnRecv(listener func(map[string]any)) func() { @@ -143,7 +293,58 @@ func (e *UpstreamTransport) OnClose(listener func(error)) func() { } } +func (e *UpstreamTransport) On(event string, listener func(map[string]any, string, string)) func() { + e.listenerMu.Lock() + if e.eventListeners == nil { + e.eventListeners = map[string][]upstreamEventListener{} + } + e.nextListenerID++ + id := e.nextListenerID + e.eventListeners[event] = append(e.eventListeners[event], upstreamEventListener{id: id, fn: listener}) + e.listenerMu.Unlock() + var once sync.Once + return func() { + once.Do(func() { + e.listenerMu.Lock() + defer e.listenerMu.Unlock() + listeners := e.eventListeners[event] + for index, candidate := range listeners { + if candidate.id != id { + continue + } + listeners = append(listeners[:index], listeners[index+1:]...) + if len(listeners) == 0 { + delete(e.eventListeners, event) + } else { + e.eventListeners[event] = listeners + } + return + } + }) + } +} + func (e *UpstreamTransport) emitRecv(message map[string]any) { + if id, ok := commandID(message["id"]); ok { + e.pendingMu.Lock() + done := e.pending[id] + delete(e.pending, id) + e.pendingMu.Unlock() + if done != nil { + done <- message + } + } + if _, ok := commandID(message["id"]); !ok { + method, _ := message["method"].(string) + params, _ := message["params"].(map[string]any) + sessionID, _ := message["sessionId"].(string) + if method != "" { + if params == nil { + params = map[string]any{} + } + e.emitUpstreamEvent(method, params, "", sessionID) + } + } e.listenerMu.Lock() listeners := append([]recvListener(nil), e.recvListeners...) e.listenerMu.Unlock() @@ -156,7 +357,67 @@ func (e *UpstreamTransport) EmitRecv(message map[string]any) { e.emitRecv(message) } +func (e *UpstreamTransport) parseAndEmitRecv(data []byte) error { + var message map[string]any + if err := json.Unmarshal(data, &message); err != nil { + return fmt.Errorf("invalid CDP message: %w", err) + } + if _, ok := commandID(message["id"]); ok { + if errObj, hasError := message["error"]; hasError && errObj != nil { + errorMap, ok := errObj.(map[string]any) + if !ok { + return fmt.Errorf("invalid CDP response error") + } + if message, ok := errorMap["message"].(string); !ok || message == "" { + return fmt.Errorf("invalid CDP response error message") + } + } + if sessionID, ok := message["sessionId"]; ok && sessionID != nil { + if _, ok := sessionID.(string); !ok { + return fmt.Errorf("invalid CDP response sessionId") + } + } + e.emitRecv(message) + return nil + } + if _, hasID := message["id"]; hasID { + return fmt.Errorf("invalid CDP response id") + } + method, _ := message["method"].(string) + if method == "" { + return fmt.Errorf("invalid CDP event method") + } + if params, ok := message["params"]; ok && params != nil { + if _, ok := params.(map[string]any); !ok { + return fmt.Errorf("invalid CDP event params") + } + } + if sessionID, ok := message["sessionId"]; ok && sessionID != nil { + if _, ok := sessionID.(string); !ok { + return fmt.Errorf("invalid CDP event sessionId") + } + } + e.emitRecv(message) + return nil +} + +func (e *UpstreamTransport) emitUpstreamEvent(method string, payload map[string]any, targetID string, sessionID string) { + e.listenerMu.Lock() + listeners := append([]upstreamEventListener(nil), e.eventListeners[method]...) + e.listenerMu.Unlock() + for _, listener := range listeners { + listener.fn(payload, targetID, sessionID) + } +} + func (e *UpstreamTransport) emitClose(err error) { + e.pendingMu.Lock() + pending := e.pending + e.pending = map[int64]chan map[string]any{} + e.pendingMu.Unlock() + for _, done := range pending { + done <- map[string]any{"error": map[string]any{"message": fmt.Sprintf("connection closed: %v", err)}} + } e.listenerMu.Lock() listeners := append([]closeListener(nil), e.closeListeners...) e.listenerMu.Unlock() @@ -177,11 +438,52 @@ func (e *UpstreamTransport) PeerGeneration() int64 { return 0 } -func EndpointKindForUpstream(mode string) UpstreamEndpointKind { - if mode == "ws" || mode == "pipe" { - return UpstreamEndpointKindRawCDP +func (e *UpstreamTransport) ToJSON() map[string]any { + e.pendingMu.Lock() + pending := len(e.pending) + e.pendingMu.Unlock() + e.listenerMu.Lock() + recvListeners := len(e.recvListeners) + closeListeners := len(e.closeListeners) + eventListeners := len(e.eventListeners) + e.listenerMu.Unlock() + config := e.Config + return types.ModCDPToJSON(e, types.ModCDPJSONConfig{ + Config: config, + State: map[string]any{ + "pending": pending, + "recv_listeners": recvListeners, + "close_listeners": closeListeners, + "event_listeners": eventListeners, + }, + }) +} + +func ParseHostPort(value string, defaultHost string, defaultPort int) (HostPort, error) { + parseValue := value + if !strings.Contains(value, "://") { + parseValue = "ws://" + value + } + parsed, err := url.Parse(parseValue) + if err != nil { + return HostPort{}, err } - return UpstreamEndpointKindModCDPServer + host := parsed.Hostname() + if host == "" { + host = defaultHost + } + port := defaultPort + if parsed.Port() != "" { + parsedPort, ok := intFromConfig(parsed.Port()) + if !ok { + return HostPort{}, fmt.Errorf("Invalid host:port %s", value) + } + port = parsedPort + } + if port <= 0 || port > 65_535 { + return HostPort{}, fmt.Errorf("Invalid host:port %s", value) + } + return HostPort{Host: host, Port: port}, nil } func intFromConfig(value any) (int, bool) { @@ -194,6 +496,25 @@ func intFromConfig(value any) (int, bool) { return int(typed), true case float32: return int(typed), true + case string: + parsed, err := strconv.Atoi(typed) + if err != nil { + return 0, false + } + return parsed, true + default: + return 0, false + } +} + +func commandID(value any) (int64, bool) { + switch typed := value.(type) { + case int: + return int64(typed), true + case int64: + return typed, true + case float64: + return int64(typed), true default: return 0, false } diff --git a/go/modcdp/transport/UpstreamTransport_internal_test.go b/go/modcdp/transport/UpstreamTransport_internal_test.go new file mode 100644 index 00000000..ed1bc881 --- /dev/null +++ b/go/modcdp/transport/UpstreamTransport_internal_test.go @@ -0,0 +1,30 @@ +// MODCDP_TRANSLATE_TEST: GO-SPECIFIC INTERNAL COVERAGE. +// The public translated tests cover behavior through user-facing transport APIs. +package transport + +import ( + "strings" + "testing" +) + +func TestUpstreamTransportRejectsInvalidIncomingMessages(t *testing.T) { + transport := NewUpstreamTransport(UpstreamTransportConfig{}) + + for _, testCase := range []struct { + name string + message string + want string + }{ + {name: "json", message: "{", want: "invalid CDP message"}, + {name: "response id", message: `{"id":"one","result":{}}`, want: "invalid CDP response id"}, + {name: "event method", message: `{"params":{}}`, want: "invalid CDP event method"}, + {name: "event params", message: `{"method":"Runtime.executionContextCreated","params":[]}`, want: "invalid CDP event params"}, + } { + t.Run(testCase.name, func(t *testing.T) { + err := transport.parseAndEmitRecv([]byte(testCase.message)) + if err == nil || !strings.Contains(err.Error(), testCase.want) { + t.Fatalf("parse error = %v", err) + } + }) + } +} diff --git a/go/modcdp/transport/UpstreamTransport_test.go b/go/modcdp/transport/UpstreamTransport_test.go index c6d59159..4782df4b 100644 --- a/go/modcdp/transport/UpstreamTransport_test.go +++ b/go/modcdp/transport/UpstreamTransport_test.go @@ -1,81 +1,75 @@ -package transport_test +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.UpstreamTransport.ts +// - ./python/tests/test_UpstreamTransport.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package transport import ( - . "github.com/browserbase/modcdp/go/modcdp/transport" + "reflect" "strings" "testing" + + "github.com/browserbase/modcdp/go/modcdp/types" ) type testUpstreamTransport struct { UpstreamTransport } -func (t *testUpstreamTransport) emit(message map[string]any) { - t.EmitRecv(message) +func (t *testUpstreamTransport) emit(message string) error { + return t.parseAndEmitRecv([]byte(message)) } -func TestUpstreamTransportSharedConfigEndpointClassificationAndRecvCallbacks(t *testing.T) { - transport := &UpstreamTransport{} +func TestOwnsSharedTransportConfigAndRecvCallbacks(t *testing.T) { + transport := NewUpstreamTransport(UpstreamTransportConfig{}) received := []map[string]any{} stop := transport.OnRecv(func(message map[string]any) { received = append(received, message) }) - if EndpointKindForUpstream("ws") != UpstreamEndpointKindRawCDP { - t.Fatal("ws endpoint kind mismatch") - } - if EndpointKindForUpstream("pipe") != UpstreamEndpointKindRawCDP { - t.Fatal("pipe endpoint kind mismatch") - } - if EndpointKindForUpstream("nativemessaging") != UpstreamEndpointKindModCDPServer { - t.Fatal("native endpoint kind mismatch") - } - if EndpointKindForUpstream("reversews") != UpstreamEndpointKindModCDPServer { - t.Fatal("reverse endpoint kind mismatch") + parsedHostPort, err := ParseHostPort("127.0.0.1:29292", "0.0.0.0", 80) + if err != nil { + t.Fatal(err) } - if EndpointKindForUpstream("nats") != UpstreamEndpointKindModCDPServer { - t.Fatal("nats endpoint kind mismatch") + if parsedHostPort.Host != "127.0.0.1" || parsedHostPort.Port != 29292 { + t.Fatalf("ParseHostPort = %#v", parsedHostPort) } transport.Update(nil) - if len(transport.GetLauncherConfig().ExtraArgs) != 0 { + if len(transport.ConfigForLauncher().LauncherLocalExtraArgs) != 0 { t.Fatal("expected empty launcher config") } - if transport.GetInjectorConfig().InjectorExtensionID != "" { - t.Fatal("expected empty injector config") - } - if len(transport.GetServerConfig()) != 0 { - t.Fatal("expected empty server config") - } - testTransport := &testUpstreamTransport{} + testTransport := &testUpstreamTransport{UpstreamTransport: NewUpstreamTransport(UpstreamTransportConfig{})} parsed := []map[string]any{} testTransport.OnRecv(func(message map[string]any) { parsed = append(parsed, message) }) - testTransport.emit(map[string]any{"id": 1, "result": map[string]any{"ok": true}}) - testTransport.emit(map[string]any{"method": "Runtime.executionContextCreated", "params": map[string]any{}}) - if len(parsed) != 2 { + for _, message := range []string{ + `{"id":1,"result":{"ok":true}}`, + `{"id":2,"result":true}`, + `{"id":3,"result":0}`, + `{"method":"Runtime.executionContextCreated","params":{}}`, + } { + if err := testTransport.emit(message); err != nil { + t.Fatal(err) + } + } + expected := []map[string]any{ + {"id": float64(1), "result": map[string]any{"ok": true}}, + {"id": float64(2), "result": true}, + {"id": float64(3), "result": float64(0)}, + {"method": "Runtime.executionContextCreated", "params": map[string]any{}}, + } + if !reflect.DeepEqual(parsed, expected) { t.Fatalf("parsed = %#v", parsed) } - transport.EmitRecv(map[string]any{"method": "before.stop"}) - if len(received) != 1 { - t.Fatalf("received = %#v", received) - } stop() - stop() - transport.EmitRecv(map[string]any{"method": "after.stop"}) - if len(received) != 1 { - t.Fatalf("received after stop = %#v", received) - } - closed := 0 - stopClose := transport.OnClose(func(error) { closed++ }) - stopClose() - stopClose() - transport.EmitClose(nil) - if closed != 0 { - t.Fatalf("closed after stop = %d", closed) + if len(received) != 0 { + t.Fatalf("received = %#v", received) } if err := transport.Connect(); err == nil || !strings.Contains(err.Error(), "Connect is not implemented") { t.Fatalf("connect error = %v", err) } - if err := transport.Send(map[string]any{"id": 1, "method": "Browser.getVersion", "params": map[string]any{}}); err == nil || !strings.Contains(err.Error(), "Send is not implemented") { + if _, err := transport.Send(types.CdpCommandMessage{ID: 1, Method: "Browser.getVersion", Params: map[string]any{}}, nil, ""); err == nil || !strings.Contains(err.Error(), "send is not implemented") { t.Fatalf("send error = %v", err) } } diff --git a/go/modcdp/transport/WSUpstreamTransport.go b/go/modcdp/transport/WSUpstreamTransport.go new file mode 100644 index 00000000..84e2033a --- /dev/null +++ b/go/modcdp/transport/WSUpstreamTransport.go @@ -0,0 +1,151 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/transport/WSUpstreamTransport.ts +// - ./python/modcdp/transport/WSUpstreamTransport.py +package transport + +import ( + "context" + "encoding/json" + "fmt" + "net" + "sync" + "time" + + "github.com/browserbase/modcdp/go/modcdp/launcher" + "github.com/browserbase/modcdp/go/modcdp/types" + "github.com/gobwas/ws" + "github.com/gobwas/ws/wsutil" +) + +type WSUpstreamTransport struct { + UpstreamTransport + URL string + Conn net.Conn + writeMu sync.Mutex +} + +func NewWSUpstreamTransport(config UpstreamTransportConfig) *WSUpstreamTransport { + transport := &WSUpstreamTransport{UpstreamTransport: NewUpstreamTransport(config), URL: config.UpstreamWSCDPURL} + transport.writeCommand = func(command map[string]any) error { + body, err := json.Marshal(command) + if err != nil { + return err + } + transport.writeMu.Lock() + defer transport.writeMu.Unlock() + conn := transport.Conn + if conn == nil { + return fmt.Errorf("CDP websocket is not connected") + } + return wsutil.WriteClientText(conn, body) + } + return transport +} + +func (t *WSUpstreamTransport) Update(config map[string]any) { + t.UpstreamTransport.Update(config) + if config == nil { + return + } + if value, ok := config["upstream_ws_cdp_url"].(string); ok && value != "" { + t.URL = value + } +} + +func (t *WSUpstreamTransport) Connect() error { + t.writeMu.Lock() + if t.Conn != nil { + t.writeMu.Unlock() + return nil + } + t.writeMu.Unlock() + if t.URL == "" { + return fmt.Errorf("WSUpstreamTransport requires upstream_ws_cdp_url or launcher-provided cdp_url") + } + // URL may start as an HTTP upstream_ws_cdp_url; from here on it is the resolved WebSocket CDP endpoint. + resolvedURL, err := launcher.WebsocketURLFor(t.URL) + if err != nil { + return err + } + t.URL = resolvedURL + t.Config.UpstreamWSCDPURL = resolvedURL + conn, _, _, err := ws.Dial(context.Background(), t.URL) + if err != nil { + return err + } + t.writeMu.Lock() + t.Conn = conn + t.writeMu.Unlock() + go t.readLoop(conn) + return nil +} + +func (t *WSUpstreamTransport) Send(command any, params map[string]any, sessionID string, timeout ...time.Duration) (map[string]any, error) { + if _, is_command_message := command.(types.CdpCommandMessage); !is_command_message { + t.writeMu.Lock() + connected := t.Conn != nil + t.writeMu.Unlock() + if !connected { + if err := t.Connect(); err != nil { + return nil, err + } + } + } + return t.UpstreamTransport.Send(command, params, sessionID, timeout...) +} + +func (t *WSUpstreamTransport) Close() error { + t.writeMu.Lock() + defer t.writeMu.Unlock() + if t.Conn != nil { + err := t.Conn.Close() + t.Conn = nil + return err + } + return nil +} + +func (t *WSUpstreamTransport) ToJSON() map[string]any { + jsonValue := t.UpstreamTransport.ToJSON() + state, _ := jsonValue["state"].(map[string]any) + if state == nil { + state = map[string]any{} + } + t.writeMu.Lock() + state["connected"] = t.Conn != nil + t.writeMu.Unlock() + jsonValue["state"] = state + return jsonValue +} + +func (t *WSUpstreamTransport) readLoop(conn net.Conn) { + for { + data, err := wsutil.ReadServerText(conn) + if err != nil { + t.writeMu.Lock() + currentConn := t.Conn + if currentConn == conn { + t.Conn = nil + } + t.writeMu.Unlock() + if currentConn == conn { + t.emitClose(err) + } + return + } + if err := t.parseAndEmitRecv(data); err != nil { + t.writeMu.Lock() + currentConn := t.Conn + if currentConn == conn { + t.Conn = nil + } + t.writeMu.Unlock() + if currentConn == conn { + _ = conn.Close() + t.emitClose(err) + } + return + } + } +} diff --git a/go/modcdp/transport/WSUpstreamTransport_test.go b/go/modcdp/transport/WSUpstreamTransport_test.go new file mode 100644 index 00000000..99db93e8 --- /dev/null +++ b/go/modcdp/transport/WSUpstreamTransport_test.go @@ -0,0 +1,149 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./js/test/test.WSUpstreamTransport.ts +// - ./python/tests/test_WSUpstreamTransport.py +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +package transport_test + +import ( + "fmt" + "strings" + "testing" + "time" + + modcdp "github.com/browserbase/modcdp/go/modcdp/client" + "github.com/browserbase/modcdp/go/modcdp/launcher" + . "github.com/browserbase/modcdp/go/modcdp/transport" + "github.com/browserbase/modcdp/go/modcdp/types" +) + +func TestWSUpstreamConstructorUpdateServerConfigAndUnconnectedErrorsMatchTheTransportSurface(t *testing.T) { + transport := NewWSUpstreamTransport(UpstreamTransportConfig{}) + if transport.URL != "" { + t.Fatalf("URL = %q", transport.URL) + } + transport.Update(map[string]any{"upstream_ws_cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}) + if transport.URL != "ws://127.0.0.1:1/devtools/browser/test" { + t.Fatalf("URL = %q", transport.URL) + } + if err := NewWSUpstreamTransport(UpstreamTransportConfig{}).Connect(); err == nil || !strings.Contains(err.Error(), "WSUpstreamTransport requires") { + t.Fatalf("connect error = %v", err) + } + if _, err := NewWSUpstreamTransport(UpstreamTransportConfig{}).Send(types.CdpCommandMessage{ID: 1, Method: "Browser.getVersion"}, nil, ""); err == nil || !strings.Contains(err.Error(), "CDP websocket is not connected") { + t.Fatalf("send error = %v", err) + } + state := transport.ToJSON()["state"].(map[string]any) + if state["connected"] != false { + t.Fatalf("connected state = %#v", state["connected"]) + } +} + +func TestWSUpstreamLaunchesARealBrowserAndSpeaksRawCDP(t *testing.T) { + headless := true + chrome, err := modcdp.NewLocalBrowserLauncher(modcdp.LauncherConfig{ + LauncherLocalHeadless: &headless, + }).Launch(modcdp.LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + defer chrome.Close() + + transport := NewWSUpstreamTransport(UpstreamTransportConfig{UpstreamWSCDPURL: chrome.CDPURL}) + received := make(chan map[string]any, 1) + transport.OnRecv(func(message map[string]any) { + if message["id"] == float64(1) || message["id"] == int64(1) || message["id"] == 1 { + received <- message + } + }) + if err := transport.Connect(); err != nil { + t.Fatal(err) + } + defer transport.Close() + if !strings.HasPrefix(transport.URL, "ws://") { + t.Fatalf("transport.URL = %q", transport.URL) + } + if _, err := transport.Send(types.CdpCommandMessage{ID: 1, Method: "Browser.getVersion", Params: map[string]any{}}, nil, ""); err != nil { + t.Fatal(err) + } + select { + case message := <-received: + result, _ := message["result"].(map[string]any) + if _, ok := result["product"].(string); !ok { + t.Fatalf("Browser.getVersion result = %#v", result) + } + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for Browser.getVersion response") + } +} + +func TestWSUpstreamResolvesABareHostPortCDPEndpointToTheBrowserWebsocket(t *testing.T) { + headless := true + port, err := launcher.NewLocalBrowserLauncher(launcher.LauncherConfig{}).FreePort() + if err != nil { + t.Fatal(err) + } + chrome, err := modcdp.NewLocalBrowserLauncher(modcdp.LauncherConfig{ + LauncherLocalCDPListenPort: port, + LauncherLocalHeadless: &headless, + }).Launch(modcdp.LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + defer chrome.Close() + + transport := NewWSUpstreamTransport(UpstreamTransportConfig{UpstreamWSCDPURL: fmt.Sprintf("127.0.0.1:%d", port)}) + if err := transport.Connect(); err != nil { + t.Fatal(err) + } + defer transport.Close() + if transport.URL != chrome.CDPURL { + t.Fatalf("transport.URL = %q", transport.URL) + } + if !strings.HasPrefix(transport.URL, "ws://") && !strings.HasPrefix(transport.URL, "wss://") { + t.Fatalf("transport.URL = %q", transport.URL) + } + hostPortResult, err := transport.Send("Browser.getVersion", map[string]any{}, "") + if err != nil { + t.Fatal(err) + } + if _, ok := hostPortResult["product"].(string); !ok { + t.Fatalf("Browser.getVersion host:port result = %#v", hostPortResult) + } +} + +func TestWSUpstreamCloseClearsConnectionState(t *testing.T) { + headless := true + chrome, err := modcdp.NewLocalBrowserLauncher(modcdp.LauncherConfig{ + LauncherLocalHeadless: &headless, + }).Launch(modcdp.LauncherConfig{}) + if err != nil { + t.Fatal(err) + } + defer chrome.Close() + + transport := NewWSUpstreamTransport(UpstreamTransportConfig{UpstreamWSCDPURL: chrome.CDPURL}) + if err := transport.Connect(); err != nil { + t.Fatal(err) + } + if transport.Conn == nil { + t.Fatal("expected connected websocket") + } + state := transport.ToJSON()["state"].(map[string]any) + if state["connected"] != true { + t.Fatalf("connected state = %#v", state["connected"]) + } + if err := transport.Close(); err != nil { + t.Fatal(err) + } + if transport.Conn != nil { + t.Fatal("Close left Conn set") + } + state = transport.ToJSON()["state"].(map[string]any) + if state["connected"] != false { + t.Fatalf("connected state after close = %#v", state["connected"]) + } + if _, err := transport.Send(types.CdpCommandMessage{ID: 1, Method: "Browser.getVersion"}, nil, ""); err == nil || !strings.Contains(err.Error(), "CDP websocket is not connected") { + t.Fatalf("Send after close error = %v", err) + } +} diff --git a/go/modcdp/transport/WebSocketUpstreamTransport.go b/go/modcdp/transport/WebSocketUpstreamTransport.go deleted file mode 100644 index f8b7a337..00000000 --- a/go/modcdp/transport/WebSocketUpstreamTransport.go +++ /dev/null @@ -1,128 +0,0 @@ -package transport - -import ( - "context" - "encoding/json" - "fmt" - "net" - "sync" - - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" -) - -type WebSocketUpstreamTransport struct { - UpstreamTransport - URL string - Conn net.Conn - writeMu sync.Mutex - stateMu sync.Mutex - ctx context.Context - cancel context.CancelFunc - closed bool -} - -type WebSocketUpstreamTransportOptions struct { - CDPURL string `json:"cdp_url,omitempty"` -} - -func NewWebSocketUpstreamTransport(options WebSocketUpstreamTransportOptions) *WebSocketUpstreamTransport { - return &WebSocketUpstreamTransport{URL: options.CDPURL} -} - -func (t *WebSocketUpstreamTransport) Update(config map[string]any) { - if config == nil { - return - } - if value, ok := config["cdp_url"].(string); ok && value != "" { - t.URL = value - } -} - -func (t *WebSocketUpstreamTransport) GetServerConfig() map[string]any { - if t.URL == "" { - return map[string]any{} - } - return map[string]any{"server_loopback_cdp_url": t.URL} -} - -func (t *WebSocketUpstreamTransport) Connect() error { - if t.URL == "" { - return fmt.Errorf("upstream.upstream_mode=ws requires upstream.upstream_cdp_url or launcher-provided cdp_url") - } - // URL may start as an HTTP cdp_url; from here on it is the resolved WebSocket CDP endpoint. - resolvedURL, err := websocketURLFor(t.URL) - if err != nil { - return err - } - t.URL = resolvedURL - t.ctx, t.cancel = context.WithCancel(context.Background()) - conn, _, _, err := ws.Dial(t.ctx, t.URL) - if err != nil { - return err - } - t.writeMu.Lock() - t.Conn = conn - t.writeMu.Unlock() - t.setClosed(false) - go t.readLoop(conn) - return nil -} - -func (t *WebSocketUpstreamTransport) Send(message map[string]any) error { - body, err := json.Marshal(message) - if err != nil { - return err - } - t.writeMu.Lock() - defer t.writeMu.Unlock() - conn := t.Conn - if conn == nil { - return fmt.Errorf("CDP websocket is not connected") - } - return wsutil.WriteClientText(conn, body) -} - -func (t *WebSocketUpstreamTransport) Close() error { - t.setClosed(true) - if t.cancel != nil { - t.cancel() - t.cancel = nil - } - t.writeMu.Lock() - defer t.writeMu.Unlock() - if t.Conn != nil { - err := t.Conn.Close() - t.Conn = nil - return err - } - return nil -} - -func (t *WebSocketUpstreamTransport) readLoop(conn net.Conn) { - for !t.isClosed() { - data, err := wsutil.ReadServerText(conn) - if err != nil { - if !t.isClosed() { - t.emitClose(err) - } - return - } - var message map[string]any - if err := json.Unmarshal(data, &message); err == nil { - t.emitRecv(message) - } - } -} - -func (t *WebSocketUpstreamTransport) setClosed(closed bool) { - t.stateMu.Lock() - t.closed = closed - t.stateMu.Unlock() -} - -func (t *WebSocketUpstreamTransport) isClosed() bool { - t.stateMu.Lock() - defer t.stateMu.Unlock() - return t.closed -} diff --git a/go/modcdp/transport/WebSocketUpstreamTransport_test.go b/go/modcdp/transport/WebSocketUpstreamTransport_test.go deleted file mode 100644 index 5892ddf7..00000000 --- a/go/modcdp/transport/WebSocketUpstreamTransport_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package transport_test - -import ( - modcdp "github.com/browserbase/modcdp/go/modcdp/client" - . "github.com/browserbase/modcdp/go/modcdp/transport" - "net/url" - "strings" - "testing" - "time" -) - -func TestWebSocketUpstreamTransportConstructorUpdateAndServerConfigMatchTSShape(t *testing.T) { - transport := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{}) - if transport.URL != "" { - t.Fatalf("URL = %q", transport.URL) - } - if len(transport.GetServerConfig()) != 0 { - t.Fatalf("server config = %#v", transport.GetServerConfig()) - } - transport.Update(map[string]any{"cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}) - if transport.URL != "ws://127.0.0.1:1/devtools/browser/test" { - t.Fatalf("URL = %q", transport.URL) - } - if transport.GetServerConfig()["server_loopback_cdp_url"] != "ws://127.0.0.1:1/devtools/browser/test" { - t.Fatalf("server config = %#v", transport.GetServerConfig()) - } - if err := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{}).Connect(); err == nil || !strings.Contains(err.Error(), "upstream.upstream_mode=ws requires") { - t.Fatalf("connect error = %v", err) - } - if err := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{}).Send(map[string]any{"id": 1, "method": "Browser.getVersion"}); err == nil || !strings.Contains(err.Error(), "CDP websocket is not connected") { - t.Fatalf("send error = %v", err) - } -} - -func TestWebSocketUpstreamTransportLaunchesRealBrowserAndSpeaksRawCDP(t *testing.T) { - cdp := modcdp.New(modcdp.Options{ - Launcher: modcdp.LauncherConfig{LauncherMode: "local", - LauncherOptions: modcdp.LaunchOptions{ - Headless: boolPtr(true), - }, - }, - Upstream: modcdp.UpstreamConfig{UpstreamMode: "ws"}, - Injector: modcdp.InjectorConfig{ - InjectorMode: "auto", - InjectorServiceWorkerURLSuffixes: []string{"/modcdp/service_worker.js"}, - InjectorTrustServiceWorkerTarget: true, - }, - }) - defer cdp.Close() - - if err := cdp.Connect(); err != nil { - t.Fatal(err) - } - if cdp.ConnectTiming["upstream_mode"] != "ws" { - t.Fatalf("upstream_mode = %v", cdp.ConnectTiming["upstream_mode"]) - } - if cdp.ConnectTiming["upstream_endpoint_kind"] != UpstreamEndpointKindRawCDP { - t.Fatalf("upstream_endpoint_kind = %v", cdp.ConnectTiming["upstream_endpoint_kind"]) - } - transportStartedAt, ok := cdp.ConnectTiming["transport_started_at"].(int64) - if !ok { - t.Fatalf("transport_started_at = %#v", cdp.ConnectTiming["transport_started_at"]) - } - transportConnectedAt, ok := cdp.ConnectTiming["transport_connected_at"].(int64) - if !ok { - t.Fatalf("transport_connected_at = %#v", cdp.ConnectTiming["transport_connected_at"]) - } - if transportConnectedAt < transportStartedAt { - t.Fatalf("transport timing went backwards: %d < %d", transportConnectedAt, transportStartedAt) - } - if cdp.ConnectTiming["transport_duration_ms"] != transportConnectedAt-transportStartedAt { - t.Fatalf("transport_duration_ms = %v", cdp.ConnectTiming["transport_duration_ms"]) - } - if _, ok := cdp.Transport().(*WebSocketUpstreamTransport); !ok { - t.Fatalf("transport = %T", cdp.Transport()) - } - if !strings.HasPrefix(cdp.CDPURL, "ws://") { - t.Fatalf("CDPURL = %q", cdp.CDPURL) - } - version, err := cdp.SendRaw("Browser.getVersion", map[string]any{}) - if err != nil { - t.Fatal(err) - } - if _, ok := version["product"].(string); !ok { - t.Fatalf("Browser.getVersion product = %#v", version["product"]) - } - time.Sleep(1500 * time.Millisecond) - targets, err := cdp.SendRaw("Target.getTargets", map[string]any{}) - if err != nil { - t.Fatal(err) - } - foundServiceWorker := false - for _, target := range targets["targetInfos"].([]any) { - targetMap := target.(map[string]any) - if targetMap["type"] == "service_worker" && strings.HasSuffix(targetMap["url"].(string), "/modcdp/service_worker.js") { - foundServiceWorker = true - break - } - } - if !foundServiceWorker { - t.Fatalf("ModCDP service worker target not found after connect: %#v", targets["targetInfos"]) - } - evaluated, err := cdp.Mod.Evaluate(map[string]any{ - "expression": "Boolean(globalThis.ModCDP?.handleCommand && chrome.runtime.getURL('modcdp/service_worker.js'))", - }) - if err != nil { - t.Fatal(err) - } - if evaluated != true { - t.Fatalf("Mod.evaluate liveness = %#v", evaluated) - } -} - -func TestWebSocketUpstreamTransportResolvesRealHTTPCDPEndpointToBrowserWebSocket(t *testing.T) { - chrome, err := modcdp.NewLocalBrowserLauncher(modcdp.LaunchOptions{ - Headless: boolPtr(true), - }).Launch(modcdp.LaunchOptions{}) - if err != nil { - t.Fatal(err) - } - defer chrome.Close() - - transport := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{CDPURL: chrome.CDPURL}) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - defer transport.Close() - if !strings.HasPrefix(transport.URL, "ws://") { - t.Fatalf("transport.URL = %q", transport.URL) - } - received := make(chan map[string]any, 1) - transport.OnRecv(func(message map[string]any) { received <- message }) - if err := transport.Send(map[string]any{"id": 1, "method": "Browser.getVersion", "params": map[string]any{}}); err != nil { - t.Fatal(err) - } - message := <-received - if message["id"] != float64(1) && message["id"] != 1 { - t.Fatalf("Browser.getVersion id = %#v", message["id"]) - } - result, _ := message["result"].(map[string]any) - if _, ok := result["product"].(string); !ok { - t.Fatalf("Browser.getVersion response = %#v", message) - } - - parsedCDPURL, err := url.Parse(chrome.CDPURL) - if err != nil { - t.Fatal(err) - } - hostPortTransport := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{CDPURL: parsedCDPURL.Host}) - if err := hostPortTransport.Connect(); err != nil { - t.Fatal(err) - } - defer hostPortTransport.Close() - if !strings.HasPrefix(hostPortTransport.URL, "ws://") && !strings.HasPrefix(hostPortTransport.URL, "wss://") { - t.Fatalf("hostPortTransport.URL = %q", hostPortTransport.URL) - } -} - -func TestWebSocketUpstreamTransportCloseClearsConnectionState(t *testing.T) { - chrome, err := modcdp.NewLocalBrowserLauncher(modcdp.LaunchOptions{ - Headless: boolPtr(true), - }).Launch(modcdp.LaunchOptions{}) - if err != nil { - t.Fatal(err) - } - defer chrome.Close() - - transport := NewWebSocketUpstreamTransport(WebSocketUpstreamTransportOptions{CDPURL: chrome.CDPURL}) - if err := transport.Connect(); err != nil { - t.Fatal(err) - } - if transport.Conn == nil { - t.Fatal("expected connected websocket") - } - if err := transport.Close(); err != nil { - t.Fatal(err) - } - if transport.Conn != nil { - t.Fatal("Close left Conn set") - } - if err := transport.Send(map[string]any{"id": 1, "method": "Browser.getVersion"}); err == nil || !strings.Contains(err.Error(), "CDP websocket is not connected") { - t.Fatalf("Send after close error = %v", err) - } -} diff --git a/go/modcdp/transport/helpers_test.go b/go/modcdp/transport/helpers_test.go deleted file mode 100644 index 85f893b2..00000000 --- a/go/modcdp/transport/helpers_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package transport_test - -import "net" - -func boolPtr(value bool) *bool { - return &value -} - -func freePort() (int, error) { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return 0, err - } - defer listener.Close() - return listener.Addr().(*net.TCPAddr).Port, nil -} diff --git a/go/modcdp/types/codegen.go b/go/modcdp/types/codegen.go index c4267498..db7a9549 100644 --- a/go/modcdp/types/codegen.go +++ b/go/modcdp/types/codegen.go @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/types/codegen.ts +// - ./python/modcdp/types/codegen.py //go:build ignore // Go CDP generated-surface entrypoint. diff --git a/go/modcdp/types/toJSON.go b/go/modcdp/types/toJSON.go new file mode 100644 index 00000000..d8fa2d17 --- /dev/null +++ b/go/modcdp/types/toJSON.go @@ -0,0 +1,110 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/types/toJSON.ts +// - ./python/modcdp/types/toJSON.py +package types + +import ( + "reflect" + "strings" +) + +type ModCDPJSONChild interface { + ToJSON() map[string]any +} + +type ModCDPJSONConfig struct { + Config any + State map[string]any + Children map[string]ModCDPJSONChild +} + +func ModCDPToJSON(instance any, config ModCDPJSONConfig) map[string]any { + children := map[string]any{} + for key, child := range config.Children { + if child != nil { + children[key] = child.ToJSON() + } + } + jsonConfig := config.Config + if jsonConfig == nil { + value := reflect.Indirect(reflect.ValueOf(instance)) + if value.IsValid() && value.Kind() == reflect.Struct { + field := value.FieldByName("Config") + if field.IsValid() && field.CanInterface() { + jsonConfig = field.Interface() + } + } + } + result := map[string]any{ + "type": reflect.Indirect(reflect.ValueOf(instance)).Type().Name(), + "config": jsonConfig, + "state": mergeSimpleState(simpleState(instance), simpleState(config.State)), + } + if result["config"] == nil { + result["config"] = map[string]any{} + } + if len(children) > 0 { + result["children"] = children + } + return result +} + +func simpleState(input any) map[string]any { + state := map[string]any{} + if input == nil { + return state + } + value := reflect.Indirect(reflect.ValueOf(input)) + if value.Kind() == reflect.Map { + for _, key := range value.MapKeys() { + if key.Kind() != reflect.String { + continue + } + addSimpleStateValue(state, key.String(), value.MapIndex(key).Interface()) + } + return state + } + if value.Kind() != reflect.Struct { + return state + } + valueType := value.Type() + for index := 0; index < value.NumField(); index++ { + field := valueType.Field(index) + if !field.IsExported() || field.Name == "Config" { + continue + } + addSimpleStateValue(state, field.Name, value.Field(index).Interface()) + } + return state +} + +func addSimpleStateValue(state map[string]any, key string, value any) { + normalizedKey := strings.ToLower(key) + if key == "Config" || key == "config" || strings.Contains(normalizedKey, "token") || strings.Contains(normalizedKey, "secret") || strings.Contains(normalizedKey, "api_key") || strings.Contains(normalizedKey, "apikey") { + return + } + switch typed := value.(type) { + case string: + state[key] = typed + case int: + state[key] = typed + case int64: + state[key] = typed + case float64: + state[key] = typed + case bool: + state[key] = typed + } +} + +func mergeSimpleState(left map[string]any, right map[string]any) map[string]any { + merged := map[string]any{} + for key, value := range left { + merged[key] = value + } + for key, value := range right { + merged[key] = value + } + return merged +} diff --git a/go/modcdp/types/types.go b/go/modcdp/types/types.go index eb446f74..d18acf06 100644 --- a/go/modcdp/types/types.go +++ b/go/modcdp/types/types.go @@ -1,60 +1,88 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./js/src/types/modcdp.ts +// - ./python/modcdp/types/modcdp.py package types -type LaunchOptions struct { - ExecutablePath string `json:"executable_path,omitempty"` - ExtraArgs []string `json:"extra_args,omitempty"` - Args []string `json:"args,omitempty"` - Headless *bool `json:"headless,omitempty"` - Port int `json:"port,omitempty"` - RemoteDebugging string `json:"remote_debugging,omitempty"` - LoopbackCDP *bool `json:"loopback_cdp,omitempty"` - Sandbox *bool `json:"sandbox,omitempty"` - UserDataDir string `json:"user_data_dir,omitempty"` - CleanupUserDataDir *bool `json:"cleanup_user_data_dir,omitempty"` - ChromeReadyTimeoutMS int `json:"chrome_ready_timeout_ms,omitempty"` - ChromeReadyPollIntervalMS int `json:"chrome_ready_poll_interval_ms,omitempty"` - CDPURL string `json:"cdp_url,omitempty"` - BrowserbaseAPIKey string `json:"browserbase_api_key,omitempty"` - BrowserbaseBaseURL string `json:"browserbase_base_url,omitempty"` - BrowserbaseSessionID string `json:"browserbase_session_id,omitempty"` - BrowserbaseKeepAlive *bool `json:"browserbase_keep_alive,omitempty"` - BrowserbaseCloseSessionOnClose *bool `json:"browserbase_close_session_on_close,omitempty"` - Region string `json:"region,omitempty"` - Timeout int `json:"timeout,omitempty"` - InjectorExtensionID string `json:"injector_extension_id,omitempty"` - BrowserbaseBrowserSettings map[string]any `json:"browserbase_browser_settings,omitempty"` - BrowserbaseUserMetadata map[string]any `json:"browserbase_user_metadata,omitempty"` - BrowserbaseSessionCreateParams map[string]any `json:"browserbase_session_create_params,omitempty"` +type CdpCommandParams = map[string]any +type CdpCommandResult = map[string]any +type CdpEventParams = map[string]any +type ProtocolParams = map[string]any +type ProtocolResult = map[string]any +type ProtocolPayload = map[string]any +type ModCDPRoutes = map[string]string + +type RuntimeBindingCalledEvent struct { + Name string `json:"name"` + Payload string `json:"payload"` + ExecutionContextID *int `json:"executionContextId,omitempty"` +} + +type TargetAttachedToTargetEvent struct { + SessionID string `json:"sessionId"` + TargetInfo map[string]any `json:"targetInfo"` + WaitingForDebugger bool `json:"waitingForDebugger"` +} + +type LauncherConfig struct { + LauncherMode string `json:"launcher_mode,omitempty"` + LauncherLocalExecutablePath string `json:"launcher_local_executable_path,omitempty"` + LauncherLocalExtraArgs []string `json:"launcher_local_extra_args,omitempty"` + LauncherLocalArgs []string `json:"launcher_local_args,omitempty"` + LauncherLocalHeadless *bool `json:"launcher_local_headless,omitempty"` + LauncherLocalCDPListenPort int `json:"launcher_local_cdp_listen_port,omitempty"` + LauncherLocalLoopbackCDP *bool `json:"launcher_local_loopback_cdp,omitempty"` + LauncherLocalSandbox *bool `json:"launcher_local_sandbox,omitempty"` + LauncherLocalUserDataDir string `json:"launcher_local_user_data_dir,omitempty"` + LauncherLocalCleanupUserDataDir *bool `json:"launcher_local_cleanup_user_data_dir,omitempty"` + LauncherLocalChromeReadyTimeoutMS int `json:"launcher_local_chrome_ready_timeout_ms,omitempty"` + LauncherLocalChromeReadyPollIntervalMS int `json:"launcher_local_chrome_ready_poll_interval_ms,omitempty"` + LauncherRemoteCDPURL string `json:"launcher_remote_cdp_url,omitempty"` + LauncherBBAPIKey string `json:"launcher_bb_api_key,omitempty"` + LauncherBBBaseURL string `json:"launcher_bb_base_url,omitempty"` + LauncherBBSessionID string `json:"launcher_bb_session_id,omitempty"` + LauncherBBKeepAlive *bool `json:"launcher_bb_keep_alive,omitempty"` + LauncherBBCloseSessionOnClose *bool `json:"launcher_bb_close_session_on_close,omitempty"` + LauncherBBRegion string `json:"launcher_bb_region,omitempty"` + LauncherBBTimeout int `json:"launcher_bb_timeout,omitempty"` + LauncherBBExtensionID string `json:"launcher_bb_extension_id,omitempty"` + LauncherBBBrowserSettings map[string]any `json:"launcher_bb_browser_settings,omitempty"` + LauncherBBUserMetadata map[string]any `json:"launcher_bb_user_metadata,omitempty"` + LauncherBBSessionCreateParams map[string]any `json:"launcher_bb_session_create_params,omitempty"` +} + +type UpstreamTransportConfig struct { + UpstreamMode string `json:"upstream_mode,omitempty"` + UpstreamWSCDPURL string `json:"upstream_ws_cdp_url,omitempty"` + UpstreamWSConnectErrorSettleTimeoutMS int `json:"upstream_ws_connect_error_settle_timeout_ms,omitempty"` + UpstreamCDPSendTimeoutMS int `json:"upstream_cdp_send_timeout_ms,omitempty"` } type SendCDP func(method string, params map[string]any, sessionID string) (map[string]any, error) -type SessionIDForTarget func(targetID string) string -type AttachToTarget func(targetID string) string -type WaitForExecutionContext func(sessionID string, timeoutMS int) int - -type ExtensionInjectorConfig struct { - Send SendCDP `json:"-"` - SessionIDForTarget SessionIDForTarget `json:"-"` - AttachToTarget AttachToTarget `json:"-"` - WaitForExecutionContext WaitForExecutionContext `json:"-"` - InjectorExtensionPath string `json:"injector_extension_path,omitempty"` - InjectorExtensionID string `json:"injector_extension_id,omitempty"` - InjectorServiceWorkerURLIncludes []string `json:"injector_service_worker_url_includes,omitempty"` - InjectorServiceWorkerURLSuffixes []string `json:"injector_service_worker_url_suffixes,omitempty"` - InjectorTrustServiceWorkerTarget bool `json:"injector_trust_service_worker_target,omitempty"` - InjectorRequireServiceWorkerTarget bool `json:"injector_require_service_worker_target,omitempty"` - InjectorServiceWorkerReadyExpression string `json:"injector_service_worker_ready_expression,omitempty"` - InjectorCDPSendTimeoutMS int `json:"injector_cdp_send_timeout_ms,omitempty"` - InjectorExecutionContextTimeoutMS int `json:"injector_execution_context_timeout_ms,omitempty"` - InjectorServiceWorkerProbeTimeoutMS int `json:"injector_service_worker_probe_timeout_ms,omitempty"` - InjectorServiceWorkerReadyTimeoutMS int `json:"injector_service_worker_ready_timeout_ms,omitempty"` - InjectorServiceWorkerPollIntervalMS int `json:"injector_service_worker_poll_interval_ms,omitempty"` - InjectorTargetSessionPollIntervalMS int `json:"injector_target_session_poll_interval_ms,omitempty"` - InjectorBrowserbaseAPIKey string `json:"injector_browserbase_api_key,omitempty"` - InjectorBrowserbaseBaseURL string `json:"injector_browserbase_base_url,omitempty"` - UpstreamNativeMessagingHostName string `json:"upstream_nativemessaging_host_name,omitempty"` - UpstreamNATSURL string `json:"upstream_nats_url,omitempty"` - UpstreamNATSSubjectPrefix string `json:"upstream_nats_subject_prefix,omitempty"` +type InjectorConfig struct { + Send SendCDP `json:"-"` + InjectorMode string `json:"injector_mode,omitempty"` + InjectorCLIExtensionPath string `json:"injector_cli_extension_path,omitempty"` + InjectorCLIExtensionID string `json:"injector_cli_extension_id,omitempty"` + InjectorCDPExtensionPath string `json:"injector_cdp_extension_path,omitempty"` + InjectorCDPExtensionID string `json:"injector_cdp_extension_id,omitempty"` + InjectorBBExtensionPath string `json:"injector_bb_extension_path,omitempty"` + InjectorBBExtensionID string `json:"injector_bb_extension_id,omitempty"` + InjectorDiscoverExtensionPath string `json:"injector_discover_extension_path,omitempty"` + InjectorServiceWorkerExtensionID string `json:"injector_service_worker_extension_id,omitempty"` + InjectorServiceWorkerURLIncludes []string `json:"injector_service_worker_url_includes,omitempty"` + InjectorServiceWorkerURLSuffixes []string `json:"injector_service_worker_url_suffixes,omitempty"` + InjectorTrustServiceWorkerTarget bool `json:"injector_trust_service_worker_target,omitempty"` + InjectorRequireServiceWorkerTarget bool `json:"injector_require_service_worker_target,omitempty"` + InjectorServiceWorkerReadyExpression string `json:"injector_service_worker_ready_expression,omitempty"` + InjectorCDPSendTimeoutMS int `json:"injector_cdp_send_timeout_ms,omitempty"` + InjectorExecutionContextTimeoutMS int `json:"injector_execution_context_timeout_ms,omitempty"` + InjectorServiceWorkerProbeTimeoutMS int `json:"injector_service_worker_probe_timeout_ms,omitempty"` + InjectorServiceWorkerReadyTimeoutMS int `json:"injector_service_worker_ready_timeout_ms,omitempty"` + InjectorServiceWorkerPollIntervalMS int `json:"injector_service_worker_poll_interval_ms,omitempty"` + InjectorTargetSessionPollIntervalMS int `json:"injector_target_session_poll_interval_ms,omitempty"` + InjectorBBAPIKey string `json:"injector_bb_api_key,omitempty"` + InjectorBBBaseURL string `json:"injector_bb_base_url,omitempty"` } type ExtensionInjectionResult struct { @@ -63,6 +91,218 @@ type ExtensionInjectionResult struct { TargetID string `json:"target_id"` URL string `json:"url,omitempty"` SessionID string `json:"session_id"` - HasTabs bool `json:"has_tabs,omitempty"` - HasDebugger bool `json:"has_debugger,omitempty"` +} + +type ModCDPEvaluateParams struct { + Expression string `json:"expression"` + Params map[string]any `json:"params,omitempty"` + CDPSessionID *string `json:"cdpSessionId,omitempty"` +} + +type ModCDPAddCustomCommandParams struct { + Name string `json:"name"` + Expression string `json:"expression,omitempty"` + ParamsSchema map[string]any `json:"params_schema,omitempty"` + ResultSchema map[string]any `json:"result_schema,omitempty"` +} + +type ModCDPAddCustomEventObjectParams struct { + Name string `json:"name"` + EventSchema map[string]any `json:"event_schema,omitempty"` +} + +type ModCDPAddMiddlewareParams struct { + Name string `json:"name,omitempty"` + Phase string `json:"phase"` + Expression string `json:"expression"` +} + +type ModCDPPingParams struct { + SentAt int `json:"sent_at,omitempty"` +} + +type ModCDPPongEvent struct { + SentAt int `json:"sent_at"` + ReceivedAt int `json:"received_at"` + From string `json:"from"` +} + +type ModCDPPingLatency struct { + SentAt int `json:"sent_at"` + ReceivedAt *int `json:"received_at"` + ReturnedAt int `json:"returned_at"` + RoundTripMS int `json:"round_trip_ms"` + ServiceWorkerMS *int `json:"service_worker_ms"` + ReturnPathMS *int `json:"return_path_ms"` +} + +type ModCDPRouterConfig struct { + RouterRoutes map[string]string `json:"router_routes,omitempty"` + LoopbackExecutionContextTimeoutMS int `json:"loopback_execution_context_timeout_ms,omitempty"` +} + +type ModCDPClientConfig struct { + ClientHydrateAliases *bool `json:"client_hydrate_aliases,omitempty"` + ClientMirrorUpstreamEvents *bool `json:"client_mirror_upstream_events,omitempty"` + ClientCDPSendTimeoutMS int `json:"client_cdp_send_timeout_ms,omitempty"` + ClientEventWaitTimeoutMS int `json:"client_event_wait_timeout_ms,omitempty"` + ClientHeartbeatIntervalMS int `json:"client_heartbeat_interval_ms,omitempty"` +} + +type ModCDPDownstreamConfig struct { + DownstreamClientTimeoutMS int `json:"downstream_client_timeout_ms,omitempty"` + DownstreamCloseBrowserOnDisconnect *bool `json:"downstream_close_browser_on_disconnect,omitempty"` +} + +type ModCDPServerConfig struct { + Upstream UpstreamTransportConfig `json:"upstream,omitempty"` + Router ModCDPRouterConfig `json:"router,omitempty"` + ClientConfig ModCDPClientConfig `json:"client_config,omitempty"` + Downstream ModCDPDownstreamConfig `json:"downstream,omitempty"` + ServerBrowserToken string `json:"server_browser_token,omitempty"` + CustomCommands []ModCDPAddCustomCommandParams `json:"custom_commands,omitempty"` + CustomEvents []ModCDPAddCustomEventObjectParams `json:"custom_events,omitempty"` + CustomMiddlewares []ModCDPAddMiddlewareParams `json:"custom_middlewares,omitempty"` +} + +type ModCDPGetTopologyParams struct { + RootTargetID *string `json:"rootTargetId,omitempty"` + TargetID *string `json:"targetId,omitempty"` + Active *bool `json:"active,omitempty"` +} + +type ModCDPTopologyFrame struct { + TargetID string `json:"targetId"` + URL *string `json:"url,omitempty"` + ParentFrameID *string `json:"parentFrameId,omitempty"` + OuterBackendNodeID *int `json:"outerBackendNodeId,omitempty"` +} + +type ModCDPTopologyDomRoot struct { + Kind string `json:"kind"` + FrameID string `json:"frameId"` + OuterBackendNodeID *int `json:"outerBackendNodeId,omitempty"` + InnerBackendNodeID *int `json:"innerBackendNodeId,omitempty"` + Mode *string `json:"mode,omitempty"` + ExecutionContextID *int `json:"executionContextId,omitempty"` + UniqueContextID *string `json:"uniqueContextId,omitempty"` +} + +type ModCDPTopologyTarget struct { + TargetID string `json:"targetId"` + Type string `json:"type"` + Title *string `json:"title,omitempty"` + URL *string `json:"url,omitempty"` + Attached *bool `json:"attached,omitempty"` + ParentID *string `json:"parentId,omitempty"` + ParentFrameID *string `json:"parentFrameId,omitempty"` + SessionID *string `json:"sessionId,omitempty"` +} + +type ModCDPTopologyExecutionContext struct { + ID int `json:"id"` + Origin *string `json:"origin,omitempty"` + Name *string `json:"name,omitempty"` + UniqueID *string `json:"uniqueId,omitempty"` + AuxData map[string]any `json:"auxData,omitempty"` + SessionID *string `json:"sessionId"` + TargetID string `json:"targetId"` + FrameID *string `json:"frameId,omitempty"` + World string `json:"world"` +} + +type ModCDPTopology struct { + ObjectGroup string `json:"objectGroup"` + RootFrameID string `json:"rootFrameId"` + Frames map[string]ModCDPTopologyFrame `json:"frames"` + Roots map[string]ModCDPTopologyDomRoot `json:"roots"` + Targets map[string]ModCDPTopologyTarget `json:"targets"` + Contexts map[string]ModCDPTopologyExecutionContext `json:"contexts"` +} + +type ModCDPGetTopologyResponse = ModCDPTopology +type ModCDPConfigureParams = ModCDPServerConfig +type ModCDPCommandParams = any +type ModCDPCommandResult = any +type ModCDPEvaluateResponse = any +type ModCDPConfigureResponse = map[string]any + +type ModCDPOkResponse struct { + OK bool `json:"ok"` +} + +type ModCDPAddCustomCommandResponse struct { + Name string `json:"name"` + Registered bool `json:"registered"` +} + +type ModCDPAddCustomEventResponse struct { + Name string `json:"name"` + Registered bool `json:"registered"` +} + +type ModCDPAddMiddlewareResponse struct { + Name string `json:"name"` + Phase string `json:"phase"` + Registered bool `json:"registered"` +} + +type ModCDPPingResponse = ModCDPOkResponse + +type ModCDPBindingPayload struct { + Event string `json:"event"` + Data any `json:"data"` + CDPSessionID *string `json:"cdpSessionId"` +} + +type CdpDebuggeeCommandParams struct { + Debuggee map[string]any `json:"debuggee,omitempty"` + TabID *int `json:"tabId,omitempty"` + TargetID string `json:"targetId,omitempty"` + ExtensionID string `json:"extensionId,omitempty"` +} + +type CdpError struct { + Code *int `json:"code,omitempty"` + Message string `json:"message"` + Data any `json:"data,omitempty"` +} + +type CdpCommandMessage struct { + ID int `json:"id"` + Method string `json:"method"` + Params map[string]any `json:"params,omitempty"` + SessionID string `json:"sessionId,omitempty"` +} + +type CdpResponseMessage struct { + ID int `json:"id"` + Result any `json:"result,omitempty"` + Error *CdpError `json:"error,omitempty"` + SessionID string `json:"sessionId,omitempty"` +} + +type CdpEventMessage struct { + Method string `json:"method"` + Params map[string]any `json:"params,omitempty"` + SessionID string `json:"sessionId,omitempty"` +} + +type TranslatedStep struct { + Method string `json:"method"` + Params map[string]any `json:"params,omitempty"` + SessionID string `json:"sessionId,omitempty"` + Unwrap string `json:"unwrap,omitempty"` +} + +type TranslatedCommand struct { + Route string `json:"route"` + Target string `json:"target"` + Steps []TranslatedStep `json:"steps"` +} + +type UnwrappedModCDPEvent struct { + Event string `json:"event"` + Data any `json:"data"` + SessionID *string `json:"sessionId"` } diff --git a/js/examples/browserPaths.ts b/js/examples/browserPaths.ts new file mode 100644 index 00000000..72bba158 --- /dev/null +++ b/js/examples/browserPaths.ts @@ -0,0 +1,96 @@ +import { existsSync, readdirSync, statSync } from "node:fs"; +import { homedir, platform } from "node:os"; +import path from "node:path"; + +function wildcardToRegExp(value: string) { + return new RegExp(`^${value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`); +} + +function expandGlob(pattern: string) { + const normalized = path.normalize(pattern); + const { root } = path.parse(normalized); + const parts = normalized.slice(root.length).split(path.sep).filter(Boolean); + let candidates = [root || "."]; + for (const part of parts) { + const hasWildcard = part.includes("*"); + const matcher = hasWildcard ? wildcardToRegExp(part) : null; + const next: string[] = []; + for (const base of candidates) { + if (!existsSync(base)) continue; + if (!hasWildcard) { + const candidate = path.join(base, part); + if (existsSync(candidate)) next.push(candidate); + continue; + } + try { + for (const child of readdirSync(base)) { + if (matcher!.test(child)) next.push(path.join(base, child)); + } + } catch {} + } + candidates = next; + } + return candidates.filter((candidate) => existsSync(candidate)); +} + +function newestFirst(candidates: string[]) { + const score = (candidate: string) => { + const numbers = candidate.match(/\d+/g)?.map(Number) ?? []; + const version = numbers.length > 0 ? Math.max(...numbers) : 0; + let mtime = 0; + try { + mtime = statSync(candidate).mtimeMs; + } catch {} + return { version, mtime }; + }; + return [...new Set(candidates)].sort((a, b) => { + const left = score(a); + const right = score(b); + return right.version - left.version || right.mtime - left.mtime || a.localeCompare(b); + }); +} + +function chromeForTestingCandidates() { + const home = homedir(); + const patterns = + platform() === "darwin" + ? [ + path.join( + home, + "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ), + path.join(home, "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + path.join( + home, + "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ), + ] + : platform() === "win32" + ? [ + path.join( + process.env.LOCALAPPDATA || path.join(home, "AppData/Local"), + "ms-playwright/chromium-*/chrome-win*/chrome.exe", + ), + path.join(home, ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), + ] + : [ + path.join(home, ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + path.join(home, ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ]; + return newestFirst(patterns.flatMap(expandGlob)); +} + +function loadExtensionBrowserPath() { + const explicit_candidates = [process.env.CHROME_PATH, platform() === "linux" ? "/usr/bin/chromium" : null].filter( + (candidate): candidate is string => Boolean(candidate), + ); + for (const candidate of explicit_candidates) { + if (existsSync(candidate)) return candidate; + } + const [chrome_for_testing] = chromeForTestingCandidates(); + if (chrome_for_testing) return chrome_for_testing; + return undefined; +} + +export { loadExtensionBrowserPath }; diff --git a/js/examples/demo.ts b/js/examples/demo.ts index f6763830..d8bf9901 100644 --- a/js/examples/demo.ts +++ b/js/examples/demo.ts @@ -10,16 +10,16 @@ // *.* -> loopback_cdp on server. Default mode.) // --debugger client routes *.* through the extension service worker, // which uses chrome.debugger.sendCommand against the active -// tab. (*.* -> service_worker on client, *.* -> chrome_debugger +// tab. (*.* -> service_worker on client, *.* -> chromedebugger // on server.) // // --upstream select the browser/upstream transport. Defaults to ws. // reversews and nativemessaging use the fixed extension // defaults: ws://127.0.0.1:29292 and com.modcdp.bridge. // -// Valid CI/local demo combinations exercise the same surface: raw Browser.getVersion, raw -// Target.targetCreated event handling, Mod.evaluate, Custom.* commands, -// Custom.* events, and response middleware. +// Valid CI/local demo combinations exercise the same surface: a native Runtime +// command/event pair, Mod.evaluate, Custom.* commands, Custom.* events, and +// response/event middleware. import path from "node:path"; import { existsSync } from "node:fs"; @@ -28,13 +28,9 @@ import { fileURLToPath } from "node:url"; import { setTimeout as sleep } from "node:timers/promises"; import { createInterface } from "node:readline/promises"; import { spawn } from "node:child_process"; -import { z } from "zod"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -type TargetCreatedPayload = { - targetInfo?: { targetId?: string } & Record; -}; +import { ModCDPClient } from "../src/index.js"; +import { loadExtensionBrowserPath } from "./browserPaths.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = @@ -42,12 +38,11 @@ const EXTENSION_PATH = existsSync(path.join(candidate, "modcdp/service_worker.js")), ) ?? path.resolve(HERE, "..", "..", "extension"); const DEFAULT_DEMO_EVENT_TIMEOUT_MS = 10_000; +const DEFAULT_DEMO_CDP_SEND_TIMEOUT_MS = 60_000; +const DEFAULT_DEMO_EXECUTION_CONTEXT_TIMEOUT_MS = 60_000; const DEFAULT_REVERSE_TRANSPORT_WAIT_TIMEOUT_MS = 60_000; const DEFAULT_LIVE_CDP_POLL_INTERVAL_MS = 250; const DEFAULT_LIVE_CDP_ACTIVE_PORT_STALE_MS = 1_000; -const DEFAULT_TARGET_EVENT_TIMEOUT_MS = 10_000; -const DEFAULT_PAGE_TARGET_EVENT_TIMEOUT_MS = 10_000; -const DEFAULT_DEMO_EVENT_POLL_INTERVAL_MS = 20; const UPSTREAM_MODES = new Set(["ws", "pipe", "reversews", "nativemessaging", "nats"]); @@ -91,60 +86,53 @@ function parseArgs(argv) { } function serverRoutesFor(mode, upstream_mode) { - const routes = { + void upstream_mode; + return { "Mod.*": "service_worker", "Custom.*": "service_worker", - "*.*": mode === "loopback" ? "loopback_cdp" : mode === "debugger" ? "chrome_debugger" : "auto", + "*.*": mode === "loopback" ? "loopback_cdp" : mode === "debugger" ? "chromedebugger" : "auto", }; - if (mode === "loopback" || ["reversews", "nativemessaging", "nats"].includes(upstream_mode)) { - routes["Target.setDiscoverTargets"] = "loopback_cdp"; - routes["Target.createTarget"] = "loopback_cdp"; - routes["Target.activateTarget"] = "loopback_cdp"; - } - return routes; } function clientRoutesFor(mode) { - const directNormalEventRoutes = { - "Target.setDiscoverTargets": "direct_cdp", - "Target.createTarget": "direct_cdp", - "Target.activateTarget": "direct_cdp", - }; return { "Mod.*": "service_worker", "Custom.*": "service_worker", + "Runtime.*": "service_worker", "*.*": mode === "direct" ? "direct_cdp" : "service_worker", - ...directNormalEventRoutes, }; } -function clientOptionsFor(mode, upstream_mode, cdp_url, launch_options = {}) { +function clientConfigFor(mode, upstream_mode, cdp_url, launcher_config = {}) { const launcher = cdp_url ? ({ launcher_mode: "remote" } as const) - : ({ launcher_mode: "local", launcher_options: launch_options } as const); + : ({ launcher_mode: "local", ...launcher_config } as const); const upstream = { upstream_mode, - upstream_cdp_url: cdp_url, + ...(cdp_url ? { upstream_ws_cdp_url: cdp_url } : {}), ...(upstream_mode === "reversews" ? { upstream_reversews_wait_timeout_ms: DEFAULT_REVERSE_TRANSPORT_WAIT_TIMEOUT_MS } : {}), - ...(upstream_mode === "nativemessaging" - ? { upstream_nativemessaging_wait_timeout_ms: DEFAULT_REVERSE_TRANSPORT_WAIT_TIMEOUT_MS } - : {}), ...(upstream_mode === "nats" ? { upstream_nats_wait_timeout_ms: DEFAULT_REVERSE_TRANSPORT_WAIT_TIMEOUT_MS } : {}), }; const injector = { - injector_mode: "auto" as const, - injector_extension_path: EXTENSION_PATH, + injector_mode: cdp_url ? ("discover" as const) : ("cli" as const), + ...(cdp_url + ? { injector_discover_extension_path: EXTENSION_PATH } + : { injector_cli_extension_path: EXTENSION_PATH }), injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_execution_context_timeout_ms: DEFAULT_DEMO_EXECUTION_CONTEXT_TIMEOUT_MS, }; if (mode === "direct") { return { launcher, upstream, injector, - client: { - client_routes: clientRoutesFor(mode), + router: { + router_routes: clientRoutesFor(mode), + }, + client_config: { + client_cdp_send_timeout_ms: DEFAULT_DEMO_CDP_SEND_TIMEOUT_MS, }, }; } @@ -152,11 +140,17 @@ function clientOptionsFor(mode, upstream_mode, cdp_url, launch_options = {}) { launcher, upstream, injector, - client: { - client_routes: clientRoutesFor(mode), + router: { + router_routes: clientRoutesFor(mode), }, - server: { - server_routes: serverRoutesFor(mode, upstream_mode), + client_config: { + client_cdp_send_timeout_ms: DEFAULT_DEMO_CDP_SEND_TIMEOUT_MS, + }, + server_config: { + router: { + router_routes: serverRoutesFor(mode, upstream_mode), + loopback_execution_context_timeout_ms: DEFAULT_DEMO_EXECUTION_CONTEXT_TIMEOUT_MS, + }, }, }; } @@ -168,12 +162,6 @@ function assertObject(value, label) { return value; } -function isTargetCreatedPayload(value: unknown): value is TargetCreatedPayload { - if (value == null || typeof value !== "object" || Array.isArray(value)) return false; - const targetInfo = (value as Record).targetInfo; - return targetInfo == null || (typeof targetInfo === "object" && !Array.isArray(targetInfo)); -} - async function waitForEvent(cdp, eventName, predicate = (_payload) => true, timeoutMs = DEFAULT_DEMO_EVENT_TIMEOUT_MS) { return await new Promise((resolve, reject) => { const timeout = setTimeout(() => { @@ -244,51 +232,40 @@ async function main() { } let cdp_url; - let launch_options = {}; + let launcher_config = {}; if (live) { cdp_url = await waitForLiveCdpUrl(); } else { cdp_url = null; - launch_options = { - chrome_ready_timeout_ms: 60_000, - headless: process.platform === "linux" && !process.env.DISPLAY, - sandbox: process.platform !== "linux", + launcher_config = { + launcher_local_chrome_ready_timeout_ms: 60_000, + launcher_local_headless: process.platform === "linux" && !process.env.DISPLAY, + launcher_local_sandbox: process.platform !== "linux", + launcher_local_executable_path: loadExtensionBrowserPath(), }; } - const cdp = new ModCDPClient(clientOptionsFor(mode, upstream_mode, cdp_url, launch_options)); - const pageTargetEvents = []; - const targetCreatedEvents: TargetCreatedPayload[] = []; + const cdp = new ModCDPClient(clientConfigFor(mode, upstream_mode, cdp_url, launcher_config)); try { await cdp.connect(); - console.log("upstream cdp:", cdp.cdp_url); - cdp.on(cdp.Target.targetCreated, (payload) => { - const event = isTargetCreatedPayload(payload) ? payload : {}; - console.log("Target.targetCreated ->", event.targetInfo?.targetId); - targetCreatedEvents.push(event); - }); - console.log("connected; ext", cdp.extension_id, "session", cdp.ext_session_id); + console.log("upstream cdp:", cdp.upstream.config.upstream_ws_cdp_url); + console.log("connected; ext", cdp.injector?.extension_id, "session", cdp.injector?.session_id); console.log("connect timing ->", cdp.connect_timing); const configureResult = assertObject( await cdp.Mod.configure({ - upstream: { - upstream_mode, - }, - client: { - client_routes: clientRoutesFor(mode), - }, - server: { - server_routes: serverRoutesFor(mode, upstream_mode), + router: { + router_routes: serverRoutesFor(mode, upstream_mode), + loopback_execution_context_timeout_ms: DEFAULT_DEMO_EXECUTION_CONTEXT_TIMEOUT_MS, }, }), "Mod.configure", ); - if (configureResult.routes?.["*.*"] !== serverRoutesFor(mode, upstream_mode)["*.*"]) { + if (configureResult.router?.router_routes?.["*.*"] !== serverRoutesFor(mode, upstream_mode)["*.*"]) { throw new Error(`unexpected Mod.configure result ${JSON.stringify(configureResult)}`); } - console.log("Mod.configure ->", configureResult.routes); + console.log("Mod.configure ->", configureResult.router); const ping_sent_at = Date.now(); const pongPromise = waitForEvent(cdp, "Mod.pong", (event) => event?.sent_at === ping_sent_at); @@ -305,36 +282,6 @@ async function main() { return_path_ms: typeof pong.received_at === "number" ? ping_returned_at - pong.received_at : null, }); - // Browser.getVersion is browser-scoped and chrome.debugger is tab-scoped, - // so debugger mode asserts a positive raw CDP Runtime.evaluate instead. - if (mode === "debugger") { - try { - const version = assertObject(await cdp.Browser.getVersion(), "Browser.getVersion"); - if (typeof version.protocolVersion !== "string" || typeof version.product !== "string") { - throw new Error(`unexpected Browser.getVersion result ${JSON.stringify(version)}`); - } - console.log("Browser.getVersion ->", version); - } catch (e) { - console.log("Browser.getVersion -> (debugger route rejected:", e.message.replace(/\n/g, " "), ")"); - } - const runtimeEval = assertObject( - await cdp.Runtime.evaluate({ - expression: "(() => 42)()", - returnByValue: true, - }), - "Runtime.evaluate", - ); - if (runtimeEval.result?.value !== 42) - throw new Error(`unexpected Runtime.evaluate result ${JSON.stringify(runtimeEval)}`); - console.log("Runtime.evaluate ->", runtimeEval); - } else { - const version = assertObject(await cdp.Browser.getVersion(), "Browser.getVersion"); - if (typeof version.protocolVersion !== "string" || typeof version.product !== "string") { - throw new Error(`unexpected Browser.getVersion result ${JSON.stringify(version)}`); - } - console.log("Browser.getVersion ->", version); - } - const modcdpEval = (await cdp.Mod.evaluate({ expression: "({ extension_id: chrome.runtime.id })", })) as { @@ -342,86 +289,36 @@ async function main() { }; if ( typeof modcdpEval.extension_id !== "string" || - (cdp.extension_id && modcdpEval.extension_id !== cdp.extension_id) + (cdp.injector?.extension_id && modcdpEval.extension_id !== cdp.injector.extension_id) ) throw new Error(`unexpected Mod.evaluate result ${JSON.stringify(modcdpEval)}`); console.log("Mod.evaluate ->", modcdpEval); - const echoRegistration = assertObject( - await cdp.Mod.addCustomCommand({ - name: "Custom.echo", - expression: `async (params, method) => ({ echoed: params.value, method })`, - }), - "Mod.addCustomCommand Custom.echo", - ); - if (echoRegistration.registered !== true || echoRegistration.name !== "Custom.echo") { - throw new Error(`unexpected Custom.echo registration ${JSON.stringify(echoRegistration)}`); - } - const echoResult = assertObject(await cdp.send("Custom.echo", { value: "custom-command-ok" }), "Custom.echo"); - if (echoResult.echoed !== "custom-command-ok" || echoResult.method !== "Custom.echo") { - throw new Error(`unexpected Custom.echo result ${JSON.stringify(echoResult)}`); + let topologyChecked = false; + if (mode !== "direct") { + const topology = assertObject(await cdp.Mod.getTopology(), "Mod.getTopology"); + if ( + typeof topology.rootFrameId !== "string" || + !topology.frames?.[topology.rootFrameId] || + !Object.values(topology.roots || {}).some((root: any) => root?.kind === "document") || + !Object.values(topology.contexts || {}).some((context: any) => context?.world === "piercer") + ) { + throw new Error(`unexpected Mod.getTopology result ${JSON.stringify(topology)}`); + } + topologyChecked = true; + console.log("Mod.getTopology ->", { + rootFrameId: topology.rootFrameId, + frames: Object.keys(topology.frames || {}).length, + roots: Object.keys(topology.roots || {}).length, + contexts: Object.keys(topology.contexts || {}).length, + }); } - console.log("Custom.echo ->", echoResult); - const tabCommandRegistration = assertObject( - await cdp.Mod.addCustomCommand({ - name: "Custom.TabIdFromTargetId", - params_schema: { - targetId: cdp.types.zod.Target.TargetID, - }, - result_schema: { - tabId: z.number().nullable(), - }, - expression: `async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - return { tabId: target?.tabId ?? null }; - }`, - }), - "Mod.addCustomCommand Custom.TabIdFromTargetId", - ); - if (tabCommandRegistration.registered !== true) { - throw new Error(`unexpected TabIdFromTargetId registration ${JSON.stringify(tabCommandRegistration)}`); - } - const targetCommandRegistration = assertObject( - await cdp.Mod.addCustomCommand({ - name: "Custom.targetIdFromTabId", - params_schema: { - tabId: z.number(), - }, - result_schema: { - targetId: cdp.types.zod.Target.TargetID.nullable(), - tabId: z.number().optional(), - }, - expression: `async ({ tabId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.type === "page" && target.tabId === tabId); - return { targetId: target?.id ?? null }; - }`, - }), - "Mod.addCustomCommand Custom.targetIdFromTabId", - ); - if (targetCommandRegistration.registered !== true) { - throw new Error(`unexpected targetIdFromTabId registration ${JSON.stringify(targetCommandRegistration)}`); - } const responseMiddlewareRegistration = assertObject( await cdp.Mod.addMiddleware({ - name: "*", + name: "Custom.echo", phase: cdp.RESPONSE, - expression: `async (payload, next) => { - const seen = new WeakSet(); - const visit = async value => { - if (!value || typeof value !== "object" || seen.has(value)) return; - seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { - const { tabId } = await cdp.send("Custom.TabIdFromTargetId", { targetId: value.targetId }); - if (tabId != null) value.tabId = tabId; - } - for (const child of Array.isArray(value) ? value : Object.values(value)) await visit(child); - }; - await visit(payload); - return next(payload); - }`, + expression: `async (payload, next) => next({ ...payload, responseMiddleware: "ok" })`, }), "Mod.addMiddleware response", ); @@ -429,30 +326,39 @@ async function main() { throw new Error(`unexpected response middleware registration ${JSON.stringify(responseMiddlewareRegistration)}`); } - const eventMiddlewareRegistration = assertObject( - await cdp.Mod.addMiddleware({ - name: "*", - phase: cdp.EVENT, - expression: `async (payload, next) => { - const seen = new WeakSet(); - const visit = async value => { - if (!value || typeof value !== "object" || seen.has(value)) return; - seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { - const { tabId } = await cdp.send("Custom.TabIdFromTargetId", { targetId: value.targetId }); - if (tabId != null) value.tabId = tabId; - } - for (const child of Array.isArray(value) ? value : Object.values(value)) await visit(child); - }; - await visit(payload); - return next(payload); - }`, + if (mode !== "direct") { + const eventMiddlewareRegistration = assertObject( + await cdp.Mod.addMiddleware({ + name: "Custom.demoEvent", + phase: cdp.EVENT, + expression: `async (payload, next) => next({ ...payload, eventMiddleware: "ok" })`, + }), + "Mod.addMiddleware event", + ); + if (eventMiddlewareRegistration.registered !== true || eventMiddlewareRegistration.phase !== cdp.EVENT) { + throw new Error(`unexpected event middleware registration ${JSON.stringify(eventMiddlewareRegistration)}`); + } + } + + const echoRegistration = assertObject( + await cdp.Mod.addCustomCommand({ + name: "Custom.echo", + expression: `async (params, method) => ({ echoed: params.value, method })`, }), - "Mod.addMiddleware event", + "Mod.addCustomCommand Custom.echo", ); - if (eventMiddlewareRegistration.registered !== true || eventMiddlewareRegistration.phase !== cdp.EVENT) { - throw new Error(`unexpected event middleware registration ${JSON.stringify(eventMiddlewareRegistration)}`); + if (echoRegistration.registered !== true || echoRegistration.name !== "Custom.echo") { + throw new Error(`unexpected Custom.echo registration ${JSON.stringify(echoRegistration)}`); + } + const echoResult = assertObject(await cdp.send("Custom.echo", { value: "custom-command-ok" }), "Custom.echo"); + if ( + echoResult.echoed !== "custom-command-ok" || + echoResult.method !== "Custom.echo" || + echoResult.responseMiddleware !== "ok" + ) { + throw new Error(`unexpected Custom.echo result ${JSON.stringify(echoResult)}`); } + console.log("Custom.echo ->", echoResult); const demoEventRegistration = assertObject( await cdp.Mod.addCustomEvent({ name: "Custom.demoEvent" }), @@ -461,10 +367,37 @@ async function main() { if (demoEventRegistration.registered !== true || demoEventRegistration.name !== "Custom.demoEvent") { throw new Error(`unexpected Custom.demoEvent registration ${JSON.stringify(demoEventRegistration)}`); } - const demoEventPromise = waitForEvent(cdp, "Custom.demoEvent", (event) => event?.value === "custom-event-ok"); + const demoEventPromise = waitForEvent( + cdp, + "Custom.demoEvent", + (event) => event?.value === "custom-event-ok" && (mode === "direct" || event?.eventMiddleware === "ok"), + ); const emitResult = assertObject( await cdp.Mod.evaluate({ - expression: `async () => await ModCDP.emit("Custom.demoEvent", { value: "custom-event-ok" })`, + expression: + mode === "direct" + ? `async () => { + await globalThis.__ModCDP_custom_event__(JSON.stringify({ + event: "Custom.demoEvent", + data: { value: "custom-event-ok" }, + cdpSessionId: null, + })); + return { emitted: true }; + }` + : `async () => { + const params = await ModCDP.runMiddleware("event", "Custom.demoEvent", { value: "custom-event-ok" }, { + cdpSessionId, + event: { + method: "Custom.demoEvent", + params: { value: "custom-event-ok" }, + }, + }); + const sent = downstream.sendEvent({ + method: "Custom.demoEvent", + params, + }); + return { emitted: sent > 0 }; + }`, }), "Custom.demoEvent emit", ); @@ -473,95 +406,20 @@ async function main() { const demoEvent = assertObject(await demoEventPromise, "Custom.demoEvent"); console.log("Custom.demoEvent ->", demoEvent); - const PageTargetUpdated = z - .object({ - targetId: cdp.types.zod.Target.TargetID, - tabId: z.number().optional(), - url: z.string().nullable().optional(), - }) - .passthrough() - .meta({ id: "Custom.pageTargetUpdated" }); - const pageTargetEventRegistration = assertObject( - await cdp.Mod.addCustomEvent(PageTargetUpdated), - "Mod.addCustomEvent Custom.pageTargetUpdated", - ); - if (pageTargetEventRegistration.registered !== true) { - throw new Error(`unexpected page target event registration ${JSON.stringify(pageTargetEventRegistration)}`); - } - cdp.on(PageTargetUpdated, (event) => { - console.log("Custom.pageTargetUpdated ->", event); - pageTargetEvents.push(event); - }); - - await cdp.Target.setDiscoverTargets({ discover: true }); - const createdTarget = await cdp.Target.createTarget({ - url: "https://example.com", - background: true, - }); - const targetDeadline = Date.now() + DEFAULT_TARGET_EVENT_TIMEOUT_MS; - while ( - !targetCreatedEvents.some((event) => event?.targetInfo?.targetId === createdTarget.targetId) && - Date.now() < targetDeadline - ) { - await sleep(DEFAULT_DEMO_EVENT_POLL_INTERVAL_MS); - } - if (!targetCreatedEvents.some((event) => event?.targetInfo?.targetId === createdTarget.targetId)) { - throw new Error(`expected Target.targetCreated for ${createdTarget.targetId}`); - } - console.log("normal event matched ->", createdTarget.targetId); - - const tabFromTarget = await cdp.send("Custom.TabIdFromTargetId", { - targetId: createdTarget.targetId, - }); - const tabFromTargetId = - typeof tabFromTarget === "number" - ? tabFromTarget - : tabFromTarget && typeof tabFromTarget === "object" - ? (tabFromTarget as { tabId?: unknown }).tabId - : null; - if (typeof tabFromTargetId !== "number") - throw new Error(`unexpected Custom.TabIdFromTargetId result ${JSON.stringify(tabFromTarget)}`); - console.log("Custom.TabIdFromTargetId ->", tabFromTarget); - - await cdp.Target.activateTarget({ targetId: createdTarget.targetId }); - const pageTargetEmitResult = assertObject( - await cdp.Mod.evaluate({ - params: { targetId: createdTarget.targetId }, - expression: `async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - if (!target?.id) throw new Error(\`target \${targetId} not found\`); - await cdp.emit("Custom.pageTargetUpdated", { targetId: target.id, url: target.url ?? null }); - return { emitted: true, targetId: target.id }; - }`, + const runtimeEval = assertObject( + await cdp.Runtime.evaluate({ + expression: "(() => 42)()", + returnByValue: true, }), - "Custom.pageTargetUpdated emit", + "Runtime.evaluate", ); - if (pageTargetEmitResult.emitted !== true || pageTargetEmitResult.targetId !== createdTarget.targetId) { - throw new Error(`unexpected Custom.pageTargetUpdated emit result ${JSON.stringify(pageTargetEmitResult)}`); - } - const pageTargetDeadline = Date.now() + DEFAULT_PAGE_TARGET_EVENT_TIMEOUT_MS; - while ( - !pageTargetEvents.some((event) => event.targetId === createdTarget.targetId) && - Date.now() < pageTargetDeadline - ) { - await sleep(DEFAULT_DEMO_EVENT_POLL_INTERVAL_MS); - } - const pageTarget = pageTargetEvents.find((event) => event.targetId === createdTarget.targetId); - if (!pageTarget) throw new Error(`expected Custom.pageTargetUpdated for ${createdTarget.targetId}`); - if (pageTarget.tabId !== tabFromTargetId) - throw new Error(`unexpected Custom.pageTargetUpdated result ${JSON.stringify(pageTarget)}`); - - const targetFromTab = await cdp.send("Custom.targetIdFromTabId", { - tabId: pageTarget.tabId, - }); - if (targetFromTab.targetId !== createdTarget.targetId || targetFromTab.tabId !== pageTarget.tabId) { - throw new Error(`unexpected Custom.targetIdFromTabId/middleware result ${JSON.stringify(targetFromTab)}`); + if (runtimeEval.result?.value !== 42) { + throw new Error(`unexpected Runtime.evaluate result ${JSON.stringify(runtimeEval)}`); } - console.log("Custom.targetIdFromTabId ->", targetFromTab); + console.log("Runtime.evaluate ->", runtimeEval); console.log( - `\nSUCCESS (${mode}/${upstream_mode}): normal command, normal event, custom commands, custom event, and middleware all passed`, + `\nSUCCESS (${mode}/${upstream_mode}): native command, ${topologyChecked ? "topology, " : ""}custom commands, custom event, and middleware all passed`, ); // Drop into an interactive prompt when stdin is a TTY. Lets you poke at @@ -582,7 +440,7 @@ async function runRepl(cdp, mode) { console.log("Enter commands as Domain.method({...JSON params...}). Examples:"); console.log(" Browser.getVersion({})"); console.log(' Mod.evaluate({"expression": "chrome.tabs.query({active: true})"})'); - console.log(' Custom.TabIdFromTargetId({"targetId": "..."})'); + console.log(' Runtime.evaluate({"expression": "document.title", "returnByValue": true})'); console.log("Type exit or quit to disconnect (browser keeps running)."); const rl = createInterface({ input: process.stdin, output: process.stdout }); diff --git a/js/examples/playwright.ts b/js/examples/playwright.ts index b868ccfe..720881a1 100644 --- a/js/examples/playwright.ts +++ b/js/examples/playwright.ts @@ -12,6 +12,7 @@ import { chromium } from "playwright"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; import { startProxy } from "../src/proxy/proxy.js"; +import { loadExtensionBrowserPath } from "./browserPaths.js"; const here = path.dirname(fileURLToPath(import.meta.url)); const extension_path = @@ -26,15 +27,21 @@ let browser: Awaited> | null = null; try { chrome = await new LocalBrowserLauncher().launch({ - chrome_ready_timeout_ms: 60_000, - headless: process.platform === "linux" && !process.env.DISPLAY, - sandbox: process.platform !== "linux", - extra_args: [`--load-extension=${extension_path}`], + launcher_local_chrome_ready_timeout_ms: 60_000, + launcher_local_headless: process.platform === "linux" && !process.env.DISPLAY, + launcher_local_sandbox: process.platform !== "linux", + launcher_local_executable_path: loadExtensionBrowserPath(), + launcher_local_extra_args: [`--load-extension=${extension_path}`], }); proxy = await startProxy({ - port: await LocalBrowserLauncher.freePort(), - upstream: { upstream_mode: "ws", upstream_cdp_url: chrome.cdp_url }, - injector: { injector_mode: "auto", injector_extension_path: extension_path }, + proxy_listen_port: await LocalBrowserLauncher.freePort(), + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: chrome.cdp_url }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "discover", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, }); browser = await chromium.connectOverCDP(proxy.url); @@ -66,7 +73,9 @@ try { await cdp.send("Mod.addCustomCommand", { name: "Custom.proxyEcho", expression: `async (params) => { - await cdp.emit("Custom.proxyEvent", { source: "playwright", value: params.value }); + const event = { method: "Custom.proxyEvent", params: { source: "playwright", value: params.value } }; + if (cdpSessionId) event.sessionId = cdpSessionId; + downstream.sendEvent(event); return { source: "playwright", value: params.value }; }`, }); diff --git a/js/examples/puppeteer.ts b/js/examples/puppeteer.ts index 0aa8c0aa..812d63e9 100644 --- a/js/examples/puppeteer.ts +++ b/js/examples/puppeteer.ts @@ -12,6 +12,7 @@ import puppeteer from "puppeteer-core"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; import { startProxy } from "../src/proxy/proxy.js"; +import { loadExtensionBrowserPath } from "./browserPaths.js"; const here = path.dirname(fileURLToPath(import.meta.url)); const extension_path = @@ -26,15 +27,21 @@ let browser: Awaited> | null = null; try { chrome = await new LocalBrowserLauncher().launch({ - chrome_ready_timeout_ms: 60_000, - headless: process.platform === "linux" && !process.env.DISPLAY, - sandbox: process.platform !== "linux", - extra_args: [`--load-extension=${extension_path}`], + launcher_local_chrome_ready_timeout_ms: 60_000, + launcher_local_headless: process.platform === "linux" && !process.env.DISPLAY, + launcher_local_sandbox: process.platform !== "linux", + launcher_local_executable_path: loadExtensionBrowserPath(), + launcher_local_extra_args: [`--load-extension=${extension_path}`], }); proxy = await startProxy({ - port: await LocalBrowserLauncher.freePort(), - upstream: { upstream_mode: "ws", upstream_cdp_url: chrome.cdp_url }, - injector: { injector_mode: "auto", injector_extension_path: extension_path }, + proxy_listen_port: await LocalBrowserLauncher.freePort(), + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: chrome.cdp_url }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "discover", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, }); browser = await puppeteer.connect({ browserURL: proxy.url }); @@ -67,7 +74,9 @@ try { await cdp.send("Mod.addCustomCommand", { name: "Custom.proxyEcho", expression: `async (params) => { - await cdp.emit("Custom.proxyEvent", { source: "puppeteer", value: params.value }); + const event = { method: "Custom.proxyEvent", params: { source: "puppeteer", value: params.value } }; + if (cdpSessionId) event.sessionId = cdpSessionId; + downstream.sendEvent(event); return { source: "puppeteer", value: params.value }; }`, }); diff --git a/js/src/client/ModCDPClient.ts b/js/src/client/ModCDPClient.ts index 295c188e..09e31da8 100644 --- a/js/src/client/ModCDPClient.ts +++ b/js/src/client/ModCDPClient.ts @@ -1,221 +1,115 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/client/ModCDPClient.py +// - ./go/modcdp/client/ModCDPClient.go // ModCDPClient (JS): importable, no CLI, no demo code. // -// Constructor option groups mirror the owning runtime components: +// Constructor config groups mirror the owning runtime components: // launcher browser/session creation and cleanup // upstream message transport to either raw CDP or a ModCDP server -// injector raw-CDP extension discovery/injection/borrowing +// injector raw-CDP extension discovery/injection // client client-side routing, alias hydration, event mirroring, send/event timeouts -// server ModCDPServer.configure params +// server_config ModCDPServer.configure params // // Public methods: connect, send(method, params), on(event, handler), close. // oxlint-disable typescript-eslint/no-unsafe-declaration-merging -- alias members are assigned by connect(). import type { z } from "zod"; -import { createCdpAliases, type CdpAliases } from "../types/generated/aliases.js"; -export type { CdpAliases } from "../types/generated/aliases.js"; -import { commands as nativeCommandSchemas, events as nativeEventSchemas } from "../types/generated/zod.js"; +import type { CdpAliases, CdpCommandAliases } from "../types/generated/aliases.js"; +import * as Runtime from "../types/generated/zod/Runtime.js"; import { CUSTOM_EVENT_BINDING_NAME, - DEFAULT_CLIENT_ROUTES, UPSTREAM_EVENT_BINDING_NAME, wrapCommandIfNeeded, unwrapResponseIfNeeded, unwrapEventIfNeeded, } from "../translate/translate.js"; +import { type UpstreamTransportConfig, UpstreamTransport } from "../transport/UpstreamTransport.js"; +import { + CDPTypes, + type CDPCommandMap, + type CDPEventMap, + type CDPEventMapPayloads, + type CDPEventNameInput, + type CDPEventPayload, + type CDPTypesConfig, +} from "../types/CDPTypes.js"; +import { WSUpstreamTransport } from "../transport/WSUpstreamTransport.js"; +import { AutoSessionRouter, DEFAULT_CLIENT_ROUTER_ROUTES } from "../router/AutoSessionRouter.js"; +import { BrowserLauncher, type LauncherConfig } from "../launcher/BrowserLauncher.js"; +import { NoneBrowserLauncher } from "../launcher/NoneBrowserLauncher.js"; import { - endpointKindForUpstream, - type UpstreamEndpointKind, - type UpstreamMode, - type UpstreamTransport, -} from "../transport/UpstreamTransport.js"; -import type { BrowserLauncher, BrowserLaunchOptions, LaunchedBrowser } from "../launcher/BrowserLauncher.js"; -import { type ExtensionInjectorConfig, type ExtensionInjector, type SendCDP } from "../injector/ExtensionInjector.js"; -import { AutoSessionRouter } from "../router/AutoSessionRouter.js"; + ExtensionInjector, + InjectorConfigSchema, + type InjectorConfig, + type SendCDP, +} from "../injector/ExtensionInjector.js"; +import { + ModCDPClientConfigSchema, + ModCDPConfigureParamsSchema, + ModCDPLauncherConfigSchema, + ModCDPServerConfigSchema, + ModCDPRouterConfigSchema, + ModCDPUpstreamConfigSchema, +} from "../types/modcdp.js"; +import { modCDPToJSON } from "../types/toJSON.js"; import type { - CdpCommandMessage, - CdpError, CdpEventMessage, CdpResponseMessage, RuntimeBindingCalledEvent, ModCDPConfigureParams, - ModCDPServerOptions, - ModCDPCustomPayload, - ModCDPAddCustomCommandParams, - ModCDPAddCustomEventObjectParams, - ModCDPAddMiddlewareParams, + ModCDPServerConfig, ModCDPNamedValue, ModCDPPingLatency, ModCDPPongEvent, - ModCDPRoutes, ProtocolPayload, ProtocolParams, ProtocolResult, - TranslatedCommand, -} from "../types/modcdp.js"; -import { - CdpEventMessageSchema, - CdpResponseMessageSchema, - Mod, - normalizeModCDPName, - normalizeModCDPPayloadSchema, } from "../types/modcdp.js"; -export const DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000; -export const DEFAULT_EVENT_WAIT_TIMEOUT_MS = 10_000; -export const DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; -export const DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS = 10_000; -export const DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS = 60_000; -export const DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS = 100; -export const DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS = 20; -export const DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS = 250; -export const DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS = 250; -export const DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES = ["/modcdp/service_worker.js"]; -export const DEFAULT_UPSTREAM_REVERSEWS_BIND = "127.0.0.1:29292"; -export const DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS = 10_000; -export const DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS = 10_000; -export const DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS = 10_000; - -type PendingCommand = { - method: string; - resolve: (value: ProtocolResult) => void; - reject: (error: Error) => void; -}; -export type LauncherMode = "local" | "remote" | "bb" | "none"; -export type InjectorMode = "auto" | "discover" | "inject" | "borrow" | "none"; -export type LauncherOptions = { - launcher_mode?: LauncherMode; - launcher_executable_path?: string | null; - launcher_user_data_dir?: string | null; - launcher_options?: Record; -}; -export type UpstreamOptions = { - upstream_mode?: UpstreamMode; - upstream_cdp_url?: string | null; - upstream_nats_url?: string | null; - upstream_nats_subject_prefix?: string | null; - upstream_nats_wait_timeout_ms?: number; - upstream_reversews_bind?: string | null; - upstream_reversews_wait_timeout_ms?: number; - upstream_nativemessaging_manifest?: string | null; - upstream_nativemessaging_manifests?: string[] | null; - upstream_nativemessaging_host_name?: string | null; - upstream_nativemessaging_wait_timeout_ms?: number; - upstream_ws_connect_error_settle_timeout_ms?: number; -}; -export type InjectorOptions = { - injector_mode?: InjectorMode; - injector_extension_path?: string | null; - injector_extension_id?: string | null; - injector_service_worker_url_includes?: string[]; - injector_service_worker_url_suffixes?: string[] | null; - injector_trust_service_worker_target?: boolean; - injector_require_service_worker_target?: boolean; - injector_service_worker_ready_expression?: string | null; - injector_execution_context_timeout_ms?: number; - injector_service_worker_probe_timeout_ms?: number; - injector_service_worker_ready_timeout_ms?: number; - injector_service_worker_poll_interval_ms?: number; - injector_target_session_poll_interval_ms?: number; -}; -type InjectorConfig = Omit, "injector_service_worker_url_suffixes"> & { - injector_service_worker_url_suffixes: string[]; -}; -export type ClientConfigOptions = { - client_routes?: ModCDPRoutes; - client_hydrate_aliases?: boolean; - client_mirror_upstream_events?: boolean; - client_cdp_send_timeout_ms?: number; - client_event_wait_timeout_ms?: number; - client_heartbeat_interval_ms?: number; -}; -export type ClientOptions = { - launcher?: LauncherOptions; - upstream?: UpstreamOptions; - injector?: InjectorOptions; - client?: ClientConfigOptions; - server?: ModCDPServerOptions | null; - custom_commands?: ModCDPClientCustomCommandParams[]; - custom_events?: ModCDPAddCustomEventObjectParams[]; - custom_middlewares?: ModCDPAddMiddlewareParams[]; -}; -type ClientConfig = { - client_routes: ModCDPRoutes; - client_hydrate_aliases: boolean; - client_mirror_upstream_events: boolean; - client_cdp_send_timeout_ms: number; - client_event_wait_timeout_ms: number; - client_heartbeat_interval_ms: number; -}; -type NormalizedClientOptions = { - launcher: Required; - upstream: Required; - injector: InjectorConfig; - client: ClientConfig; - server: ModCDPServerOptions | null; - upstream_endpoint_kind: UpstreamEndpointKind; -}; -type ModCDPEventNameInput = string | symbol | (z.ZodType & ModCDPNamedValue); -type ModCDPEventPayload = TEvent extends z.ZodType ? TPayload : never; -type ModCDPClientCustomCommandParams = Omit & { - expression?: string | null; -}; -type ProtocolCommandSchema = { - params: z.ZodType; - result: z.ZodType; +type ModCDPClientConfig = { + launcher?: LauncherConfig; + upstream?: UpstreamTransportConfig; + injector?: z.input; + router?: z.input; + client_config?: z.input; + server_config?: z.input | null; + types?: CDPTypesConfig | CDPTypes; }; +type UpstreamTransportConstructor = new (config?: Record) => UpstreamTransport; +type ExtensionInjectorConstructor = new (config?: Record) => ExtensionInjector; +const upstream_transport_constructors = new Map([["ws", WSUpstreamTransport]]); -export type ModCDPCommandSpec = { - params: Params; - result: Result; -}; -export type ModCDPCommandMap = Record; -export type ModCDPEventMap = Record; -type MethodName = TName extends `${string}.${infer TMethod}` ? TMethod : never; -type DomainName = TName extends `${infer TDomain}.${string}` ? TDomain : never; -type CommandsForDomain = { - [TName in keyof TCommands as TName extends `${TDomain}.${string}` - ? MethodName> - : never]: undefined extends TCommands[TName]["params"] - ? (params?: TCommands[TName]["params"]) => Promise - : (params: TCommands[TName]["params"]) => Promise; -}; -export type ModCDPClientInstance< - TCommands extends ModCDPCommandMap = Record, - TEvents extends ModCDPEventMap = Record, -> = ModCDPClient & { - [TDomain in DomainName>]: CommandsForDomain; -} & { - on>( - eventName: TName, - listener: (event: TEvents[TName]) => void, - ): ModCDPClient; - once>( - eventName: TName, - listener: (event: TEvents[TName]) => void, - ): ModCDPClient; -}; +const browser_launcher_constructors = new Map, typeof BrowserLauncher>([ + ["none", NoneBrowserLauncher], +]); + +const extension_injector_constructors = new Map(); class ModCDPEventEmitter { private listeners = new Map void>>(); - on(event_name: string | symbol, listener: (...args: unknown[]) => void) { - const listeners = this.listeners.get(event_name); + on(event_name: CDPEventNameInput, listener: (...args: unknown[]) => void) { + const event_key = typeof event_name === "string" || typeof event_name === "symbol" ? event_name : event_name.id; + const listeners = this.listeners.get(event_key); if (listeners) listeners.add(listener); - else this.listeners.set(event_name, new Set([listener])); + else this.listeners.set(event_key, new Set([listener])); return this; } - once(event_name: string | symbol, listener: (...args: unknown[]) => void) { + once(event_name: CDPEventNameInput, listener: (...args: unknown[]) => void) { + const event_key = typeof event_name === "string" || typeof event_name === "symbol" ? event_name : event_name.id; const wrapped = (...args: unknown[]) => { - this.listeners.get(event_name)?.delete(wrapped); + this.listeners.get(event_key)?.delete(wrapped); listener(...args); }; - return this.on(event_name, wrapped); + return this.on(event_key, wrapped); } - off(event_name: string | symbol, listener: (...args: unknown[]) => void) { - this.listeners.get(event_name)?.delete(listener); + off(event_name: CDPEventNameInput, listener: (...args: unknown[]) => void) { + const event_key = typeof event_name === "string" || typeof event_name === "symbol" ? event_name : event_name.id; + this.listeners.get(event_key)?.delete(listener); return this; } @@ -228,326 +122,255 @@ class ModCDPEventEmitter { } } -function normalizeClientOptions({ - launcher = {}, - upstream = {}, - injector = {}, - client = {}, - server = {}, -}: ClientOptions) { - const upstream_mode = upstream.upstream_mode ?? "ws"; - const upstream_endpoint_kind = endpointKindForUpstream(upstream_mode); - const launcher_mode = - launcher.launcher_mode ?? - (upstream_endpoint_kind === "modcdp_server" ? "none" : upstream.upstream_cdp_url ? "remote" : "local"); - const injector_mode = - injector.injector_mode ?? (upstream_endpoint_kind === "raw_cdp" || launcher_mode !== "none" ? "auto" : "none"); - return { - launcher: { - launcher_mode, - launcher_executable_path: launcher.launcher_executable_path ?? null, - launcher_user_data_dir: launcher.launcher_user_data_dir ?? null, - launcher_options: launcher.launcher_options ?? {}, - }, - upstream: { - upstream_mode, - upstream_cdp_url: upstream.upstream_cdp_url ?? null, - upstream_nats_url: upstream.upstream_nats_url ?? null, - upstream_nats_subject_prefix: upstream.upstream_nats_subject_prefix ?? null, - upstream_nats_wait_timeout_ms: upstream.upstream_nats_wait_timeout_ms ?? DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS, - upstream_reversews_bind: upstream.upstream_reversews_bind ?? DEFAULT_UPSTREAM_REVERSEWS_BIND, - upstream_reversews_wait_timeout_ms: - upstream.upstream_reversews_wait_timeout_ms ?? DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS, - upstream_nativemessaging_manifest: upstream.upstream_nativemessaging_manifest ?? null, - upstream_nativemessaging_manifests: upstream.upstream_nativemessaging_manifests ?? null, - upstream_nativemessaging_host_name: upstream.upstream_nativemessaging_host_name ?? null, - upstream_nativemessaging_wait_timeout_ms: - upstream.upstream_nativemessaging_wait_timeout_ms ?? DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS, - upstream_ws_connect_error_settle_timeout_ms: - upstream.upstream_ws_connect_error_settle_timeout_ms ?? DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS, - }, - injector: { - injector_mode, - injector_extension_path: injector.injector_extension_path ?? null, - injector_extension_id: injector.injector_extension_id ?? null, - injector_service_worker_url_includes: injector.injector_service_worker_url_includes ?? [], - injector_service_worker_url_suffixes: - injector.injector_service_worker_url_suffixes ?? DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES, - injector_trust_service_worker_target: injector.injector_trust_service_worker_target ?? false, - injector_require_service_worker_target: injector.injector_require_service_worker_target ?? false, - injector_service_worker_ready_expression: injector.injector_service_worker_ready_expression ?? null, - injector_execution_context_timeout_ms: - injector.injector_execution_context_timeout_ms ?? DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS, - injector_service_worker_probe_timeout_ms: - injector.injector_service_worker_probe_timeout_ms ?? DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, - injector_service_worker_ready_timeout_ms: - injector.injector_service_worker_ready_timeout_ms ?? DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, - injector_service_worker_poll_interval_ms: - injector.injector_service_worker_poll_interval_ms ?? DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, - injector_target_session_poll_interval_ms: - injector.injector_target_session_poll_interval_ms ?? DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS, - }, - client: { - client_routes: { - ...DEFAULT_CLIENT_ROUTES, - ...(client.client_routes ?? {}), - }, - client_hydrate_aliases: client.client_hydrate_aliases ?? true, - client_mirror_upstream_events: client.client_mirror_upstream_events ?? true, - client_cdp_send_timeout_ms: client.client_cdp_send_timeout_ms ?? DEFAULT_CDP_SEND_TIMEOUT_MS, - client_event_wait_timeout_ms: client.client_event_wait_timeout_ms ?? DEFAULT_EVENT_WAIT_TIMEOUT_MS, - client_heartbeat_interval_ms: client.client_heartbeat_interval_ms ?? DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS, - }, - server: - server === null - ? null - : { - ...(upstream_endpoint_kind === "modcdp_server" ? { server_routes: { "*.*": "chrome_debugger" } } : {}), - ...(server ?? {}), - }, - upstream_endpoint_kind, - } satisfies NormalizedClientOptions; -} - -function defineCustomCommandMethod(client: ModCDPClient, name: string) { - const parts = name.split("."); - if (parts.length !== 2 || !parts[0] || !parts[1]) { - throw new Error(`Custom command must use Domain.method format, got ${name}`); - } - const [domain, method] = parts; - const target = client as unknown as Record>; - if (method === "*") { - target[domain] = new Proxy(target[domain] ?? {}, { - get(existing, property, receiver) { - if (typeof property !== "string") return Reflect.get(existing, property, receiver); - if (property in existing) return Reflect.get(existing, property, receiver); - const command_name = `${domain}.${property}`; - const alias = (params?: unknown) => client.send(command_name, params ?? {}); - Object.defineProperties(alias, { - cdp_command_name: { - value: command_name, - enumerable: true, - configurable: true, - }, - id: { value: command_name, enumerable: true, configurable: true }, - name: { value: command_name, configurable: true }, - kind: { value: "command", enumerable: true, configurable: true }, - meta: { - value: () => ({ - cdp_command_name: command_name, - id: command_name, - name: command_name, - kind: "command", - }), - configurable: true, - }, - }); - existing[property] = alias; - return alias; - }, - }); - return; - } - target[domain] ??= {}; - const alias = (params?: unknown) => client.send(name, params ?? {}); - Object.defineProperties(alias, { - cdp_command_name: { value: name, enumerable: true, configurable: true }, - id: { value: name, enumerable: true, configurable: true }, - name: { value: name, configurable: true }, - kind: { value: "command", enumerable: true, configurable: true }, - meta: { - value: () => ({ - cdp_command_name: name, - id: name, - name, - kind: "command", - }), - configurable: true, - }, - }); - target[domain][method] = alias; -} - -function hasCommandExpression( - command: ModCDPClientCustomCommandParams, -): command is ModCDPClientCustomCommandParams & { expression: string } { - return typeof command.expression === "string" && command.expression.length > 0; -} - -export class ModCDPClient extends ModCDPEventEmitter { - launcher: NormalizedClientOptions["launcher"]; - upstream: NormalizedClientOptions["upstream"]; - injector: NormalizedClientOptions["injector"]; - client: NormalizedClientOptions["client"]; - upstream_endpoint_kind: UpstreamEndpointKind; - cdp_url: string | null; - server: ModCDPServerOptions | null; - custom_commands: ModCDPClientCustomCommandParams[]; - custom_events: ModCDPAddCustomEventObjectParams[]; - custom_middlewares: ModCDPAddMiddlewareParams[]; - transport: UpstreamTransport | null; - next_id: number; - pending: Map; - ext_session_id: string | null; - ext_target_id: string | null; - ext_execution_context_id: number | null; - extension_id: string | null; +export class ModCDPClient< + TCommands extends CDPCommandMap = {}, + TEvents extends CDPEventMap = {}, +> extends ModCDPEventEmitter { + // sub-services + launcher: BrowserLauncher; + upstream: UpstreamTransport; + injector: ExtensionInjector | null; + router: AutoSessionRouter; + types: CDPTypes; + + // configuration + config: z.infer; + server_config: ModCDPServerConfig | null; + + // runtime state + event_wait_cleanups: Set<() => void>; + heartbeat_timer: ReturnType | null; latency: ModCDPPingLatency | null; connect_timing: Record | null; last_command_timing: Record | null; - last_raw_timing: Record | null; - event_schemas: Map; - command_params_schemas: Map; - command_result_schemas: Map; - command_result_unwrap_keys: Map; - cdp_aliases_hydrated: boolean; - event_wait_cleanups: Set<() => void>; - auto_sessions: AutoSessionRouter; - heartbeat_timer: ReturnType | null; - _injectors: ExtensionInjector[]; - _cdp: { - send: (method: string, params?: ProtocolParams, sessionId?: string | null) => Promise; - on: (eventName: string | symbol, listener: (...args: unknown[]) => void) => ModCDPClient; - once: (eventName: string | symbol, listener: (...args: unknown[]) => void) => ModCDPClient; - }; - _launched: LaunchedBrowser | null; + private readonly on_upstream_recv: (message: CdpResponseMessage | CdpEventMessage) => void; + private readonly on_upstream_close: (error: Error) => void; constructor({ launcher = {}, upstream = {}, injector = {}, - client = {}, - server = {}, - custom_commands = [], - custom_events = [], - custom_middlewares = [], - }: ClientOptions = {}) { + router = {}, + client_config = {}, + server_config = {}, + types = {}, + }: ModCDPClientConfig = {}) { super(); - const normalized = normalizeClientOptions({ - launcher, - upstream, - injector, - client, - server, + const raw_upstream = upstream as Record; + const upstream_mode_input = typeof raw_upstream.upstream_mode === "string" ? raw_upstream.upstream_mode : "ws"; + if (upstream_mode_input !== "ws" && !upstream_transport_constructors.has(upstream_mode_input)) { + throw new Error(`unknown upstream_mode=${upstream_mode_input}`); + } + const upstream_config = + upstream_mode_input === "ws" || !upstream_transport_constructors.has(upstream_mode_input) + ? ModCDPUpstreamConfigSchema.parse(upstream) + : raw_upstream; + const launcher_config = ModCDPLauncherConfigSchema.parse(launcher); + const raw_injector = injector as Record; + const injector_mode_input = typeof raw_injector.injector_mode === "string" ? raw_injector.injector_mode : "none"; + const injector_config = + injector_mode_input === "none" || + ["cli", "cdp", "bb", "discover"].includes(injector_mode_input) || + !extension_injector_constructors.has(injector_mode_input) + ? InjectorConfigSchema.parse(injector) + : raw_injector; + const router_config = ModCDPRouterConfigSchema.parse({ + ...router, + router_routes: { + ...DEFAULT_CLIENT_ROUTER_ROUTES, + ...(router.router_routes ?? {}), + }, + }); + const parsed_client_config = ModCDPClientConfigSchema.parse(client_config); + const parsed_server_config = server_config === null ? null : ModCDPServerConfigSchema.parse(server_config ?? {}); + const upstream_mode = upstream_config.upstream_mode as string; + const launcher_mode = launcher_config.launcher_mode; + const injector_mode = injector_config.injector_mode as string; + const Upstream = upstream_transport_constructors.get(upstream_mode); + if (!Upstream) throw new Error(`unknown upstream_mode=${upstream_mode}`); + this.upstream = new Upstream(upstream_config); + + const Launcher = browser_launcher_constructors.get(launcher_mode); + if (!Launcher) throw new Error(`unknown launcher_mode=${launcher_mode}`); + this.launcher = new Launcher(launcher_config); + + if (injector_mode === "none") this.injector = null; + else { + const Injector = extension_injector_constructors.get(injector_mode); + if (!Injector) throw new Error(`unknown injector.injector_mode=${injector_mode}`); + this.injector = new Injector(injector_config); + } + this.config = parsed_client_config; + this.upstream.update({ + upstream_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, }); - this.launcher = normalized.launcher; - this.upstream = normalized.upstream; - this.injector = normalized.injector; - this.client = normalized.client; - this.upstream_endpoint_kind = normalized.upstream_endpoint_kind; - this.cdp_url = this.upstream.upstream_cdp_url; - this.server = normalized.server; - this.custom_commands = custom_commands; - this.custom_events = custom_events; - this.custom_middlewares = custom_middlewares; + this.server_config = parsed_server_config === null ? null : parsed_server_config; + this.types = types instanceof CDPTypes ? types : new CDPTypes(types); - this.transport = null; - this.next_id = 1; - this.pending = new Map(); - this.ext_session_id = null; - this.ext_target_id = null; - this.ext_execution_context_id = null; - this.extension_id = null; this.latency = null; this.connect_timing = null; this.last_command_timing = null; - this.last_raw_timing = null; - this.event_schemas = new Map(); - this.command_params_schemas = new Map(); - this.command_result_schemas = new Map(); - this.command_result_unwrap_keys = new Map(); - this.cdp_aliases_hydrated = false; this.event_wait_cleanups = new Set(); this.heartbeat_timer = null; - this.auto_sessions = new AutoSessionRouter( - (method, params = {}, session_id = null) => - this._sendMessage(method, params, session_id) as Promise, - () => this.injector.injector_execution_context_timeout_ms, - ); - this._injectors = []; - this._launched = null; - - this._cdp = { - send: (method: string, params: ProtocolParams = {}, session_id: string | null = null) => - this._sendMessage(method, params, session_id, { - record_raw_timing: true, - }) as Promise, - on: (event_name: string | symbol, listener: (...args: unknown[]) => void) => this.on(event_name, listener), - once: (event_name: string | symbol, listener: (...args: unknown[]) => void) => this.once(event_name, listener), + this.on_upstream_recv = (message) => this._onRecv(message); + this.on_upstream_close = (error) => { + this._stopHeartbeat(); + this.emit("error", error); }; - this._hydrateNativeProtocolSchemas(); - void this._hydrateCdpAliases(); - this._hydrateCustomSurface(); + this.router = new AutoSessionRouter({ + ...router_config, + upstream: this.upstream, + types: this.types, + }); + if (this.config.client_hydrate_aliases) + this.types.installAliases(this, (method, params) => this.send(method, params)); + } + + toJSON() { + return modCDPToJSON(this, { + config: { client_config: this.config, server_config: this.server_config }, + state: { + event_wait_cleanups: this.event_wait_cleanups.size, + heartbeat_timer: this.heartbeat_timer != null, + latency: this.latency?.round_trip_ms ?? null, + connected: this.connect_timing != null, + }, + children: { + launcher: this.launcher, + upstream: this.upstream, + injector: this.injector, + router: this.router, + types: this.types, + }, + }); + } + + configure({ + upstream, + router, + client_config, + server_config, + }: Pick, "upstream" | "router" | "client_config" | "server_config"> = {}) { + if (client_config !== undefined) { + this.config = ModCDPClientConfigSchema.parse({ ...this.config, ...client_config }); + } + this.upstream.update({ + upstream_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, + }); + if (upstream !== undefined) { + const upstream_mode = + typeof (upstream as { upstream_mode?: unknown }).upstream_mode === "string" + ? (upstream as { upstream_mode: string }).upstream_mode + : this.upstream.config.upstream_mode; + if (upstream_mode !== this.upstream.config.upstream_mode) { + const Upstream = upstream_transport_constructors.get(upstream_mode); + if (!Upstream) throw new Error(`unknown upstream_mode=${upstream_mode}`); + const previous_upstream = this.upstream; + this.upstream = new Upstream(upstream); + this.upstream.update({ + upstream_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, + }); + this.router.stop(); + this.router = new AutoSessionRouter({ + ...this.router.config, + upstream: this.upstream, + types: this.types, + }); + void previous_upstream.close(); + } else { + this.upstream.update(upstream); + } + } + if (router !== undefined) { + this.router.config = ModCDPRouterConfigSchema.parse({ + ...this.router.config, + ...router, + router_routes: { + ...this.router.config.router_routes, + ...(router.router_routes ?? {}), + }, + }); + } + if (server_config !== undefined) { + const parsed_server_config = server_config === null ? null : ModCDPServerConfigSchema.parse(server_config); + this.server_config = parsed_server_config; + } + if (this.config.client_hydrate_aliases) + this.types.installAliases(this, (method, params) => this.send(method, params)); + return this; } async connect() { const connect_started_at = Date.now(); - await this._hydrateCdpAliases(); const transport_started_at = Date.now(); await this._connectUpstreamTransport(); const transport_connected_at = Date.now(); - this.transport?.onRecv((message) => this._onRecv(message)); - this.transport?.onClose((error) => { - this._stopHeartbeat(); - if (this.pending.size > 0) this._rejectAll(error); - }); + this.upstream.onRecv(this.on_upstream_recv); + this.upstream.onClose(this.on_upstream_close); - if (this.upstream_endpoint_kind === "modcdp_server") { - await this.transport?.waitForPeer?.(); - this.event_schemas.set("Mod.pong", Mod.PongEvent); - if (this.server !== null) { - await this._sendMessage("Mod.configure", this._serverConfigureParams(), null); - } + if (this.injector == null && this.server_config === null) { + const connected_at = Date.now(); + this.connect_timing = { + started_at: connect_started_at, + upstream_mode: this.upstream.config.upstream_mode, + transport_started_at, + transport_connected_at, + transport_duration_ms: transport_connected_at - transport_started_at, + connected_at, + duration_ms: connected_at - connect_started_at, + }; + return this; + } + + if (this.upstream.peer_kind === "modcdp_server") { + const configure_started_at = Date.now(); + if (this.server_config !== null) await this.upstream.send("Mod.configure", this._serverConfigureParams()); + const configure_completed_at = Date.now(); this._startHeartbeat(); void this._measurePingLatency().catch(() => {}); const connected_at = Date.now(); this.connect_timing = { started_at: connect_started_at, - upstream_mode: this.upstream.upstream_mode, - upstream_endpoint_kind: this.upstream_endpoint_kind, + upstream_mode: this.upstream.config.upstream_mode, transport_started_at, transport_connected_at, transport_duration_ms: transport_connected_at - transport_started_at, + configure_started_at, + configure_completed_at, + configure_duration_ms: configure_completed_at - configure_started_at, connected_at, duration_ms: connected_at - connect_started_at, }; return this; } - await this._initializeRawCDPTransport(); + await this.router.start(); const injector_started_at = Date.now(); - if (this.injector.injector_mode === "none") { - throw new Error("injector.injector_mode=none cannot be used with a raw_cdp upstream."); + if (this.injector == null) { + throw new Error("injector.injector_mode=none cannot be used with an extension-routed browser upstream."); } - const ext = await this._runInjectors( - (method, params, session_id) => this._sendMessage(method, params, session_id) as Promise, + const injector = this.injector; + await this._runInjector((method, params, session_id) => + this.upstream.send(method, params, session_id, { + timeout_ms: this.config.client_cdp_send_timeout_ms, + }), ); const injector_completed_at = Date.now(); - this.extension_id = typeof ext.extension_id === "string" ? ext.extension_id : null; - this.ext_target_id = ext.target_id as string; - this.ext_session_id = ext.session_id as string; - this.event_schemas.set("Mod.pong", Mod.PongEvent); - const ext_context = this.auto_sessions.waitForExecutionContext(this.ext_session_id, { - timeout_ms: this.injector.injector_execution_context_timeout_ms, - }); - await this._sendMessage("Runtime.enable", {}, this.ext_session_id); - this.ext_execution_context_id = await ext_context; + if (injector.target_id == null || injector.session_id == null) { + throw new Error(`${injector.constructor.name} did not record a ModCDP extension target.`); + } + await this.router.send(Runtime.EnableCommand.id, {}, injector.session_id); await Promise.all([ - this._sendMessage("Runtime.addBinding", { name: CUSTOM_EVENT_BINDING_NAME }, this.ext_session_id), - this.client.client_mirror_upstream_events - ? this._sendMessage("Runtime.addBinding", { name: UPSTREAM_EVENT_BINDING_NAME }, this.ext_session_id) + this.router.send(Runtime.AddBindingCommand.id, { name: CUSTOM_EVENT_BINDING_NAME }, injector.session_id), + this.config.client_mirror_upstream_events + ? this.router.send(Runtime.AddBindingCommand.id, { name: UPSTREAM_EVENT_BINDING_NAME }, injector.session_id) : Promise.resolve(), ]); - if (this.server !== null) { - await this._sendRaw( - wrapCommandIfNeeded("Mod.configure", this._serverConfigureParams(), { - routes: this.client.client_routes, - cdpSessionId: this.ext_session_id, - }), - ); + if (this.server_config !== null) { + await this.send("Mod.configure", this._serverConfigureParams()); } this._startHeartbeat(); @@ -555,12 +378,11 @@ export class ModCDPClient extends ModCDPEventEmitter { const connected_at = Date.now(); this.connect_timing = { started_at: connect_started_at, - upstream_mode: this.upstream.upstream_mode, - upstream_endpoint_kind: this.upstream_endpoint_kind, + upstream_mode: this.upstream.config.upstream_mode, transport_started_at, transport_connected_at, transport_duration_ms: transport_connected_at - transport_started_at, - injector_source: ext.source, + injector_source: injector.source, injector_started_at, injector_completed_at, injector_duration_ms: injector_completed_at - injector_started_at, @@ -570,55 +392,34 @@ export class ModCDPClient extends ModCDPEventEmitter { return this; } - async send(method: string, params: unknown = {}, session_id: string | null = null) { + async send(method: string, params: unknown = {}, session_id: string | null = null): Promise> { const started_at = Date.now(); - let command_params = this.command_params_schemas.get(method)?.parse(params ?? {}) ?? params ?? {}; - if (method === "Mod.addCustomCommand") { - const parsed = Mod.AddCustomCommandParams.parse(command_params); - const name = normalizeModCDPName(parsed.name); - const params_schema = normalizeModCDPPayloadSchema(parsed.params_schema); - const result_schema = normalizeModCDPPayloadSchema(parsed.result_schema); - if (params_schema) this.command_params_schemas.set(name, params_schema); - if (result_schema) { - this.command_result_schemas.set(name, result_schema); - this._setResultUnwrapKey(name, result_schema); - } - defineCustomCommandMethod(this, name); - if (!parsed.expression) { - this.last_command_timing = { - method, - target: "client", - started_at, - completed_at: Date.now(), - duration_ms: Date.now() - started_at, - }; - return this.command_result_schemas.get(method)?.parse({ name, registered: true }) ?? { name, registered: true }; - } - command_params = { - ...parsed, - name, - params_schema: null, - result_schema: null, + const can_register_locally = + this.upstream.peer_kind !== "modcdp_server" && + (method === "Mod.addCustomCommand" || + (method === "Mod.addCustomEvent" && !this.injector?.session_id) || + (method === "Mod.addMiddleware" && !this.injector?.session_id)); + const prepared = this.types.prepareCommand(method, params, can_register_locally); + const command_params = prepared.params; + if (prepared.custom_command_name) { + this.types.installCustomCommandAlias(this, prepared.custom_command_name, (alias_method, alias_params) => + this.send(alias_method, alias_params), + ); + } + if (prepared.local_result) { + this.last_command_timing = { + method, + target: "client", + started_at, + completed_at: Date.now(), + duration_ms: Date.now() - started_at, }; - } else if (method === "Mod.addCustomEvent") { - const parsed = Mod.AddCustomEventObjectParams.parse(params ?? {}); - const name = normalizeModCDPName(parsed.name); - const event_schema = normalizeModCDPPayloadSchema(parsed.event_schema); - if (event_schema) this.event_schemas.set(name, event_schema); - if (!this.ext_session_id && this.upstream_endpoint_kind !== "modcdp_server") { - this.last_command_timing = { - method, - target: "client", - started_at, - completed_at: Date.now(), - duration_ms: Date.now() - started_at, - }; - return this.command_result_schemas.get(method)?.parse({ name, registered: true }) ?? { name, registered: true }; - } - command_params = { ...parsed, name, event_schema: null }; + return this.types.parseCommandResult(method, prepared.local_result); } - if (this.upstream_endpoint_kind === "modcdp_server") { - const result = await this._sendMessage(method, command_params as ProtocolParams); + if (this.upstream.peer_kind === "modcdp_server") { + const result = await this.upstream.send(method, command_params as ProtocolPayload, session_id, { + timeout_ms: this.config.client_cdp_send_timeout_ms, + }); const completed_at = Date.now(); this.last_command_timing = { method, @@ -627,13 +428,42 @@ export class ModCDPClient extends ModCDPEventEmitter { completed_at, duration_ms: completed_at - started_at, }; - return result; + return this.types.parseCommandResult(method, result); + } + if (this.injector == null && this.server_config === null) { + const result = await this.router.send(method, command_params as ProtocolParams, session_id); + const completed_at = Date.now(); + this.last_command_timing = { + method, + target: "browser_targets", + started_at, + completed_at, + duration_ms: completed_at - started_at, + }; + return this.types.parseCommandResult(method, result); } const command = wrapCommandIfNeeded(method, command_params as ProtocolParams, { - routes: this.client.client_routes, - targetCdpSessionId: session_id, + routes: this.router.config.router_routes, + cdpSessionId: session_id, }); - const result = await this._sendRaw(command); + let result: ProtocolResult = {}; + let unwrap = null; + if (command.target === "direct_cdp") { + const [step] = command.steps; + result = await this.router.send(step.method, step.params ?? {}, step.sessionId ?? null); + unwrap = step.unwrap ?? null; + } else if (command.target === "service_worker") { + const injector = this.injector; + if (injector == null || injector.session_id == null) { + throw new Error("service_worker commands require an injected ModCDP extension target."); + } + const step = this.types.serviceWorkerCommandStep(method, command_params as ProtocolParams, session_id); + result = await this.router.send(step.method, step.params ?? {}, injector.session_id); + unwrap = step.unwrap ?? null; + result = unwrapResponseIfNeeded(result, unwrap); + } else { + throw new Error(`Unsupported command target "${command.target}"`); + } const completed_at = Date.now(); this.last_command_timing = { method, @@ -642,405 +472,132 @@ export class ModCDPClient extends ModCDPEventEmitter { completed_at, duration_ms: completed_at - started_at, }; - const result_schema = this.command_result_schemas.get(method); - if (!result_schema) return result; - const parsed_result = result_schema.parse(result); - const unwrap_key = this.command_result_unwrap_keys.get(method); - return unwrap_key && parsed_result && typeof parsed_result === "object" - ? (parsed_result as Record)[unwrap_key] - : parsed_result; - } - - async sendRaw(method: string, params: ProtocolParams = {}, session_id: string | null = null) { - return await this._sendMessage(method, params, session_id); + return this.types.parseCommandResult(method, result); } - async _hydrateCdpAliases() { - if (!this.client.client_hydrate_aliases || this.cdp_aliases_hydrated) return; - Object.assign( - this, - createCdpAliases((method, params) => this.send(method, params), { - onCustomCommand: (name, params_schema, result_schema) => { - if (params_schema) this.command_params_schemas.set(name, params_schema); - if (result_schema) { - this.command_result_schemas.set(name, result_schema); - this._setResultUnwrapKey(name, result_schema); - } - defineCustomCommandMethod(this, name); - }, - onCustomEvent: (name, event_schema) => { - if (event_schema) this.event_schemas.set(name, event_schema); - }, - }), - ); - this.cdp_aliases_hydrated = true; - } - - _hydrateCustomSurface() { - for (const command of this.custom_commands) { - const name = normalizeModCDPName(command.name); - const params_schema = command.params_schema ? Mod.PayloadSchemaSpec.parse(command.params_schema) : null; - const result_schema = command.result_schema ? Mod.PayloadSchemaSpec.parse(command.result_schema) : null; - const normalized_params_schema = params_schema == null ? null : this._normalizePayloadSchema(params_schema); - const normalized_result_schema = result_schema == null ? null : this._normalizePayloadSchema(result_schema); - if (normalized_params_schema) this.command_params_schemas.set(name, normalized_params_schema); - if (normalized_result_schema) { - this.command_result_schemas.set(name, normalized_result_schema); - this._setResultUnwrapKey(name, normalized_result_schema); - } - defineCustomCommandMethod(this, name); - } - for (const event of this.custom_events) { - const name = normalizeModCDPName(event.name); - const event_schema = event.event_schema ? this._normalizePayloadSchema(event.event_schema) : null; - if (event_schema) this.event_schemas.set(name, event_schema); - } - } - - _hydrateNativeProtocolSchemas() { - for (const [method, schema] of Object.entries(nativeCommandSchemas) as [string, ProtocolCommandSchema][]) { - this.command_params_schemas.set(method, schema.params); - this.command_result_schemas.set(method, schema.result); - } - this.command_params_schemas.set("Mod.evaluate", Mod.EvaluateParams); - this.command_result_schemas.set("Mod.evaluate", Mod.EvaluateResponse); - this.command_params_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandParams); - this.command_result_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandResponse); - this.command_params_schemas.set("Mod.addCustomEvent", Mod.AddCustomEventParams); - this.command_result_schemas.set("Mod.addCustomEvent", Mod.AddCustomEventResponse); - this.command_params_schemas.set("Mod.addMiddleware", Mod.AddMiddlewareParams); - this.command_result_schemas.set("Mod.addMiddleware", Mod.AddMiddlewareResponse); - this.command_params_schemas.set("Mod.configure", Mod.ConfigureParams); - this.command_result_schemas.set("Mod.configure", Mod.ConfigureResponse); - this.command_params_schemas.set("Mod.ping", Mod.PingParams); - this.command_result_schemas.set("Mod.ping", Mod.PingResponse); - - for (const [event, schema] of Object.entries(nativeEventSchemas) as [string, z.ZodType][]) { - this.event_schemas.set(event, schema); - } - this.event_schemas.set("Mod.pong", Mod.PongEvent); - } - - _normalizePayloadSchema(schema: unknown) { - return normalizeModCDPPayloadSchema(Mod.PayloadSchemaSpec.parse(schema)); - } - - _setResultUnwrapKey(name: string, schema: z.ZodType) { - const shape = "shape" in schema && schema.shape && typeof schema.shape === "object" ? schema.shape : null; - const keys = shape ? Object.keys(shape) : []; - if (keys.length === 1) this.command_result_unwrap_keys.set(name, keys[0]); - else this.command_result_unwrap_keys.delete(name); - } - - _parseEventPayload(method: string, data: unknown) { - return this.event_schemas.get(method)?.parse(data) ?? data; - } - - _serviceWorkerUrlSuffixes() { - return this.injector.injector_service_worker_url_suffixes; - } - - _serverConfigureParams(): ModCDPConfigureParams { - return { + _serverConfigureParams(): z.input { + const configured_server_config = this.server_config ?? {}; + const launcher_server_config = this.launcher.configForServer(this.upstream); + const has_upstream_config = launcher_server_config.upstream != null || configured_server_config.upstream != null; + const server_config = { + ...launcher_server_config, + ...configured_server_config, upstream: { - upstream_mode: this.upstream.upstream_mode, - ...(this.upstream.upstream_nats_url ? { upstream_nats_url: this.upstream.upstream_nats_url } : {}), - ...(this.upstream.upstream_nats_subject_prefix - ? { - upstream_nats_subject_prefix: this.upstream.upstream_nats_subject_prefix, - } - : {}), + ...(launcher_server_config.upstream ?? {}), + ...(configured_server_config.upstream ?? {}), }, - client: { - client_routes: this.client.client_routes, + router: { + ...(launcher_server_config.router ?? {}), + ...(configured_server_config.router ?? {}), }, - server: { - ...this._serverDefaults(), - ...(this.server ?? {}), + client_config: { + ...(launcher_server_config.client_config ?? {}), + ...(configured_server_config.client_config ?? {}), + }, + downstream: { + ...(launcher_server_config.downstream ?? {}), + ...(configured_server_config.downstream ?? {}), }, - custom_commands: this.custom_commands.filter(hasCommandExpression).map((command) => ({ - name: normalizeModCDPName(command.name), - expression: command.expression, - params_schema: null as null, - result_schema: null as null, - })), - custom_events: this.custom_events.map((event) => ({ - name: normalizeModCDPName(event.name), - event_schema: null as null, - })), - custom_middlewares: this.custom_middlewares.map(({ name, phase, expression }) => ({ - ...(name == null ? {} : { name: normalizeModCDPName(name) }), - phase, - expression, - })), }; - } - - _serverDefaults(): ModCDPServerOptions { + const loopback_execution_context_timeout_ms = this.injector + ? this.injector.config.injector_execution_context_timeout_ms + : this.router.config.loopback_execution_context_timeout_ms; return { - server_cdp_send_timeout_ms: this.client.client_cdp_send_timeout_ms, - server_loopback_execution_context_timeout_ms: this.injector.injector_execution_context_timeout_ms, - server_ws_connect_error_settle_timeout_ms: this.upstream.upstream_ws_connect_error_settle_timeout_ms, - server_downstream_client_timeout_ms: Math.max(this.client.client_heartbeat_interval_ms * 4, 1_000), - }; - } - - async _connectUpstreamTransport() { - if (this.transport) return; - const launcher = await this._browserLauncher(); - const transport = await this._upstreamTransport(); - const injectors = await this._injectorsForConfig(); - this._injectors = injectors; - const initial_transport_config = this._upstreamTransportConfig(); - transport.update(initial_transport_config); - launcher.update(this._launcherOptions()); - for (const injector of injectors) injector.update(this._baseInjectorConfig()); - for (const injector of injectors) injector.update(launcher.getInjectorConfig()); - for (const injector of injectors) injector.update(transport.getInjectorConfig()); - for (const injector of injectors) await injector.prepare(); - for (const injector of injectors) launcher.update(injector.getLauncherConfig()); - for (const injector of injectors) transport.update(injector.getTransportConfig()); - launcher.update(transport.getLauncherConfig()); - launcher.update({ loopback_cdp: this._serverNeedsLoopbackCdp() }); - transport.update(launcher.getTransportConfig()); - - if (transport.endpoint_kind === "modcdp_server") await transport.connect(); - if (this.launcher.launcher_mode !== "none") { - this._launched = await launcher.launch(); - transport.update(launcher.getTransportConfig()); - for (const injector of injectors) injector.update(launcher.getInjectorConfig()); - for (const injector of injectors) transport.update(injector.getTransportConfig()); - } - const launched_cdp_url = this._launched?.cdp_url ?? null; - if (transport.endpoint_kind === "raw_cdp") await transport.connect(); - - this.transport = transport; - this.cdp_url = - transport.endpoint_kind === "raw_cdp" ? ((transport.url || launched_cdp_url) ?? null) : launched_cdp_url; - // For ws mode, cdp_url has been resolved to the concrete WebSocket CDP endpoint after connect(). - if (transport.mode === "ws" && transport.url) this.upstream.upstream_cdp_url = transport.url; - const server_config = { - ...(transport.endpoint_kind === "modcdp_server" && launched_cdp_url && !launched_cdp_url.startsWith("pipe://") - ? { server_loopback_cdp_url: launched_cdp_url } + ...(has_upstream_config + ? { + upstream: { + upstream_ws_connect_error_settle_timeout_ms: + this.upstream.config.upstream_ws_connect_error_settle_timeout_ms, + ...server_config.upstream, + }, + } : {}), - ...launcher.getServerConfig(), - ...transport.getServerConfig(), + router: { + ...(server_config.router ?? {}), + loopback_execution_context_timeout_ms, + }, + client_config: { + client_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, + ...(server_config.client_config ?? {}), + }, + downstream: { + downstream_client_timeout_ms: Math.max(this.config.client_heartbeat_interval_ms * 4, 1_000), + ...(server_config.downstream ?? {}), + }, + ...(server_config.server_browser_token !== undefined + ? { server_browser_token: server_config.server_browser_token } + : {}), + custom_commands: this.types.customCommandWireRegistrations({ + expression_required: true, + }), + custom_events: this.types.customEventWireRegistrations(), + custom_middlewares: this.types.customMiddlewareWireRegistrations(), }; - if (this.server !== null && server_config.server_loopback_cdp_url) { - const configured_loopback = this.server.server_loopback_cdp_url; - if ( - !Object.hasOwn(this.server, "server_loopback_cdp_url") || - configured_loopback === initial_transport_config.cdp_url || - configured_loopback === launched_cdp_url - ) { - this.server = { ...this.server, ...server_config }; - } - } } - async _upstreamTransport(): Promise { - switch (this.upstream.upstream_mode as UpstreamMode) { - case "ws": { - const { WebSocketUpstreamTransport } = await import("../transport/WebSocketUpstreamTransport.js"); - return new WebSocketUpstreamTransport(); - } - case "pipe": { - const { PipeUpstreamTransport } = await import(/* @vite-ignore */ "../transport/PipeUpstreamTransport.js"); - return new PipeUpstreamTransport(); - } - case "reversews": { - const { ReverseWebSocketUpstreamTransport } = await import( - /* @vite-ignore */ "../transport/ReverseWebSocketUpstreamTransport.js" - ); - return new ReverseWebSocketUpstreamTransport(); - } - case "nativemessaging": { - const { NativeMessagingUpstreamTransport } = await import( - /* @vite-ignore */ "../transport/NativeMessagingUpstreamTransport.js" - ); - return new NativeMessagingUpstreamTransport(); - } - case "nats": { - const { NatsUpstreamTransport } = await import(/* @vite-ignore */ "../transport/NatsUpstreamTransport.js"); - return new NatsUpstreamTransport(); - } - default: - throw new Error(`unknown upstream.upstream_mode=${this.upstream.upstream_mode}`); + async _connectUpstreamTransport() { + const launcher = this.launcher; + const transport = this.upstream; + if (this.injector) { + this.injector.update({ + injector_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, + }); + await this.injector.prepare(); + launcher.update(this.injector.configForLauncher()); + transport.update(this.injector.configForUpstream()); } - } + launcher.update(transport.configForLauncher()); + launcher.update({ + launcher_local_loopback_cdp: + this.server_config != null && + !this.server_config.upstream?.upstream_ws_cdp_url && + this.server_config.router?.router_routes?.["*.*"] === "loopback_cdp", + }); + transport.update(launcher.configForUpstream()); - async _browserLauncher(): Promise { - switch (this.launcher.launcher_mode as LauncherMode) { - case "local": { - const { LocalBrowserLauncher } = await import(/* @vite-ignore */ "../launcher/LocalBrowserLauncher.js"); - return new LocalBrowserLauncher(this.launcher.launcher_options); - } - case "remote": { - const { RemoteBrowserLauncher } = await import("../launcher/RemoteBrowserLauncher.js"); - return new RemoteBrowserLauncher(this.launcher.launcher_options, this.upstream.upstream_cdp_url); - } - case "bb": { - const { BrowserbaseBrowserLauncher } = await import( - /* @vite-ignore */ "../launcher/BrowserbaseBrowserLauncher.js" - ); - return new BrowserbaseBrowserLauncher(this.launcher.launcher_options); - } - case "none": { - const { NoopBrowserLauncher } = await import("../launcher/NoopBrowserLauncher.js"); - return new NoopBrowserLauncher(this.launcher.launcher_options); - } - default: - throw new Error(`unknown launcher.launcher_mode=${this.launcher.launcher_mode}`); + if (launcher.config.launcher_mode !== "none") { + await launcher.launch(); + transport.update(launcher.configForUpstream()); + if (this.injector) transport.update(this.injector.configForUpstream()); } - } - - _launcherOptions(): BrowserLaunchOptions { - return { - ...this.launcher.launcher_options, - ...(this.launcher.launcher_executable_path ? { executable_path: this.launcher.launcher_executable_path } : {}), - ...(this.launcher.launcher_user_data_dir ? { user_data_dir: this.launcher.launcher_user_data_dir } : {}), - }; - } + const peer_wait_started_at = Date.now(); + await transport.connect(); + await transport.waitForPeer({ connected_after_ms: peer_wait_started_at }); - _serverNeedsLoopbackCdp() { - if (!this.server || this.server.server_loopback_cdp_url) return false; - return Object.values(this.server.server_routes ?? {}).includes("loopback_cdp"); + if (this.upstream.config.upstream_mode === "ws" && transport.config.upstream_ws_cdp_url) + this.upstream.update({ upstream_ws_cdp_url: transport.config.upstream_ws_cdp_url }); } - _upstreamTransportConfig() { - return { - cdp_url: this.upstream.upstream_cdp_url, - upstream_nats_url: this.upstream.upstream_nats_url, - upstream_nats_subject_prefix: this.upstream.upstream_nats_subject_prefix, - upstream_nats_wait_timeout_ms: this.upstream.upstream_nats_wait_timeout_ms, - upstream_reversews_bind: this.upstream.upstream_reversews_bind, - upstream_reversews_wait_timeout_ms: this.upstream.upstream_reversews_wait_timeout_ms, - upstream_nativemessaging_manifest: this.upstream.upstream_nativemessaging_manifest, - upstream_nativemessaging_manifests: this.upstream.upstream_nativemessaging_manifests, - upstream_nativemessaging_host_name: this.upstream.upstream_nativemessaging_host_name, - upstream_nativemessaging_wait_timeout_ms: this.upstream.upstream_nativemessaging_wait_timeout_ms, - injector_extension_id: this.injector.injector_extension_id, - }; - } - - async _injectorsForConfig() { - if (this.injector.injector_mode === "none") return []; - const injectors: ExtensionInjector[] = []; - const prefer_launch_injection = this.injector.injector_mode === "auto" && this.launcher.launcher_mode === "local"; - if ( - (this.injector.injector_mode === "auto" || this.injector.injector_mode === "discover") && - !prefer_launch_injection - ) { - const { DiscoveredExtensionInjector } = await import("../injector/DiscoveredExtensionInjector.js"); - injectors.push(new DiscoveredExtensionInjector()); - } - if (this.injector.injector_mode === "auto" || this.injector.injector_mode === "inject") { - if (this.launcher.launcher_mode === "bb") { - const { BBBrowserExtensionInjector } = await import( - /* @vite-ignore */ "../injector/BBBrowserExtensionInjector.js" - ); - injectors.push(new BBBrowserExtensionInjector()); - } - if (this.launcher.launcher_mode === "local") { - const { LocalBrowserLaunchExtensionInjector } = await import( - /* @vite-ignore */ "../injector/LocalBrowserLaunchExtensionInjector.js" - ); - injectors.push(new LocalBrowserLaunchExtensionInjector()); - } - const { ExtensionsLoadUnpackedInjector } = await import( - /* @vite-ignore */ "../injector/ExtensionsLoadUnpackedInjector.js" - ); - injectors.push(new ExtensionsLoadUnpackedInjector()); - } - if (prefer_launch_injection) { - const { DiscoveredExtensionInjector } = await import("../injector/DiscoveredExtensionInjector.js"); - injectors.push(new DiscoveredExtensionInjector()); - } - if (this.injector.injector_mode === "auto" || this.injector.injector_mode === "borrow") { - const { BorrowedExtensionInjector } = await import(/* @vite-ignore */ "../injector/BorrowedExtensionInjector.js"); - injectors.push(new BorrowedExtensionInjector()); - } - if (injectors.length === 0) throw new Error(`unknown injector.injector_mode=${this.injector.injector_mode}`); - return injectors; - } - - _baseInjectorConfig(send: SendCDP | null = null): ExtensionInjectorConfig { - const service_worker_url_suffixes = this._serviceWorkerUrlSuffixes(); - const trust_service_worker_target = - this.injector.injector_trust_service_worker_target || - this.injector.injector_service_worker_url_includes.length > 0 || - service_worker_url_suffixes.some((suffix) => suffix.split("/").filter(Boolean).length > 1); - return { + async _runInjector(send: SendCDP) { + if (this.injector == null) throw new Error("injector.injector_mode=none cannot inject an extension."); + const injector = this.injector; + injector.update({ send, - sessionIdForTarget: (target_id) => this.auto_sessions.sessionIdForTarget(target_id), - attachToTarget: send ? (target_id) => this.auto_sessions.attachToTarget(target_id) : null, - waitForExecutionContext: (session_id, timeout_ms) => - this.auto_sessions.waitForExecutionContext(session_id, { timeout_ms }), - injector_extension_path: this.injector.injector_extension_path, - injector_extension_id: this.injector.injector_extension_id, - injector_service_worker_url_includes: this.injector.injector_service_worker_url_includes, - injector_service_worker_url_suffixes: service_worker_url_suffixes, - injector_trust_service_worker_target: trust_service_worker_target, - injector_require_service_worker_target: - this.injector.injector_require_service_worker_target || this.injector.injector_mode === "discover", - injector_service_worker_ready_expression: this.injector.injector_service_worker_ready_expression, - injector_cdp_send_timeout_ms: this.client.client_cdp_send_timeout_ms, - injector_execution_context_timeout_ms: this.injector.injector_execution_context_timeout_ms, - injector_service_worker_probe_timeout_ms: this.injector.injector_service_worker_probe_timeout_ms, - injector_service_worker_ready_timeout_ms: this.injector.injector_service_worker_ready_timeout_ms, - injector_service_worker_poll_interval_ms: this.injector.injector_service_worker_poll_interval_ms, - injector_target_session_poll_interval_ms: this.injector.injector_target_session_poll_interval_ms, - }; - } - - async _runInjectors(send: SendCDP, injectors: ExtensionInjector[] | null = null) { - injectors ??= await this._injectorsForConfig(); - const errors: string[] = []; - for (const injector of injectors) { - injector.update(this._baseInjectorConfig(send)); - try { - await injector.prepare(); - const result = await injector.inject(); - if (result) return result; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - injector.last_error = error instanceof Error ? error : new Error(message); - errors.push(`${injector.constructor.name}: ${message}`); - } + injector_cdp_send_timeout_ms: this.config.client_cdp_send_timeout_ms, + }); + await injector.prepare(); + const result = await injector.inject(); + if (result) { + injector.recordInjectionResult(result); + return result; } - throw new Error( - `Cannot install, discover, or borrow the ModCDP extension in the running browser.` + - (errors.length ? `\n\n${errors.join("\n")}` : ""), - ); - } - - async _initializeRawCDPTransport() { - await Promise.all([ - this._sendMessage("Target.setAutoAttach", { - autoAttach: true, - waitForDebuggerOnStart: false, - flatten: true, - }), - this._sendMessage("Target.setDiscoverTargets", { discover: true }), - ]); + throw new Error(`${injector.constructor.name} did not return a ModCDP extension target.`); } async close() { this._stopHeartbeat(); for (const cleanup of this.event_wait_cleanups) cleanup(); this.event_wait_cleanups.clear(); - if (this._launched) await this._launched.close(); - this._launched = null; - await this.transport?.close(); - this.transport = null; - for (const injector of this._injectors) await injector.close(); - this._injectors = []; + this.router.stop(); + await this.launcher.close(); + await this.upstream.close(); + await this.injector?.close(); } _startHeartbeat() { this._stopHeartbeat(); - if (this.server?.server_close_browser_on_downstream_disconnect !== true) return; - const interval_ms = this.client.client_heartbeat_interval_ms; + if (this.server_config?.downstream?.downstream_close_browser_on_disconnect !== true) return; + const interval_ms = this.config.client_heartbeat_interval_ms; this.heartbeat_timer = setInterval(() => { void this.send("Mod.ping", { sent_at: Date.now() }).catch(() => {}); }, interval_ms); @@ -1054,49 +611,44 @@ export class ModCDPClient extends ModCDPEventEmitter { on( event_name: TEvent, - listener: (event: ModCDPEventPayload) => void, + listener: (event: CDPEventPayload, sessionId: string | null) => void, + ): this; + on, string>>( + event_name: TName, + listener: (event: CDPEventMapPayloads[TName], sessionId: string | null) => void, ): this; on(event_name: string | symbol, listener: (...args: unknown[]) => void): this; - on(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void): this; - on(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void) { - if (typeof event_name !== "string" && typeof event_name !== "symbol") { - const name = normalizeModCDPName(event_name); - this.event_schemas.set(name, event_name); - return super.on(name, listener); - } - return super.on(typeof event_name === "symbol" ? event_name : normalizeModCDPName(event_name), listener); + on(event_name: CDPEventNameInput, listener: (...args: never[]) => void): this; + on(event_name: CDPEventNameInput, listener: (...args: never[]) => void) { + return super.on(this.types.normalizeEventName(event_name), listener); } once( event_name: TEvent, - listener: (event: ModCDPEventPayload) => void, + listener: (event: CDPEventPayload, sessionId: string | null) => void, + ): this; + once, string>>( + event_name: TName, + listener: (event: CDPEventMapPayloads[TName], sessionId: string | null) => void, ): this; once(event_name: string | symbol, listener: (...args: unknown[]) => void): this; - once(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void): this; - once(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void) { - if (typeof event_name !== "string" && typeof event_name !== "symbol") { - const name = normalizeModCDPName(event_name); - this.event_schemas.set(name, event_name); - return super.once(name, listener); - } - return super.once(typeof event_name === "symbol" ? event_name : normalizeModCDPName(event_name), listener); + once(event_name: CDPEventNameInput, listener: (...args: never[]) => void): this; + once(event_name: CDPEventNameInput, listener: (...args: never[]) => void) { + return super.once(this.types.normalizeEventName(event_name), listener); } off( event_name: TEvent, - listener: (event: ModCDPEventPayload) => void, + listener: (event: CDPEventPayload, sessionId: string | null) => void, ): this; off(event_name: string | symbol, listener: (...args: unknown[]) => void): this; - off(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void): this; - off(event_name: ModCDPEventNameInput, listener: (...args: unknown[]) => void) { - if (typeof event_name !== "string" && typeof event_name !== "symbol") { - return super.off(normalizeModCDPName(event_name), listener); - } - return super.off(typeof event_name === "symbol" ? event_name : normalizeModCDPName(event_name), listener); + off(event_name: CDPEventNameInput, listener: (...args: never[]) => void): this; + off(event_name: CDPEventNameInput, listener: (...args: never[]) => void) { + return super.off(this.types.normalizeEventName(event_name), listener); } - _waitForEvent(event_name: ModCDPEventNameInput, { timeout_ms }: { timeout_ms?: number } = {}) { - const effective_timeout_ms = timeout_ms ?? this.client.client_event_wait_timeout_ms; + _waitForEvent(event_name: CDPEventNameInput, { timeout_ms }: { timeout_ms?: number } = {}) { + const effective_timeout_ms = timeout_ms ?? this.config.client_event_wait_timeout_ms; let settled = false; let timeout: ReturnType | null = null; let cancel: () => void = () => {}; @@ -1145,152 +697,39 @@ export class ModCDPClient extends ModCDPEventEmitter { } } - async _sendRaw(command: TranslatedCommand) { - if (command.target === "direct_cdp") { - const [step] = command.steps; - return this._sendMessage(step.method, step.params ?? {}, step.sessionId ?? null) as Promise; - } - if (command.target !== "service_worker") { - throw new Error(`Unsupported command target "${command.target}"`); - } - - let result: ProtocolResult = {}; - let unwrap = null; - for (const step of command.steps) { - const step_params = - step.method === "Runtime.callFunctionOn" && step.params && !Object.hasOwn(step.params, "executionContextId") - ? { - ...step.params, - executionContextId: - this.ext_execution_context_id ?? - (await this.auto_sessions.waitForExecutionContext(this.ext_session_id, { - timeout_ms: this.injector.injector_execution_context_timeout_ms, - })), - } - : (step.params ?? {}); - result = (await this._sendMessage(step.method, step_params, this.ext_session_id)) as ProtocolResult; - unwrap = step.unwrap ?? null; - } - return unwrapResponseIfNeeded(result, unwrap); - } - - _sendMessage( - method: string, - params: ProtocolParams = {}, - session_id: string | null = null, - options: { record_raw_timing?: boolean; timeout_ms?: number | null } = {}, - ) { - if (!this.transport) return Promise.reject(new Error("ModCDP upstream is not connected.")); - const id = this.next_id++; - const started_at = Date.now(); - const message: CdpCommandMessage = { id, method, params }; - if (session_id) message.sessionId = session_id; - return new Promise((resolve, reject) => { - const timeout_ms = options.timeout_ms ?? this.client.client_cdp_send_timeout_ms; - let timeout: ReturnType | null = null; - const finish = (callback: () => void) => { - if (timeout != null) clearTimeout(timeout); - timeout = null; - callback(); - }; - this.pending.set(id, { - method, - resolve: (value: ProtocolResult) => { - finish(() => { - if (options.record_raw_timing) { - const completed_at = Date.now(); - this.last_raw_timing = { - method, - started_at, - completed_at, - duration_ms: completed_at - started_at, - }; - } - resolve(value); - }); - }, - reject: (error: Error) => { - finish(() => reject(error)); - }, - }); - if (timeout_ms != null && timeout_ms > 0) { - timeout = setTimeout(() => { - if (!this.pending.delete(id)) return; - reject(new Error(`${method} timed out after ${timeout_ms}ms`)); - }, timeout_ms); - } - void (async () => { - try { - if (this.upstream_endpoint_kind === "modcdp_server") await this.transport?.waitForPeer?.(); - this.transport?.send(message); - } catch (error) { - if (this.pending.delete(id)) reject(error instanceof Error ? error : new Error(String(error))); - } - })(); - }); - } - - _rejectAll(error: Error) { - const pending_methods = [...this.pending.values()].map((pending) => pending.method); - const reject_error = - pending_methods.length === 0 ? error : new Error(`${error.message}; pending=${pending_methods.join(",")}`); - for (const pending of this.pending.values()) pending.reject(reject_error); - this.pending.clear(); - } - _onRecv(msg: CdpResponseMessage | CdpEventMessage) { if ("id" in msg && typeof msg.id === "number") { - const response = CdpResponseMessageSchema.parse(msg); - const pending = this.pending.get(response.id); - if (!pending) return; - this.pending.delete(response.id); - if (response.error) { - const err = new Error(`${pending.method} failed: ${response.error.message}`) as Error & { cdp?: CdpError }; - err.cdp = response.error; - pending.reject(err); - } else { - pending.resolve((response.result === undefined ? {} : response.result) as ProtocolResult); - } return; } - const event = CdpEventMessageSchema.parse(msg); - if (event.sessionId === this.ext_session_id) { - if (event.method === "Runtime.executionContextCreated") { - this.auto_sessions.recordProtocolEvent(event.method, event.params || {}, event.sessionId || null); - } - if (event.method !== "Runtime.bindingCalled") return; - const u = unwrapEventIfNeeded( - event.method, - (event.params || {}) as RuntimeBindingCalledEvent, - event.sessionId || null, - this.ext_session_id, - ); + if (!("method" in msg) || typeof msg.method !== "string") return; + const method = msg.method; + const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : null; + const event = msg; + const eventParams = (event.params || {}) as ProtocolPayload; + const extension_session_id = this.injector?.session_id ?? null; + if (extension_session_id != null && sessionId === extension_session_id) { + if (method !== Runtime.BindingCalledEvent.id) return; + const u = unwrapEventIfNeeded(method, eventParams as RuntimeBindingCalledEvent, sessionId, extension_session_id); if (u) { - const payload = this._parseEventPayload(u.event, u.data); - this.auto_sessions.recordProtocolEvent(u.event, payload as ProtocolPayload, u.sessionId); + const payload = this.types.parseEventPayload(u.event, u.data); this.emit(u.event, payload, u.sessionId); } return; } - if (event.method) { - const data = event.params || {}; - const payload = this._parseEventPayload(event.method, data); - this.auto_sessions.recordProtocolEvent(event.method, payload as ProtocolPayload, event.sessionId || null); - this.emit(event.method, payload, event.sessionId || null); + if (method) { + const payload = this.types.parseEventPayload(method, eventParams); + this.emit(method, payload, sessionId); } } +} - get auto_target_sessions() { - return this.auto_sessions.target_sessions; - } - - get auto_session_targets() { - return this.auto_sessions.session_targets; - } - - get runtime_execution_contexts() { - return this.auto_sessions.execution_contexts; - } +export interface ModCDPClient extends Omit< + CdpAliases, + "types" +> { + Custom: CdpCommandAliases extends { Custom: infer TCustom } ? TCustom : never; } -export interface ModCDPClient extends CdpAliases {} +export { upstream_transport_constructors, browser_launcher_constructors, extension_injector_constructors }; +export type { ModCDPClientConfig }; +export type { CdpAliases } from "../types/generated/aliases.js"; diff --git a/js/src/index.ts b/js/src/index.ts index 2ea4a624..9853eb8f 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -1,56 +1,99 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/__init__.py +// - ./go/modcdp/modcdp.go +import { browser_launcher_constructors, extension_injector_constructors } from "./client/ModCDPClient.js"; +import { BBBrowserLauncher } from "./launcher/BBBrowserLauncher.js"; +import { LocalBrowserLauncher } from "./launcher/LocalBrowserLauncher.js"; +import { RemoteBrowserLauncher } from "./launcher/RemoteBrowserLauncher.js"; +import { BBExtensionInjector } from "./injector/BBExtensionInjector.js"; +import { CDPExtensionInjector } from "./injector/CDPExtensionInjector.js"; +import { CLIExtensionInjector } from "./injector/CLIExtensionInjector.js"; +import { DiscoverExtensionInjector } from "./injector/DiscoverExtensionInjector.js"; + +browser_launcher_constructors.set("local", LocalBrowserLauncher); +browser_launcher_constructors.set("remote", RemoteBrowserLauncher); +browser_launcher_constructors.set("bb", BBBrowserLauncher); +extension_injector_constructors.set("cli", CLIExtensionInjector); +extension_injector_constructors.set("cdp", CDPExtensionInjector); +extension_injector_constructors.set("bb", BBExtensionInjector); +extension_injector_constructors.set("discover", DiscoverExtensionInjector); + export * from "./client/ModCDPClient.js"; export { ModCDPServer } from "./server/ModCDPServer.js"; export { BrowserLauncher, resolveCdpWebSocketUrl } from "./launcher/BrowserLauncher.js"; -export type { BrowserLaunchOptions, LaunchedBrowser } from "./launcher/BrowserLauncher.js"; +export type { LauncherConfig, LaunchedBrowser, LauncherMode } from "./launcher/BrowserLauncher.js"; export { LocalBrowserLauncher } from "./launcher/LocalBrowserLauncher.js"; export { RemoteBrowserLauncher } from "./launcher/RemoteBrowserLauncher.js"; -export { BrowserbaseBrowserLauncher } from "./launcher/BrowserbaseBrowserLauncher.js"; -export { NoopBrowserLauncher } from "./launcher/NoopBrowserLauncher.js"; +export { BBBrowserLauncher } from "./launcher/BBBrowserLauncher.js"; +export { NoneBrowserLauncher } from "./launcher/NoneBrowserLauncher.js"; export { DEFAULT_MODCDP_EXTENSION_ID, DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES, ExtensionInjector, - defaultModCDPExtensionPath, } from "./injector/ExtensionInjector.js"; +export { + defaultModCDPExtensionPath, + extensionIdFromManifestKey, + prepareUnpackedExtension, +} from "./injector/NodeExtensionFiles.js"; +export type { PreparedExtension } from "./injector/NodeExtensionFiles.js"; export type { ExtensionInjectionResult, - ExtensionInjectorConfig, + InjectorConfig, + InjectorMode, SendCDP, TargetInfo, } from "./injector/ExtensionInjector.js"; -export { LocalBrowserLaunchExtensionInjector } from "./injector/LocalBrowserLaunchExtensionInjector.js"; -export { ExtensionsLoadUnpackedInjector } from "./injector/ExtensionsLoadUnpackedInjector.js"; -export { DiscoveredExtensionInjector } from "./injector/DiscoveredExtensionInjector.js"; -export { BorrowedExtensionInjector } from "./injector/BorrowedExtensionInjector.js"; -export { BBBrowserExtensionInjector } from "./injector/BBBrowserExtensionInjector.js"; -export { UpstreamTransport, endpointKindForUpstream, parseHostPort } from "./transport/UpstreamTransport.js"; -export type { UpstreamEndpointKind, UpstreamMode, UpstreamTransportConfig } from "./transport/UpstreamTransport.js"; -export { WebSocketUpstreamTransport } from "./transport/WebSocketUpstreamTransport.js"; -export { ReverseWebSocketUpstreamTransport } from "./transport/ReverseWebSocketUpstreamTransport.js"; -export { NativeMessagingUpstreamTransport } from "./transport/NativeMessagingUpstreamTransport.js"; -export { NatsUpstreamTransport } from "./transport/NatsUpstreamTransport.js"; -export { PipeUpstreamTransport } from "./transport/PipeUpstreamTransport.js"; +export { CLIExtensionInjector } from "./injector/CLIExtensionInjector.js"; +export { CDPExtensionInjector } from "./injector/CDPExtensionInjector.js"; +export { DiscoverExtensionInjector } from "./injector/DiscoverExtensionInjector.js"; +export { BBExtensionInjector } from "./injector/BBExtensionInjector.js"; +export { UpstreamTransport, parseHostPort } from "./transport/UpstreamTransport.js"; +export type { UpstreamMode, UpstreamTransportConfig } from "./transport/UpstreamTransport.js"; +export { DownstreamTransport } from "./transport/DownstreamTransport.js"; +export { DownstreamTransportSet } from "./transport/DownstreamTransportSet.js"; +export type { + DownstreamRequestHandler, + DownstreamTransportName, + DownstreamTransportStatus, +} from "./transport/DownstreamTransport.js"; +export type { TargetRoute, UpstreamEventListener } from "./transport/UpstreamTransport.js"; +export { WSUpstreamTransport } from "./transport/WSUpstreamTransport.js"; +export { ReverseWSDownstreamTransport } from "./transport/ReverseWSDownstreamTransport.js"; +export { NativeMessagingDownstreamTransport } from "./transport/NativeMessagingDownstreamTransport.js"; +export { NATSDownstreamTransport } from "./transport/NATSDownstreamTransport.js"; export { AutoSessionRouter } from "./router/AutoSessionRouter.js"; +export { CDPTypes } from "./types/CDPTypes.js"; +export type { + CDPCommandAliases, + CDPCommandMap, + CDPCommandSpec, + CDPEventMap, + CDPEventSpec, + CDPTypesConfig, +} from "./types/CDPTypes.js"; export { wrapCommandIfNeeded, unwrapResponseIfNeeded, unwrapEventIfNeeded } from "./translate/translate.js"; export * as server from "./server/ModCDPServer.js"; export * as launcher from "./launcher/BrowserLauncher.js"; export * as localBrowserLauncher from "./launcher/LocalBrowserLauncher.js"; export * as remoteBrowserLauncher from "./launcher/RemoteBrowserLauncher.js"; -export * as browserbaseBrowserLauncher from "./launcher/BrowserbaseBrowserLauncher.js"; -export * as noopBrowserLauncher from "./launcher/NoopBrowserLauncher.js"; +export * as bbBrowserLauncher from "./launcher/BBBrowserLauncher.js"; +export * as noneBrowserLauncher from "./launcher/NoneBrowserLauncher.js"; export * as injector from "./injector/ExtensionInjector.js"; -export * as localBrowserLaunchExtensionInjector from "./injector/LocalBrowserLaunchExtensionInjector.js"; -export * as extensionsLoadUnpackedInjector from "./injector/ExtensionsLoadUnpackedInjector.js"; -export * as discoveredExtensionInjector from "./injector/DiscoveredExtensionInjector.js"; -export * as borrowedExtensionInjector from "./injector/BorrowedExtensionInjector.js"; -export * as bbBrowserExtensionInjector from "./injector/BBBrowserExtensionInjector.js"; +export * as cliExtensionInjector from "./injector/CLIExtensionInjector.js"; +export * as cdpExtensionInjector from "./injector/CDPExtensionInjector.js"; +export * as discoverExtensionInjector from "./injector/DiscoverExtensionInjector.js"; +export * as bbExtensionInjector from "./injector/BBExtensionInjector.js"; export * as upstreamTransport from "./transport/UpstreamTransport.js"; -export * as webSocketUpstreamTransport from "./transport/WebSocketUpstreamTransport.js"; -export * as reverseWebSocketUpstreamTransport from "./transport/ReverseWebSocketUpstreamTransport.js"; -export * as nativeMessagingUpstreamTransport from "./transport/NativeMessagingUpstreamTransport.js"; -export * as natsUpstreamTransport from "./transport/NatsUpstreamTransport.js"; -export * as pipeUpstreamTransport from "./transport/PipeUpstreamTransport.js"; +export * as downstreamTransport from "./transport/DownstreamTransport.js"; +export * as downstreamTransportSet from "./transport/DownstreamTransportSet.js"; +export * as wsUpstreamTransport from "./transport/WSUpstreamTransport.js"; +export * as reverseWSDownstreamTransport from "./transport/ReverseWSDownstreamTransport.js"; +export * as nativeMessagingDownstreamTransport from "./transport/NativeMessagingDownstreamTransport.js"; +export * as natsDownstreamTransport from "./transport/NATSDownstreamTransport.js"; export * as router from "./router/AutoSessionRouter.js"; export * as translate from "./translate/translate.js"; export * as proxy from "./proxy/proxy.js"; export * as types from "./types/modcdp.js"; +export * as cdpTypes from "./types/CDPTypes.js"; diff --git a/js/src/injector/BBBrowserExtensionInjector.ts b/js/src/injector/BBBrowserExtensionInjector.ts deleted file mode 100644 index b523813f..00000000 --- a/js/src/injector/BBBrowserExtensionInjector.ts +++ /dev/null @@ -1,109 +0,0 @@ -import type { BrowserLaunchOptions } from "../launcher/BrowserLauncher.js"; -import { ExtensionInjector } from "./ExtensionInjector.js"; - -const DEFAULT_BROWSERBASE_BASE_URL = "https://api.browserbase.com"; - -function firstString(...values: unknown[]) { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value.trim(); - } - return null; -} - -export class BBBrowserExtensionInjector extends ExtensionInjector { - private extension_id: string | null = null; - private zip_path: string | null = null; - private cleanup: (() => Promise) | null = null; - - async prepare() { - const configured_extension_id = firstString(this.options.injector_extension_id); - if (configured_extension_id) { - this.extension_id = configured_extension_id; - return; - } - if (this.extension_id) return; - const extension_path = this.options.injector_extension_path; - if (!extension_path) return; - this.zip_path = extension_path.endsWith(".zip") ? extension_path : await this.zipExtensionDir(extension_path); - try { - this.extension_id = await this.uploadExtension(this.zip_path); - } catch (error) { - await this.close(); - throw error; - } - } - - getLauncherConfig(): BrowserLaunchOptions { - if (!this.extension_id) return {}; - return { injector_extension_id: this.extension_id }; - } - - async inject() { - const extension_id = this.options.injector_extension_id; - this.options.injector_extension_id = null; - try { - const discovered = await this.waitForReadyServiceWorker( - this.options.injector_service_worker_ready_timeout_ms ?? 60_000, - { - matched_only: this.options.injector_trust_service_worker_target, - }, - ); - return discovered ? { ...discovered, source: "bb" } : null; - } finally { - this.options.injector_extension_id = extension_id; - } - } - - async close() { - await this.cleanup?.(); - this.cleanup = null; - } - - private async zipExtensionDir(extension_path: string) { - const [{ execFileSync }, fs, os, path] = await Promise.all([ - import("node:child_process"), - import("node:fs"), - import("node:os"), - import("node:path"), - ]); - const zip_path = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "modcdp-bb-extension-")), "extension.zip"); - execFileSync("zip", ["-X", "-qr", zip_path, "."], { cwd: extension_path }); - this.cleanup = async () => fs.rmSync(path.dirname(zip_path), { recursive: true, force: true }); - return zip_path; - } - - private async uploadExtension(zip_path: string) { - const browserbase_api_key = firstString(this.options.injector_browserbase_api_key, process.env.BROWSERBASE_API_KEY); - if (!browserbase_api_key) { - throw new Error( - "BBBrowserExtensionInjector requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key.", - ); - } - const base_url = - firstString(this.options.injector_browserbase_base_url, process.env.BROWSERBASE_BASE_URL) ?? - DEFAULT_BROWSERBASE_BASE_URL; - const fs = await import("node:fs"); - const path = await import("node:path"); - const form = new FormData(); - const zip_bytes = fs.readFileSync(zip_path); - const zip_array_buffer = zip_bytes.buffer.slice( - zip_bytes.byteOffset, - zip_bytes.byteOffset + zip_bytes.byteLength, - ) as ArrayBuffer; - form.append("file", new Blob([zip_array_buffer]), path.basename(zip_path)); - const response = await fetch(new URL("/v1/extensions", `${base_url.replace(/\/$/, "")}/`), { - method: "POST", - headers: { "X-BB-API-Key": browserbase_api_key }, - body: form, - }); - if (!response.ok) { - const text = await response.text().catch(() => ""); - throw new Error(`Browserbase POST /v1/extensions -> ${response.status}${text ? `: ${text}` : ""}`); - } - const extension = (await response.json()) as Record; - if (typeof extension.id !== "string" || !extension.id) { - throw new Error(`Browserbase extension upload returned no id (got ${JSON.stringify(extension)})`); - } - return extension.id; - } -} diff --git a/js/src/injector/BBExtensionInjector.ts b/js/src/injector/BBExtensionInjector.ts new file mode 100644 index 00000000..41dda64b --- /dev/null +++ b/js/src/injector/BBExtensionInjector.ts @@ -0,0 +1,94 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/BBExtensionInjector.py +// - ./go/modcdp/injector/BBExtensionInjector.go +import { ExtensionInjector, InjectorConfigSchema } from "./ExtensionInjector.js"; +import type { z } from "zod"; +import type { LauncherConfig } from "../launcher/BrowserLauncher.js"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; + +class BBExtensionInjector extends ExtensionInjector { + private zip_path: string | null = null; + private cleanup: (() => Promise) | null = null; + + constructor(config: z.input = {}) { + super({ ...config, injector_mode: "bb" }); + } + + async prepare() { + if (this.config.injector_bb_extension_id) { + this.extension_id = this.config.injector_bb_extension_id; + return; + } + if (this.extension_id) return; + const extension_path = this.config.injector_bb_extension_path; + if (!extension_path) return; + this.zip_path = extension_path.endsWith(".zip") ? extension_path : await this.zipExtensionDir(extension_path); + try { + this.extension_id = await this.uploadExtension(this.zip_path); + } catch (error) { + await this.close(); + throw error; + } + } + + async inject() { + const discovered = await this.waitForReadyServiceWorker(this.config.injector_service_worker_ready_timeout_ms, { + matched_only: this.config.injector_trust_service_worker_target, + }); + return discovered ? { ...discovered, source: "bb" } : null; + } + + override configForLauncher(): LauncherConfig { + return { + ...super.configForLauncher(), + launcher_bb_extension_id: this.extension_id ?? this.config.injector_bb_extension_id, + }; + } + + async close() { + await this.cleanup?.(); + this.cleanup = null; + } + + private async zipExtensionDir(extension_path: string) { + const zip_path = path.join(mkdtempSync(path.join(tmpdir(), "modcdp-bb-extension-")), "extension.zip"); + execFileSync("zip", ["-X", "-qr", zip_path, "."], { cwd: extension_path }); + this.cleanup = async () => rmSync(path.dirname(zip_path), { recursive: true, force: true }); + return zip_path; + } + + private async uploadExtension(zip_path: string) { + const browserbase_api_key = this.config.injector_bb_api_key ?? process.env.BROWSERBASE_API_KEY; + if (!browserbase_api_key) { + throw new Error("BBExtensionInjector requires BROWSERBASE_API_KEY or injector.injector_bb_api_key."); + } + const base_url = this.config.injector_bb_base_url; + const form = new FormData(); + const zip_bytes = readFileSync(zip_path); + const zip_array_buffer = zip_bytes.buffer.slice( + zip_bytes.byteOffset, + zip_bytes.byteOffset + zip_bytes.byteLength, + ) as ArrayBuffer; + form.append("file", new Blob([zip_array_buffer]), path.basename(zip_path)); + const response = await fetch(new URL("/v1/extensions", `${base_url.replace(/\/$/, "")}/`), { + method: "POST", + headers: { "X-BB-API-Key": browserbase_api_key }, + body: form, + }); + if (!response.ok) { + const text = await response.text().catch(() => ""); + throw new Error(`Browserbase POST /v1/extensions -> ${response.status}${text ? `: ${text}` : ""}`); + } + const extension = (await response.json()) as Record; + if (typeof extension.id !== "string" || !extension.id) { + throw new Error(`Browserbase extension upload returned no id (got ${JSON.stringify(extension)})`); + } + return extension.id; + } +} + +export { BBExtensionInjector }; diff --git a/js/src/injector/BorrowExtensionInjector.ts b/js/src/injector/BorrowExtensionInjector.ts new file mode 100644 index 00000000..89c394ec --- /dev/null +++ b/js/src/injector/BorrowExtensionInjector.ts @@ -0,0 +1,223 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand. +import fs from "node:fs"; +import path from "node:path"; +import * as Runtime from "../types/generated/zod/Runtime.js"; +import * as Target from "../types/generated/zod/Target.js"; +import { defaultModCDPExtensionPath, prepareUnpackedExtension } from "./NodeExtensionFiles.js"; +import { + ExtensionInjector, + InjectorConfigSchema, + type ExtensionInjectionResult, + type TargetInfo, +} from "./ExtensionInjector.js"; +import { z } from "zod"; + +const EXT_ID_FROM_URL = /^chrome-extension:\/\/([a-z]+)\//; +const MODCDP_READY_EXPRESSION = "Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)"; +const BORROW_BOOTSTRAP_STATUS_EXPRESSION = ` + (() => ({ + ok: Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent), + extension_id: globalThis.chrome?.runtime?.id ?? null, + has_tabs: Boolean(globalThis.chrome?.tabs?.query), + has_debugger: Boolean(globalThis.chrome?.debugger?.sendCommand && globalThis.chrome?.debugger?.getTargets), + }))() +`; + +const BorrowInjectorConfigSchema = InjectorConfigSchema.extend({ + injector_mode: z.literal("borrow").default("borrow"), + injector_borrow_extension_path: z.string().optional(), +}).strict(); +type BorrowInjectorConfig = z.infer; + +class BorrowExtensionInjector extends ExtensionInjector { + declare config: BorrowInjectorConfig; + private unpacked_extension_path: string | null = null; + private cleanup: (() => Promise) | null = null; + private bootstrap_modcdp_server_expression: string | null = null; + + constructor(config: z.input = {}) { + super(); + this.config = BorrowInjectorConfigSchema.parse({ ...config, injector_mode: "borrow" }); + } + + override update(config: Record = {}) { + this.config = BorrowInjectorConfigSchema.parse({ ...this.config, ...config, injector_mode: "borrow" }); + return this; + } + + async prepare() { + if (this.bootstrap_modcdp_server_expression) { + await super.prepare(); + return; + } + const extension_path = this.config.injector_borrow_extension_path ?? defaultModCDPExtensionPath(); + const prepared = await prepareUnpackedExtension(extension_path); + this.unpacked_extension_path = prepared.unpacked_extension_path; + this.cleanup = prepared.cleanup; + const service_worker_path = path.join(this.unpacked_extension_path, "modcdp", "service_worker.js"); + let service_worker_source: string; + try { + service_worker_source = fs.readFileSync(service_worker_path, "utf8"); + } catch (error) { + await this.cleanup(); + this.cleanup = null; + this.unpacked_extension_path = null; + throw error; + } + this.bootstrap_modcdp_server_expression = ` + async function() { + if (!globalThis.ModCDP) { + ${service_worker_source} + } + const ModCDP = globalThis.ModCDP; + return { + ok: Boolean(ModCDP?.handleCommand && ModCDP?.addCustomEvent), + extension_id: globalThis.chrome?.runtime?.id ?? null, + has_tabs: Boolean(globalThis.chrome?.tabs?.query), + has_debugger: Boolean(globalThis.chrome?.debugger?.sendCommand && globalThis.chrome?.debugger?.getTargets), + }; + } + `; + await super.prepare(); + } + + async inject() { + const deadline = Date.now() + this.config.injector_service_worker_ready_timeout_ms; + do { + const borrowed = await this.borrowVisibleServiceWorkers(); + if (borrowed) return borrowed; + await new Promise((resolve) => setTimeout(resolve, this.config.injector_service_worker_poll_interval_ms)); + } while (Date.now() < deadline); + return null; + } + + private async borrowVisibleServiceWorkers() { + const borrowed: Array<{ + result: ExtensionInjectionResult; + has_tabs: boolean; + has_debugger: boolean; + }> = []; + const visible_service_workers = (await this.targetInfos()).filter((target) => { + const target_url = target.url ?? ""; + return target.type === "service_worker" && target_url.startsWith("chrome-extension://"); + }); + const has_configured_matcher = + Boolean(this.config.injector_service_worker_extension_id) || + this.config.injector_service_worker_url_includes.length > 0 || + this.config.injector_service_worker_url_suffixes.length > 0; + const candidates = has_configured_matcher + ? visible_service_workers.filter((target) => this.serviceWorkerTargetMatches(target)) + : visible_service_workers; + for (const target of candidates) { + try { + const bootstrapped = await this.bootstrapTarget(target as TargetInfo); + if (bootstrapped) borrowed.push(bootstrapped); + } catch {} + } + borrowed.sort((a, b) => Number(b.has_debugger) - Number(a.has_debugger) || Number(b.has_tabs) - Number(a.has_tabs)); + return borrowed[0]?.result ?? null; + } + + private async bootstrapTarget(target: TargetInfo): Promise<{ + result: ExtensionInjectionResult; + has_tabs: boolean; + has_debugger: boolean; + } | null> { + const attach_result = await this.config.send(Target.AttachToTargetCommand, { + targetId: target.targetId, + flatten: true, + }); + const session_id = attach_result.sessionId; + try { + await this.config.send(Runtime.EnableCommand, {}, session_id).catch(() => {}); + const status = await this.config.send( + Runtime.EvaluateCommand, + { + expression: BORROW_BOOTSTRAP_STATUS_EXPRESSION, + returnByValue: true, + }, + session_id, + ); + let value = status.result?.value || {}; + if (!value.has_tabs || !value.has_debugger) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + return null; + } + if (!value.ok) { + if (!this.bootstrap_modcdp_server_expression) { + throw new Error("BorrowExtensionInjector requires prepare before inject."); + } + const bootstrap = await this.config.send( + Runtime.EvaluateCommand, + { + expression: `(${this.bootstrap_modcdp_server_expression})()`, + awaitPromise: true, + returnByValue: true, + }, + session_id, + ); + value = bootstrap.result?.value || {}; + } + if (!value.has_tabs || !value.has_debugger) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + return null; + } + let ready = Boolean(value.ok); + if (ready && this.readyExpression() !== MODCDP_READY_EXPRESSION) { + const probe = await this.config.send( + Runtime.EvaluateCommand, + { + expression: this.readyExpression(), + returnByValue: true, + }, + session_id, + ); + ready = probe.result?.value === true; + } + if (!ready) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + return null; + } + return { + result: { + source: "borrow", + extension_id: value.extension_id || target.url?.match(EXT_ID_FROM_URL)?.[1] || null, + target_id: target.targetId, + url: target.url, + session_id, + }, + has_tabs: Boolean(value.has_tabs), + has_debugger: Boolean(value.has_debugger), + }; + } catch (error) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + throw error; + } + } + + async close() { + await super.close(); + await this.cleanup?.(); + this.cleanup = null; + } +} + +export { BorrowExtensionInjector, BorrowInjectorConfigSchema }; +export type { BorrowInjectorConfig }; diff --git a/js/src/injector/BorrowedExtensionInjector.ts b/js/src/injector/BorrowedExtensionInjector.ts deleted file mode 100644 index 48f5cca3..00000000 --- a/js/src/injector/BorrowedExtensionInjector.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { installModCDPServer } from "../server/ModCDPServer.js"; -import { commands as RuntimeCommands } from "../types/generated/zod/Runtime.js"; -import { ExtensionInjector, type ExtensionInjectionResult, type TargetInfo } from "./ExtensionInjector.js"; - -const EXT_ID_FROM_URL = /^chrome-extension:\/\/([a-z]+)\//; -const MODCDP_READY_EXPRESSION = - "Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)"; -const bootstrap_modcdp_server_expression = ` - function() { - const __name = (fn) => fn; - const installModCDPServer = ${installModCDPServer.toString()}; - const ModCDP = installModCDPServer(globalThis); - return { - ok: Boolean(ModCDP?.__ModCDPServerVersion >= 1 && ModCDP?.handleCommand && ModCDP?.addCustomEvent), - extension_id: globalThis.chrome?.runtime?.id ?? null, - has_tabs: Boolean(globalThis.chrome?.tabs?.query), - has_debugger: Boolean(globalThis.chrome?.debugger?.sendCommand && globalThis.chrome?.debugger?.getTargets), - }; - } -`; - -export class BorrowedExtensionInjector extends ExtensionInjector { - async inject() { - const deadline = Date.now() + (this.options.injector_service_worker_ready_timeout_ms ?? 60_000); - do { - const borrowed = await this.borrowVisibleServiceWorkers(); - if (borrowed) return borrowed; - await new Promise((resolve) => setTimeout(resolve, this.options.injector_service_worker_poll_interval_ms ?? 100)); - } while (Date.now() < deadline); - return null; - } - - private async borrowVisibleServiceWorkers() { - const borrowed: ExtensionInjectionResult[] = []; - const visible_service_workers = (await this.targetInfos()).filter((target) => { - const target_url = target.url ?? ""; - return target.type === "service_worker" && target_url.startsWith("chrome-extension://"); - }); - const has_configured_matcher = - Boolean(this.options.injector_extension_id) || - (this.options.injector_service_worker_url_includes?.length ?? 0) > 0 || - (this.options.injector_service_worker_url_suffixes?.length ?? 0) > 0; - const candidates = has_configured_matcher - ? visible_service_workers.filter((target) => this.serviceWorkerTargetMatches(target)) - : visible_service_workers; - for (const target of candidates) { - try { - const bootstrapped = await this.bootstrapTarget(target as TargetInfo); - if (bootstrapped) borrowed.push({ ...bootstrapped, source: "borrowed" }); - } catch {} - } - borrowed.sort((a, b) => Number(b.has_debugger) - Number(a.has_debugger) || Number(b.has_tabs) - Number(a.has_tabs)); - return borrowed[0] ?? null; - } - - private async bootstrapTarget(target: TargetInfo): Promise { - const session_id = await this.ensureSessionIdForTarget( - target.targetId, - this.options.injector_service_worker_probe_timeout_ms, - true, - ); - if (session_id == null) return null; - await this.sendWithTimeout("Runtime.enable", {}, session_id).catch(() => {}); - const bootstrap = RuntimeCommands["Runtime.evaluate"].result.parse( - await this.sendWithTimeout( - "Runtime.evaluate", - { - expression: `(${bootstrap_modcdp_server_expression})()`, - awaitPromise: true, - returnByValue: true, - }, - session_id, - ), - ); - const value = bootstrap.result?.value || {}; - if (!value.has_tabs || !value.has_debugger) return null; - let ready = Boolean(value.ok); - if (ready && this.readyExpression() !== MODCDP_READY_EXPRESSION) { - const probe = RuntimeCommands["Runtime.evaluate"].result.parse( - await this.sendWithTimeout( - "Runtime.evaluate", - { - expression: this.readyExpression(), - returnByValue: true, - }, - session_id, - ), - ); - ready = probe.result?.value === true; - } - if (!ready) return null; - return { - source: "borrowed", - extension_id: value.extension_id || target.url?.match(EXT_ID_FROM_URL)?.[1] || null, - target_id: target.targetId, - url: target.url, - session_id, - has_tabs: Boolean(value.has_tabs), - has_debugger: Boolean(value.has_debugger), - }; - } -} diff --git a/js/src/injector/ExtensionsLoadUnpackedInjector.ts b/js/src/injector/CDPExtensionInjector.ts similarity index 58% rename from js/src/injector/ExtensionsLoadUnpackedInjector.ts rename to js/src/injector/CDPExtensionInjector.ts index 43043c0d..3a315e28 100644 --- a/js/src/injector/ExtensionsLoadUnpackedInjector.ts +++ b/js/src/injector/CDPExtensionInjector.ts @@ -1,16 +1,22 @@ -import { - defaultModCDPExtensionPath, - ExtensionInjector, - prepareUnpackedExtension, - type TargetInfo, -} from "./ExtensionInjector.js"; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/CDPExtensionInjector.py +// - ./go/modcdp/injector/CDPExtensionInjector.go +import { ExtensionInjector, InjectorConfigSchema, type TargetInfo } from "./ExtensionInjector.js"; +import type { z } from "zod"; +import { defaultModCDPExtensionPath, prepareUnpackedExtension } from "./NodeExtensionFiles.js"; +import * as Extensions from "../types/generated/zod/Extensions.js"; -export class ExtensionsLoadUnpackedInjector extends ExtensionInjector { +class CDPExtensionInjector extends ExtensionInjector { private unpacked_extension_path: string | null = null; private cleanup: (() => Promise) | null = null; + constructor(config: z.input = {}) { + super({ ...config, injector_mode: "cdp" }); + } + async prepare() { - const extension_path = this.options.injector_extension_path ?? defaultModCDPExtensionPath(); + const extension_path = this.config.injector_cdp_extension_path ?? defaultModCDPExtensionPath(); if (this.unpacked_extension_path) { await super.prepare(); return; @@ -24,15 +30,14 @@ export class ExtensionsLoadUnpackedInjector extends ExtensionInjector { async inject() { const extension_path = this.unpacked_extension_path; if (!extension_path) return null; - let load_result: Record; + let load_result; try { - load_result = (await this.send("Extensions.loadUnpacked", { + load_result = await this.config.send(Extensions.LoadUnpackedCommand, { path: extension_path, - })) as Record; + }); } catch (error) { const load_error = error instanceof Error ? error : new Error(String(error)); if (/Method not available|Method.*not.*found|wasn't found/i.test(load_error.message)) { - this.last_error = load_error; return null; } throw new Error( @@ -41,31 +46,30 @@ export class ExtensionsLoadUnpackedInjector extends ExtensionInjector { ); } - const extension_id = load_result.id || load_result.extensionId; + const extension_id = load_result.id; if (typeof extension_id !== "string" || !extension_id) { throw new Error(`Extensions.loadUnpacked returned no extension id (got ${JSON.stringify(load_result)})`); } - this.options.injector_extension_id = extension_id; + this.extension_id = extension_id; + this.service_worker_extension_id = extension_id; const sw_url_prefix = `chrome-extension://${extension_id}/`; - const deadline = Date.now() + (this.options.injector_service_worker_ready_timeout_ms ?? 60_000); + const deadline = Date.now() + this.config.injector_service_worker_ready_timeout_ms; while (Date.now() < deadline) { const target_infos = await this.targetInfos(); const target = target_infos.find( (candidate) => candidate.type === "service_worker" && candidate.url.startsWith(sw_url_prefix), ) as TargetInfo | undefined; if (target) { - const probed = await this.probeTarget(target, this.options.injector_service_worker_probe_timeout_ms, { - allow_attach: true, - }); + const probed = await this.probeTarget(target); if (probed) return { ...probed, - source: "extensions_load_unpacked", + source: "cdp", extension_id, }; } - await new Promise((resolve) => setTimeout(resolve, this.options.injector_service_worker_poll_interval_ms ?? 100)); + await new Promise((resolve) => setTimeout(resolve, this.config.injector_service_worker_poll_interval_ms)); } throw new Error(`Timed out waiting for service worker target for extension ${extension_id}.`); } @@ -76,3 +80,5 @@ export class ExtensionsLoadUnpackedInjector extends ExtensionInjector { this.cleanup = null; } } + +export { CDPExtensionInjector }; diff --git a/js/src/injector/CLIExtensionInjector.ts b/js/src/injector/CLIExtensionInjector.ts new file mode 100644 index 00000000..80d1675c --- /dev/null +++ b/js/src/injector/CLIExtensionInjector.ts @@ -0,0 +1,65 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/CLIExtensionInjector.py +// - ./go/modcdp/injector/CLIExtensionInjector.go +import { ExtensionInjector, InjectorConfigSchema } from "./ExtensionInjector.js"; +import type { z } from "zod"; +import { + defaultModCDPExtensionPath, + extensionIdFromManifestKey, + prepareUnpackedExtension, +} from "./NodeExtensionFiles.js"; + +class CLIExtensionInjector extends ExtensionInjector { + private unpacked_extension_path: string | null = null; + private cleanup: (() => Promise) | null = null; + + constructor(config: z.input = {}) { + super({ ...config, injector_mode: "cli" }); + } + + async prepare() { + const extension_path = this.config.injector_cli_extension_path ?? defaultModCDPExtensionPath(); + if (this.unpacked_extension_path) { + await super.prepare(); + return; + } + const prepared = await prepareUnpackedExtension(extension_path); + this.unpacked_extension_path = prepared.unpacked_extension_path; + this.cleanup = prepared.cleanup; + await this.resolveExtensionId(); + await super.prepare(); + } + + async inject() { + const discovered = await this.waitForReadyServiceWorker(this.config.injector_service_worker_ready_timeout_ms, { + matched_only: this.config.injector_trust_service_worker_target, + }); + return discovered ? { ...discovered, source: "cli" } : null; + } + + async close() { + await super.close(); + await this.cleanup?.(); + this.cleanup = null; + } + + private async resolveExtensionId() { + if (this.extension_id) return this.extension_id; + this.extension_id = + typeof this.config.injector_cli_extension_id === "string" && this.config.injector_cli_extension_id.trim() + ? this.config.injector_cli_extension_id.trim() + : null; + if (!this.extension_id && this.unpacked_extension_path) { + this.extension_id = await extensionIdFromManifestKey(this.unpacked_extension_path); + } + if (this.extension_id) { + this.service_worker_extension_id = this.extension_id; + this.update({ injector_service_worker_extension_id: this.extension_id }); + } + if (this.unpacked_extension_path) this.extra_args = [`--load-extension=${this.unpacked_extension_path}`]; + return this.extension_id; + } +} + +export { CLIExtensionInjector }; diff --git a/js/src/injector/DiscoverExtensionInjector.ts b/js/src/injector/DiscoverExtensionInjector.ts new file mode 100644 index 00000000..a7903e80 --- /dev/null +++ b/js/src/injector/DiscoverExtensionInjector.ts @@ -0,0 +1,59 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/DiscoverExtensionInjector.py +// - ./go/modcdp/injector/DiscoverExtensionInjector.go +import { ExtensionInjector, InjectorConfigSchema } from "./ExtensionInjector.js"; +import type { z } from "zod"; +import { extensionIdFromManifestKey, prepareUnpackedExtension, type PreparedExtension } from "./NodeExtensionFiles.js"; + +class DiscoverExtensionInjector extends ExtensionInjector { + private prepared_extension: PreparedExtension | null = null; + + constructor(config: z.input = {}) { + super({ ...config, injector_mode: "discover" }); + } + + async prepare() { + const extension_path = this.config.injector_discover_extension_path; + if (!this.config.injector_service_worker_extension_id && extension_path) { + this.prepared_extension = extension_path.endsWith(".zip") ? await prepareUnpackedExtension(extension_path) : null; + this.service_worker_extension_id = await extensionIdFromManifestKey( + this.prepared_extension?.unpacked_extension_path ?? extension_path, + ); + } + await super.prepare(); + } + + async inject() { + const discovered = await this.discoverReadyServiceWorker(); + if (discovered) return { ...discovered, source: "discover" }; + if (this.config.injector_trust_service_worker_target) { + const waited = await this.waitForReadyServiceWorker(this.config.injector_service_worker_probe_timeout_ms, { + matched_only: true, + }); + if (waited) return { ...waited, source: "discover" }; + } + if (!this.config.injector_require_service_worker_target) return null; + const waited = await this.waitForReadyServiceWorker(this.config.injector_service_worker_ready_timeout_ms, { + matched_only: this.config.injector_trust_service_worker_target, + }); + if (waited) return { ...waited, source: "discover" }; + throw new Error( + `Required ModCDP service worker target was not visible ` + + `(${ + [ + ...this.config.injector_service_worker_url_includes, + ...this.config.injector_service_worker_url_suffixes, + ].join(", ") || "no matcher" + }).`, + ); + } + + async close() { + await super.close(); + await this.prepared_extension?.cleanup(); + this.prepared_extension = null; + } +} + +export { DiscoverExtensionInjector }; diff --git a/js/src/injector/DiscoveredExtensionInjector.ts b/js/src/injector/DiscoveredExtensionInjector.ts deleted file mode 100644 index 8d91f555..00000000 --- a/js/src/injector/DiscoveredExtensionInjector.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ExtensionInjector } from "./ExtensionInjector.js"; - -export class DiscoveredExtensionInjector extends ExtensionInjector { - async inject() { - const discovered = await this.discoverReadyServiceWorker(); - if (discovered) return { ...discovered, source: "discovered" }; - if (this.options.injector_trust_service_worker_target) { - const waited = await this.waitForReadyServiceWorker( - this.options.injector_service_worker_probe_timeout_ms ?? 10_000, - { - matched_only: true, - }, - ); - if (waited) return { ...waited, source: "discovered" }; - } - if (!this.options.injector_require_service_worker_target) return null; - const waited = await this.waitForReadyServiceWorker( - this.options.injector_service_worker_ready_timeout_ms ?? 60_000, - { - matched_only: this.options.injector_trust_service_worker_target, - }, - ); - if (waited) return { ...waited, source: "discovered" }; - throw new Error( - `Required ModCDP service worker target was not visible ` + - `(${ - [ - ...(this.options.injector_service_worker_url_includes ?? []), - ...(this.options.injector_service_worker_url_suffixes ?? []), - ].join(", ") || "no matcher" - }).`, - ); - } -} diff --git a/js/src/injector/ExtensionInjector.ts b/js/src/injector/ExtensionInjector.ts index 5acf3920..6ede62d1 100644 --- a/js/src/injector/ExtensionInjector.ts +++ b/js/src/injector/ExtensionInjector.ts @@ -1,199 +1,127 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import type { BrowserLaunchOptions } from "../launcher/BrowserLauncher.js"; -import type { UpstreamTransportConfig } from "../transport/UpstreamTransport.js"; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/ExtensionInjector.py +// - ./go/modcdp/injector/ExtensionInjector.go +import { z } from "zod"; +import type { LauncherConfig } from "../launcher/BrowserLauncher.js"; +import type { TargetRoute, UpstreamTransportConfig } from "../transport/UpstreamTransport.js"; +import type { cdp } from "../types/generated/cdp.js"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import * as Runtime from "../types/generated/zod/Runtime.js"; +import * as Target from "../types/generated/zod/Target.js"; import type { ProtocolParams, ProtocolResult } from "../types/modcdp.js"; -import { commands as RuntimeCommands } from "../types/generated/zod/Runtime.js"; -import { commands as TargetCommands } from "../types/generated/zod/Target.js"; +import { modCDPToJSON } from "../types/toJSON.js"; const EXT_ID_FROM_URL = /^chrome-extension:\/\/([a-z]+)\//; -export const DEFAULT_MODCDP_EXTENSION_ID = "mdedooklbnfejodmnhmkdpkaedafkehf"; -export const DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES = ["/modcdp/service_worker.js"]; -const MODCDP_READY_EXPRESSION = - "Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)"; -export const DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000; -export const DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; -export const DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS = 10_000; -export const DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS = 60_000; -export const DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS = 100; -export const DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS = 20; +const DEFAULT_MODCDP_EXTENSION_ID = "mdedooklbnfejodmnhmkdpkaedafkehf"; +const DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES = ["/modcdp/service_worker.js"]; +const MODCDP_READY_EXPRESSION = "Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)"; +const DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000; +const DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; +const DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS = 10_000; +const DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS = 60_000; +const DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS = 100; +const DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS = 20; +const DEFAULT_BROWSERBASE_BASE_URL = "https://api.browserbase.com"; -export type SendCDP = (method: string, params?: ProtocolParams, session_id?: string | null) => Promise; -export type TargetInfo = { targetId: string; type?: string; url?: string }; +interface SendCDP { + (method: string, params?: ProtocolParams, session_id?: string | null): Promise; + < + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | cdp.types.ts.Target.SessionID | null, + ): Promise>; +} +type TargetInfo = { targetId: string; type?: string; url?: string }; + +const InjectorModeSchema = z.enum(["cli", "cdp", "bb", "discover", "none"]); +type InjectorMode = z.infer; -export type ExtensionInjectorConfig = { - send?: SendCDP | null; - sessionIdForTarget?: ((target_id: string) => string | null | undefined) | null; - attachToTarget?: ((target_id: string) => Promise) | null; - waitForExecutionContext?: ((session_id: string, timeout_ms: number) => Promise) | null; - injector_extension_path?: string | null; - injector_extension_id?: string | null; - injector_service_worker_url_includes?: string[]; - injector_service_worker_url_suffixes?: string[]; - injector_trust_service_worker_target?: boolean; - injector_require_service_worker_target?: boolean; - injector_service_worker_ready_expression?: string | null; - injector_cdp_send_timeout_ms?: number; - injector_execution_context_timeout_ms?: number; - injector_service_worker_probe_timeout_ms?: number; - injector_service_worker_ready_timeout_ms?: number; - injector_service_worker_poll_interval_ms?: number; - injector_target_session_poll_interval_ms?: number; - injector_browserbase_api_key?: string | null; - injector_browserbase_base_url?: string | null; - upstream_nativemessaging_host_name?: string | null; - upstream_nats_url?: string | null; - upstream_nats_subject_prefix?: string | null; +const DefaultSendCDP: SendCDP = async () => { + throw new Error("ExtensionInjector requires a CDP send function."); }; -export type ExtensionInjectionResult = { +const InjectorConfigSchema = z + .object({ + injector_mode: InjectorModeSchema.default("none"), + send: z.custom((value) => typeof value === "function").default(() => DefaultSendCDP), + injector_cli_extension_path: z.string().optional(), + injector_cli_extension_id: z.string().optional(), + injector_cdp_extension_path: z.string().optional(), + injector_cdp_extension_id: z.string().optional(), + injector_bb_extension_path: z.string().optional(), + injector_bb_extension_id: z.string().optional(), + injector_discover_extension_path: z.string().optional(), + injector_service_worker_extension_id: z.string().nullable().optional(), + injector_service_worker_url_includes: z.array(z.string()).default([]), + injector_service_worker_url_suffixes: z.array(z.string()).default(DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES), + injector_trust_service_worker_target: z.boolean().default(false), + injector_require_service_worker_target: z.boolean().default(false), + injector_service_worker_ready_expression: z.string().default(MODCDP_READY_EXPRESSION), + injector_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CDP_SEND_TIMEOUT_MS), + injector_execution_context_timeout_ms: z.number().positive().default(DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS), + injector_service_worker_probe_timeout_ms: z.number().positive().default(DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS), + injector_service_worker_ready_timeout_ms: z.number().positive().default(DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS), + injector_service_worker_poll_interval_ms: z.number().positive().default(DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS), + injector_target_session_poll_interval_ms: z.number().positive().default(DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS), + injector_bb_api_key: z.string().optional(), + injector_bb_base_url: z.string().default(DEFAULT_BROWSERBASE_BASE_URL), + }) + .strict(); +type InjectorConfig = z.infer; +type InjectorBaseConfig = Omit & { injector_mode: string }; + +type ExtensionInjectionResult = { source: string; extension_id?: string | null; target_id: string; url?: string; session_id: string; - has_tabs?: boolean; - has_debugger?: boolean; -}; - -export type PreparedExtension = { - unpacked_extension_path: string; - cleanup: () => Promise; }; function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } -export function defaultModCDPExtensionPath() { - if (typeof process === "object" && process?.versions?.node && import.meta.url.startsWith("file:")) { - const relative_path = import.meta.url.includes("/dist/js/src/") - ? "../../../../dist/extension.zip" - : "../../../dist/extension.zip"; - return decodeURIComponent(new URL(/* @vite-ignore */ relative_path, import.meta.url).pathname); - } - return "../../../dist/extension.zip"; -} - -function firstString(...values: unknown[]) { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value.trim(); - } - return null; -} - -export async function prepareUnpackedExtension(extension_path: string): Promise { - const unpacked_path = fs.mkdtempSync(path.join(os.tmpdir(), "modcdp-extension-")); - const cleanup = async () => fs.rmSync(unpacked_path, { recursive: true, force: true }); - try { - if (extension_path.endsWith(".zip")) { - await extractZip(extension_path, unpacked_path); - } else { - fs.cpSync(extension_path, unpacked_path, { recursive: true }); - } - return { unpacked_extension_path: extensionRoot(unpacked_path), cleanup }; - } catch (error) { - await cleanup(); - throw error; - } -} - -export async function extensionIdFromManifestKey(extension_path: string) { - const [crypto, fs, path] = await Promise.all([import("node:crypto"), import("node:fs"), import("node:path")]); - const manifest_path = path.join(extension_path, "manifest.json"); - if (!fs.existsSync(manifest_path)) return null; - const manifest = JSON.parse(fs.readFileSync(manifest_path, "utf8")) as Record; - const key = firstString(manifest.key); - if (!key) return null; - const digest = crypto.createHash("sha256").update(Buffer.from(key, "base64")).digest().subarray(0, 16); - const alphabet = "abcdefghijklmnop"; - return [...digest].map((byte) => alphabet[byte >> 4] + alphabet[byte & 0x0f]).join(""); -} - -function extensionRoot(unpacked_path: string) { - if (fs.existsSync(path.join(unpacked_path, "manifest.json"))) return unpacked_path; - const nested_path = path.join(unpacked_path, "extension"); - if (fs.existsSync(path.join(nested_path, "manifest.json"))) return nested_path; - return unpacked_path; -} - -async function extractZip(zip_path: string, destination: string) { - const { execFileSync } = await import("node:child_process"); - const listing = execFileSync("unzip", ["-Z1", zip_path], { - encoding: "utf8", - }); - for (const raw_name of listing.split(/\r?\n/)) { - if (!raw_name) continue; - const name = raw_name.replaceAll("\\", "/"); - const normalized = path.posix.normalize(name); - if ( - path.posix.isAbsolute(normalized) || - normalized === "." || - normalized === ".." || - normalized.startsWith("../") - ) { - throw new Error(`zip entry ${JSON.stringify(raw_name)} escapes extension extraction directory`); - } - } - execFileSync("unzip", ["-q", zip_path, "-d", destination]); -} - -export class ExtensionInjector { - options: ExtensionInjectorConfig; +class ExtensionInjector { + config: InjectorBaseConfig; + source: string | null; + extension_id: string | null; + service_worker_extension_id: string | null; + target_id: string | null; + url: string | null; + session_id: string | null; + extra_args: string[]; protected unusable_target_ids = new Set(); - last_error: Error | null = null; - constructor(options: ExtensionInjectorConfig = {}) { - this.options = { - send: null, - sessionIdForTarget: null, - attachToTarget: null, - waitForExecutionContext: null, - injector_extension_path: null, - injector_extension_id: null, - injector_service_worker_url_includes: [], - injector_service_worker_url_suffixes: [], - injector_trust_service_worker_target: false, - injector_require_service_worker_target: false, - injector_service_worker_ready_expression: null, - injector_cdp_send_timeout_ms: DEFAULT_CDP_SEND_TIMEOUT_MS, - injector_execution_context_timeout_ms: DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS, - injector_service_worker_probe_timeout_ms: DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, - injector_service_worker_ready_timeout_ms: DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, - injector_service_worker_poll_interval_ms: DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, - injector_target_session_poll_interval_ms: DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS, - injector_browserbase_api_key: null, - injector_browserbase_base_url: null, - upstream_nativemessaging_host_name: null, - upstream_nats_url: null, - upstream_nats_subject_prefix: null, - ...options, - }; + constructor(config: z.input = {}) { + this.config = InjectorConfigSchema.parse(config); + this.source = null; + this.extension_id = null; + this.service_worker_extension_id = null; + this.target_id = null; + this.url = null; + this.session_id = null; + this.extra_args = []; } - update(config: ExtensionInjectorConfig = {}) { - this.options = { - ...this.options, - ...config, - injector_service_worker_url_includes: - config.injector_service_worker_url_includes ?? this.options.injector_service_worker_url_includes ?? [], - injector_service_worker_url_suffixes: - config.injector_service_worker_url_suffixes ?? this.options.injector_service_worker_url_suffixes ?? [], - }; + update(config: z.input | Record = {}) { + this.config = InjectorConfigSchema.parse({ ...this.config, ...config }); return this; } - getInjectorConfig(): ExtensionInjectorConfig { - return { ...this.options }; - } - - getLauncherConfig(): BrowserLaunchOptions { - return {}; - } - - getTransportConfig(): UpstreamTransportConfig { - return this.options.injector_extension_id ? { injector_extension_id: this.options.injector_extension_id } : {}; + recordInjectionResult(result: ExtensionInjectionResult) { + this.source = result.source; + this.extension_id = result.extension_id ?? null; + this.service_worker_extension_id = result.extension_id ?? this.service_worker_extension_id; + this.target_id = result.target_id; + this.url = result.url ?? null; + this.session_id = result.session_id; + return this; } async prepare() {} @@ -204,111 +132,93 @@ export class ExtensionInjector { throw new Error(`${this.constructor.name}.inject is not implemented.`); } - protected get send(): SendCDP { - if (typeof this.options.send !== "function") - throw new Error(`${this.constructor.name} requires a CDP send function.`); - return this.options.send; + configForLauncher(): LauncherConfig { + return { + launcher_local_extra_args: this.extra_args, + launcher_bb_extension_id: this.config.injector_bb_extension_id, + }; } - protected readyExpression() { - const expression = this.options.injector_service_worker_ready_expression; - return expression == null || expression.length === 0 - ? MODCDP_READY_EXPRESSION - : `(${MODCDP_READY_EXPRESSION}) && Boolean(${expression})`; + configForUpstream(): UpstreamTransportConfig { + return {}; } - protected async sendWithTimeout( - method: string, - params: ProtocolParams = {}, - session_id: string | null = null, - timeout_ms = this.options.injector_cdp_send_timeout_ms ?? DEFAULT_CDP_SEND_TIMEOUT_MS, - ) { - let timeout: ReturnType | null = null; - return Promise.race([ - this.send(method, params, session_id), - new Promise((_, reject) => { - timeout = setTimeout(() => reject(new Error(`${method} timed out after ${timeout_ms}ms`)), timeout_ms); - }), - ]).finally(() => { - if (timeout != null) clearTimeout(timeout); + toJSON() { + return modCDPToJSON(this, { + config: { ...this.config, send: undefined }, }); } - protected async sessionIdForTarget(target_id: string, timeout_ms = 0) { - const deadline = Date.now() + timeout_ms; - while (true) { - const session_id = this.options.sessionIdForTarget?.(target_id); - if (typeof session_id === "string" && session_id.length > 0) return session_id; - if (Date.now() >= deadline) return null; - await delay(this.options.injector_target_session_poll_interval_ms ?? DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS); - } - } - - protected async ensureSessionIdForTarget(target_id: string, timeout_ms = 0, allow_attach = false) { - const session_id = this.options.sessionIdForTarget?.(target_id); - if (typeof session_id === "string" && session_id.length > 0) return session_id; - if (allow_attach) { - const attached_session_id = await this.options.attachToTarget?.(target_id); - if (typeof attached_session_id === "string" && attached_session_id.length > 0) return attached_session_id; - } - return await this.sessionIdForTarget(target_id, timeout_ms); + protected readyExpression() { + const expression = this.config.injector_service_worker_ready_expression; + return expression === MODCDP_READY_EXPRESSION + ? expression + : `(${MODCDP_READY_EXPRESSION}) && Boolean(${expression})`; } protected async targetInfos() { - return TargetCommands["Target.getTargets"].result.parse(await this.send("Target.getTargets")).targetInfos; + return (await this.config.send(Target.GetTargetsCommand, {})).targetInfos; } - protected async probeTarget( - target: TargetInfo, - session_timeout_ms = 0, - { allow_attach = false }: { allow_attach?: boolean } = {}, - ): Promise { + protected async probeTarget(target: TargetInfo): Promise { if (this.unusable_target_ids.has(target.targetId)) return null; - const session_id = await this.ensureSessionIdForTarget(target.targetId, session_timeout_ms, allow_attach); - if (session_id == null) return null; - await this.sendWithTimeout("Runtime.enable", {}, session_id); - const probe = RuntimeCommands["Runtime.evaluate"].result.parse( - await this.sendWithTimeout( - "Runtime.evaluate", + const attached = await this.config.send(Target.AttachToTargetCommand, { + targetId: target.targetId, + flatten: true, + }); + const session_id = attached.sessionId; + try { + await this.config.send(Runtime.EnableCommand, {}, session_id); + const probe = await this.config.send( + Runtime.EvaluateCommand, { expression: this.readyExpression(), returnByValue: true, }, session_id, - ), - ); - if (probe.result?.value !== true) return null; - return { - source: "discovered", - extension_id: target.url?.match(EXT_ID_FROM_URL)?.[1], - target_id: target.targetId, - url: target.url, - session_id, - }; + ); + if (probe.result?.value !== true) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + return null; + } + return { + source: "discover", + extension_id: target.url?.match(EXT_ID_FROM_URL)?.[1], + target_id: target.targetId, + url: target.url, + session_id, + }; + } catch (error) { + await this.config + .send(Target.DetachFromTargetCommand, { + sessionId: session_id, + }) + .catch(() => {}); + throw error; + } } protected async discoverReadyServiceWorker({ matched_only = false }: { matched_only?: boolean } = {}) { const target_infos = await this.targetInfos(); - if (this.options.injector_trust_service_worker_target) { + if (this.config.injector_trust_service_worker_target) { const trusted_target = target_infos.find((candidate) => this.serviceWorkerTargetMatches(candidate)) as | TargetInfo | undefined; if (trusted_target) { - const probed = await this.probeTarget(trusted_target, this.options.injector_service_worker_probe_timeout_ms, { - allow_attach: true, - }); + const probed = await this.probeTarget(trusted_target); if (probed) return { ...probed, source: "trusted" }; } } - if (this.options.injector_trust_service_worker_target || matched_only) return null; + if (this.config.injector_trust_service_worker_target || matched_only) return null; for (const candidate of target_infos) { if (candidate.type !== "service_worker") continue; if (!candidate.url.startsWith("chrome-extension://")) continue; try { - const probed = await this.probeTarget( - candidate as TargetInfo, - this.options.injector_service_worker_probe_timeout_ms, - ); + const probed = await this.probeTarget(candidate as TargetInfo); if (probed) return probed; } catch { continue; @@ -327,7 +237,7 @@ export class ExtensionInjector { matched_only, }); if (discovered) return discovered; - await delay(this.options.injector_service_worker_poll_interval_ms ?? DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS); + await delay(this.config.injector_service_worker_poll_interval_ms); } return null; } @@ -336,16 +246,30 @@ export class ExtensionInjector { const url = candidate.url ?? ""; if (candidate.type !== "service_worker") return false; if (!url.startsWith("chrome-extension://")) return false; - const has_extension_id = Boolean(this.options.injector_extension_id); - if ( - this.options.injector_extension_id && - !url.startsWith(`chrome-extension://${this.options.injector_extension_id}/`) - ) + const service_worker_extension_id = + this.config.injector_service_worker_extension_id ?? this.service_worker_extension_id; + const has_extension_id = Boolean(service_worker_extension_id); + if (service_worker_extension_id && !url.startsWith(`chrome-extension://${service_worker_extension_id}/`)) return false; - const includes = this.options.injector_service_worker_url_includes ?? []; - const suffixes = this.options.injector_service_worker_url_suffixes ?? []; + const includes = this.config.injector_service_worker_url_includes; + const suffixes = this.config.injector_service_worker_url_suffixes; if (includes.length > 0 && !includes.every((part) => url.includes(part))) return false; if (suffixes.length > 0 && !suffixes.some((suffix) => url.endsWith(suffix))) return false; return has_extension_id || includes.length > 0 || suffixes.length > 0; } } + +export { + DEFAULT_MODCDP_EXTENSION_ID, + DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES, + DEFAULT_CDP_SEND_TIMEOUT_MS, + DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS, + DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, + DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, + DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, + DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS, + DEFAULT_BROWSERBASE_BASE_URL, + ExtensionInjector, +}; +export { InjectorModeSchema, InjectorConfigSchema }; +export type { SendCDP, TargetInfo, InjectorMode, InjectorBaseConfig, InjectorConfig, ExtensionInjectionResult }; diff --git a/js/src/injector/LocalBrowserLaunchExtensionInjector.ts b/js/src/injector/LocalBrowserLaunchExtensionInjector.ts deleted file mode 100644 index b1ff8534..00000000 --- a/js/src/injector/LocalBrowserLaunchExtensionInjector.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { BrowserLaunchOptions } from "../launcher/BrowserLauncher.js"; -import { - defaultModCDPExtensionPath, - extensionIdFromManifestKey, - ExtensionInjector, - prepareUnpackedExtension, -} from "./ExtensionInjector.js"; - -export class LocalBrowserLaunchExtensionInjector extends ExtensionInjector { - private unpacked_extension_path: string | null = null; - private extension_id: string | null = null; - private cleanup: (() => Promise) | null = null; - - async prepare() { - const extension_path = this.options.injector_extension_path ?? defaultModCDPExtensionPath(); - if (this.unpacked_extension_path) { - await super.prepare(); - return; - } - const prepared = await prepareUnpackedExtension(extension_path); - this.unpacked_extension_path = prepared.unpacked_extension_path; - this.cleanup = prepared.cleanup; - await this.resolveExtensionId(); - await super.prepare(); - } - - getLauncherConfig(): BrowserLaunchOptions { - const extension_path = this.unpacked_extension_path; - if (!extension_path) return {}; - return { extra_args: [`--load-extension=${extension_path}`] }; - } - - async inject() { - const discovered = await this.discoverReadyServiceWorker({ - matched_only: this.options.injector_trust_service_worker_target, - }); - return discovered ? { ...discovered, source: "local_launch" } : null; - } - - async close() { - await super.close(); - await this.cleanup?.(); - this.cleanup = null; - } - - private async resolveExtensionId() { - if (this.extension_id) return this.extension_id; - this.extension_id = - typeof this.options.injector_extension_id === "string" && this.options.injector_extension_id.trim() - ? this.options.injector_extension_id.trim() - : null; - if (!this.extension_id && this.unpacked_extension_path) { - this.extension_id = await extensionIdFromManifestKey(this.unpacked_extension_path); - } - if (this.extension_id) this.options.injector_extension_id = this.extension_id; - return this.extension_id; - } -} diff --git a/js/src/injector/NodeExtensionFiles.ts b/js/src/injector/NodeExtensionFiles.ts new file mode 100644 index 00000000..2e618d08 --- /dev/null +++ b/js/src/injector/NodeExtensionFiles.ts @@ -0,0 +1,88 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/injector/NodeExtensionFiles.py +// - ./go/modcdp/injector/NodeExtensionFiles.go +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import crypto from "node:crypto"; +import { execFileSync } from "node:child_process"; + +type PreparedExtension = { + unpacked_extension_path: string; + cleanup: () => Promise; +}; + +function defaultModCDPExtensionPath() { + if (typeof process === "object" && process?.versions?.node && import.meta.url.startsWith("file:")) { + const relative_path = import.meta.url.includes("/dist/js/src/") + ? "../../../../dist/extension.zip" + : "../../../dist/extension.zip"; + return decodeURIComponent(new URL(/* @vite-ignore */ relative_path, import.meta.url).pathname); + } + return "../../../dist/extension.zip"; +} + +function firstString(...values: unknown[]) { + for (const value of values) { + if (typeof value === "string" && value.trim()) return value.trim(); + } + return null; +} + +async function prepareUnpackedExtension(extension_path: string): Promise { + const unpacked_path = fs.mkdtempSync(path.join(os.tmpdir(), "modcdp-extension-")); + const cleanup = async () => fs.rmSync(unpacked_path, { recursive: true, force: true }); + try { + if (extension_path.endsWith(".zip")) { + await extractZip(extension_path, unpacked_path); + } else { + fs.cpSync(extension_path, unpacked_path, { recursive: true }); + } + return { unpacked_extension_path: extensionRoot(unpacked_path), cleanup }; + } catch (error) { + await cleanup(); + throw error; + } +} + +async function extensionIdFromManifestKey(extension_path: string) { + const manifest_path = path.join(extension_path, "manifest.json"); + if (!fs.existsSync(manifest_path)) return null; + const manifest = JSON.parse(fs.readFileSync(manifest_path, "utf8")) as Record; + const key = firstString(manifest.key); + if (!key) return null; + const digest = crypto.createHash("sha256").update(Buffer.from(key, "base64")).digest().subarray(0, 16); + const alphabet = "abcdefghijklmnop"; + return [...digest].map((byte) => alphabet[byte >> 4] + alphabet[byte & 0x0f]).join(""); +} + +function extensionRoot(unpacked_path: string) { + if (fs.existsSync(path.join(unpacked_path, "manifest.json"))) return unpacked_path; + const nested_path = path.join(unpacked_path, "extension"); + if (fs.existsSync(path.join(nested_path, "manifest.json"))) return nested_path; + return unpacked_path; +} + +async function extractZip(zip_path: string, destination: string) { + const listing = execFileSync("unzip", ["-Z1", zip_path], { + encoding: "utf8", + }); + for (const raw_name of listing.split(/\r?\n/)) { + if (!raw_name) continue; + const name = raw_name.replaceAll("\\", "/"); + const normalized = path.posix.normalize(name); + if ( + path.posix.isAbsolute(normalized) || + normalized === "." || + normalized === ".." || + normalized.startsWith("../") + ) { + throw new Error(`zip entry ${JSON.stringify(raw_name)} escapes extension extraction directory`); + } + } + execFileSync("unzip", ["-q", zip_path, "-d", destination]); +} + +export { defaultModCDPExtensionPath, prepareUnpackedExtension, extensionIdFromManifestKey }; +export type { PreparedExtension }; diff --git a/js/src/launcher/BrowserbaseBrowserLauncher.ts b/js/src/launcher/BBBrowserLauncher.ts similarity index 61% rename from js/src/launcher/BrowserbaseBrowserLauncher.ts rename to js/src/launcher/BBBrowserLauncher.ts index 0b6f9876..9e244094 100644 --- a/js/src/launcher/BrowserbaseBrowserLauncher.ts +++ b/js/src/launcher/BBBrowserLauncher.ts @@ -1,7 +1,9 @@ -import { BrowserLauncher, type BrowserLaunchOptions, type LaunchedBrowser } from "./BrowserLauncher.js"; - -const DEFAULT_BROWSERBASE_BASE_URL = "https://api.browserbase.com"; -const DEFAULT_BROWSERBASE_VIEWPORT = { width: 1288, height: 711 }; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/launcher/BBBrowserLauncher.py +// - ./go/modcdp/launcher/BBBrowserLauncher.go +import { BrowserLauncher, type LauncherConfig, type LaunchedBrowser } from "./BrowserLauncher.js"; +import { ModCDPLauncherConfigSchema } from "../types/modcdp.js"; type BrowserbaseSession = { id?: string; @@ -11,24 +13,6 @@ type BrowserbaseSession = { status?: string; }; -function firstString(...values: unknown[]) { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value.trim(); - } - return null; -} - -function firstBoolean(...values: unknown[]) { - for (const value of values) { - if (typeof value === "boolean") return value; - } - return null; -} - -function objectValue(value: unknown): Record { - return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; -} - function browserbaseUrl(base_url: string, pathname: string) { return new URL(pathname, `${base_url.replace(/\/$/, "")}/`).toString(); } @@ -89,21 +73,22 @@ async function closeBrowserCDP(cdp_url: string | undefined) { }); } -export class BrowserbaseBrowserLauncher extends BrowserLauncher { - async launch(options: BrowserLaunchOptions = {}): Promise { - const merged = { ...this.options, ...options }; - const browserbase_api_key = firstString(merged.browserbase_api_key, process.env.BROWSERBASE_API_KEY); +class BBBrowserLauncher extends BrowserLauncher { + constructor(config: LauncherConfig = {}) { + super({ ...config, launcher_mode: "bb" }); + } + + async launch(config: LauncherConfig = {}): Promise { + const launch_config = ModCDPLauncherConfigSchema.parse({ ...this.config, ...config }); + const browserbase_api_key = launch_config.launcher_bb_api_key ?? process.env.BROWSERBASE_API_KEY; if (!browserbase_api_key) { - throw new Error( - "launcher.launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key.", - ); + throw new Error("launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_bb_api_key."); } - const base_url = - firstString(merged.browserbase_base_url, process.env.BROWSERBASE_BASE_URL) ?? DEFAULT_BROWSERBASE_BASE_URL; - const resume_session_id = firstString(merged.browserbase_session_id); - const keep_alive = firstBoolean(merged.browserbase_keep_alive) ?? false; - const close_session_on_close = firstBoolean(merged.browserbase_close_session_on_close) ?? !keep_alive; + const base_url = launch_config.launcher_bb_base_url; + const resume_session_id = launch_config.launcher_bb_session_id; + const keep_alive = launch_config.launcher_bb_keep_alive; + const close_session_on_close = launch_config.launcher_bb_close_session_on_close ?? !keep_alive; let created_session = false; let session: BrowserbaseSession; @@ -115,34 +100,32 @@ export class BrowserbaseBrowserLauncher extends BrowserLauncher { pathname: `/v1/sessions/${resume_session_id}`, }); } else { - const session_create_params = objectValue(merged.browserbase_session_create_params); + const session_create_params = launch_config.launcher_bb_session_create_params; const browser_settings = { - ...objectValue(session_create_params.browserSettings), - ...objectValue(merged.browserbase_browser_settings), + ...(session_create_params.browserSettings ?? {}), + ...launch_config.launcher_bb_browser_settings, }; const user_metadata = { - ...objectValue(session_create_params.userMetadata), - ...objectValue(merged.browserbase_user_metadata), + ...session_create_params.userMetadata, + ...launch_config.launcher_bb_user_metadata, }; - const extension_id = firstString( - merged.injector_extension_id, - session_create_params.extensionId, - objectValue(session_create_params.browserSettings).extensionId, - ); + const extension_id = + launch_config.launcher_bb_extension_id ?? + session_create_params.extensionId ?? + session_create_params.browserSettings?.extensionId; + const region = launch_config.launcher_bb_region ?? session_create_params.region; const body = { ...session_create_params, ...(keep_alive ? { keepAlive: true } : {}), - ...(firstString(merged.region, session_create_params.region) - ? { region: firstString(merged.region, session_create_params.region) } + ...(region ? { region } : {}), + ...(typeof launch_config.launcher_bb_timeout === "number" + ? { timeout: launch_config.launcher_bb_timeout } : {}), - ...(typeof merged.timeout === "number" ? { timeout: merged.timeout } : {}), ...(extension_id ? { extensionId: extension_id } : {}), browserSettings: { ...browser_settings, ...(extension_id ? { extensionId: extension_id } : {}), - viewport: objectValue(browser_settings.viewport).width - ? browser_settings.viewport - : DEFAULT_BROWSERBASE_VIEWPORT, + viewport: browser_settings.viewport, }, userMetadata: { ...user_metadata, @@ -189,3 +172,5 @@ export class BrowserbaseBrowserLauncher extends BrowserLauncher { return this.launched; } } + +export { BBBrowserLauncher }; diff --git a/js/src/launcher/BrowserLauncher.ts b/js/src/launcher/BrowserLauncher.ts index 319a73f3..7b9bceea 100644 --- a/js/src/launcher/BrowserLauncher.ts +++ b/js/src/launcher/BrowserLauncher.ts @@ -1,40 +1,25 @@ -import type { ExtensionInjectorConfig } from "../injector/ExtensionInjector.js"; -import type { ModCDPServerOptions } from "../types/modcdp.js"; -import type { UpstreamTransportConfig } from "../transport/UpstreamTransport.js"; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/launcher/BrowserLauncher.py +// - ./go/modcdp/launcher/BrowserLauncher.go +import type { UpstreamTransport, UpstreamTransportConfig } from "../transport/UpstreamTransport.js"; +import { ModCDPLauncherConfigSchema, ModCDPServerConfigSchema, type ModCDPLauncherConfig } from "../types/modcdp.js"; +import { modCDPToJSON } from "../types/toJSON.js"; +import type { z } from "zod"; -export type BrowserLaunchOptions = { - executable_path?: string | null; - port?: number | null; - user_data_dir?: string | null; - headless?: boolean; - sandbox?: boolean; - args?: string[]; - extra_args?: string[]; - remote_debugging?: "port" | "pipe"; - loopback_cdp?: boolean; - cleanup_user_data_dir?: boolean; - chrome_ready_timeout_ms?: number; - chrome_ready_poll_interval_ms?: number; - cdp_url?: string | null; - browserbase_api_key?: string | null; - browserbase_base_url?: string | null; - browserbase_session_id?: string | null; - browserbase_keep_alive?: boolean; - browserbase_close_session_on_close?: boolean; - region?: string | null; - timeout?: number | null; - injector_extension_id?: string | null; - browserbase_browser_settings?: Record | null; - browserbase_user_metadata?: Record | null; - browserbase_session_create_params?: Record | null; +type LauncherConfig = z.input; +type LauncherMode = ReturnType["launcher_mode"]; +type LauncherUpstreamConfig = UpstreamTransportConfig & { + upstream_pipe_read?: NodeJS.ReadableStream; + upstream_pipe_write?: NodeJS.WritableStream; }; -export type LaunchedBrowser = { +type LaunchedBrowser = { proc?: unknown; - port?: number; - // Effective CDP endpoint for the selected transport; launchers resolve HTTP discovery endpoints to ws:// before returning when they can. + cdp_listen_port?: number; + // Browser websocket CDP endpoint when one exists. Pipe transports expose pipe handles instead. cdp_url: string | null; - // Extension-dialable loopback CDP endpoint when it differs from cdp_url, for example pipe:// primary transport. + // Extension-dialable loopback CDP endpoint when it differs from cdp_url (usually they are the same unless public-facing cdp url differs from intranet/localhost equivalent). loopback_cdp_url?: string | null; pipe_read?: NodeJS.ReadableStream | null; pipe_write?: NodeJS.WritableStream | null; @@ -45,8 +30,8 @@ export type LaunchedBrowser = { close: () => Promise | void; }; -export const DEFAULT_CHROME_READY_TIMEOUT_MS = 45_000; -export const DEFAULT_CHROME_READY_POLL_INTERVAL_MS = 100; +const DEFAULT_CHROME_READY_TIMEOUT_MS = 45_000; +const DEFAULT_CHROME_READY_POLL_INTERVAL_MS = 100; function mergeChromeArgs(existing: string[] = [], incoming: string[] = []) { const args = [...existing, ...incoming]; @@ -70,55 +55,80 @@ function mergeChromeArgs(existing: string[] = [], incoming: string[] = []) { return merged; } -export class BrowserLauncher { - options: BrowserLaunchOptions; +class BrowserLauncher { + config: ModCDPLauncherConfig; + + // runtime state launched: LaunchedBrowser | null = null; - constructor(options: BrowserLaunchOptions = {}) { - this.options = { ...options }; + constructor(config: LauncherConfig = {}) { + this.config = ModCDPLauncherConfigSchema.parse(config); } - update(config: BrowserLaunchOptions = {}) { - this.options = { - ...this.options, - ...config, - ...(config.args ? { args: mergeChromeArgs(this.options.args, config.args) } : {}), - ...(config.extra_args - ? { - extra_args: mergeChromeArgs(this.options.extra_args, config.extra_args), - } - : {}), - }; + update(config: LauncherConfig = {}) { + const next_config = ModCDPLauncherConfigSchema.parse({ ...this.config, ...config }); + if (config.launcher_local_args) { + next_config.launcher_local_args = mergeChromeArgs(this.config.launcher_local_args, config.launcher_local_args); + } + if (config.launcher_local_extra_args) { + next_config.launcher_local_extra_args = mergeChromeArgs( + this.config.launcher_local_extra_args, + config.launcher_local_extra_args, + ); + } + this.config = next_config; return this; } - getTransportConfig(): UpstreamTransportConfig { - return { - cdp_url: this.launched?.cdp_url ?? this.options.cdp_url ?? null, - user_data_dir: this.launched?.profile_dir ?? this.options.user_data_dir ?? null, - pipe_read: this.launched?.pipe_read ?? null, - pipe_write: this.launched?.pipe_write ?? null, - }; + async launch(_config: LauncherConfig = {}): Promise { + throw new Error(`${this.constructor.name}.launch is not implemented.`); + } + + configForUpstream(): UpstreamTransportConfig { + const config: LauncherUpstreamConfig = {}; + const upstream_ws_cdp_url = this.launched?.cdp_url ?? this.config.launcher_remote_cdp_url; + if (upstream_ws_cdp_url) config.upstream_ws_cdp_url = upstream_ws_cdp_url; + if (this.launched?.pipe_read) config.upstream_pipe_read = this.launched.pipe_read; + if (this.launched?.pipe_write) config.upstream_pipe_write = this.launched.pipe_write; + return config; } - getServerConfig(): Partial { - return this.launched?.loopback_cdp_url ? { server_loopback_cdp_url: this.launched.loopback_cdp_url } : {}; + configForServer(upstream: UpstreamTransport): z.input { + const launcher_local_loopback_cdp_url = + this.launched?.loopback_cdp_url ?? + (upstream.config.upstream_mode === "ws" && upstream.config.upstream_ws_cdp_url + ? upstream.config.upstream_ws_cdp_url + : upstream.config.upstream_mode !== "ws" && upstream.config.upstream_mode !== "pipe" && this.launched?.cdp_url + ? this.launched.cdp_url + : null); + return launcher_local_loopback_cdp_url + ? { upstream: { upstream_mode: "ws", upstream_ws_cdp_url: launcher_local_loopback_cdp_url } } + : {}; } - getInjectorConfig(): ExtensionInjectorConfig { - return { - injector_browserbase_api_key: this.options.browserbase_api_key ?? null, - injector_browserbase_base_url: this.options.browserbase_base_url ?? null, - injector_extension_id: this.options.injector_extension_id ?? null, - }; + async close() { + const launched = this.launched; + this.launched = null; + await launched?.close(); } - async launch(_options: BrowserLaunchOptions = {}): Promise { - throw new Error(`${this.constructor.name}.launch is not implemented.`); + toJSON() { + return modCDPToJSON(this, { + state: { + launched: this.launched != null, + cdp_url: this.launched?.cdp_url ?? null, + loopback_cdp_url: this.launched?.loopback_cdp_url ?? null, + cdp_listen_port: this.launched?.cdp_listen_port ?? null, + profile_dir: this.launched?.profile_dir ?? null, + browserbase_session_id: this.launched?.browserbase_session_id ?? null, + browserbase_session_url: this.launched?.browserbase_session_url ?? null, + browserbase_debug_url: this.launched?.browserbase_debug_url ?? null, + }, + }); } } -export async function resolveCdpWebSocketUrl(endpoint: string, name = "cdp_url") { +async function resolveCdpWebSocketUrl(endpoint: string, name = "cdp_url") { if (/^wss?:\/\//i.test(endpoint)) return endpoint; const httpEndpoint = /^[a-z][a-z\d+\-.]*:\/\//i.test(endpoint) ? endpoint : `http://${endpoint}`; const response = await fetch(`${httpEndpoint.replace(/\/$/, "")}/json/version`); @@ -127,3 +137,11 @@ export async function resolveCdpWebSocketUrl(endpoint: string, name = "cdp_url") if (!version.webSocketDebuggerUrl) throw new Error(`${name} HTTP discovery returned no webSocketDebuggerUrl`); return version.webSocketDebuggerUrl as string; } + +export { + DEFAULT_CHROME_READY_TIMEOUT_MS, + DEFAULT_CHROME_READY_POLL_INTERVAL_MS, + BrowserLauncher, + resolveCdpWebSocketUrl, +}; +export type { LauncherMode, LauncherConfig, LaunchedBrowser }; diff --git a/js/src/launcher/LocalBrowserLauncher.ts b/js/src/launcher/LocalBrowserLauncher.ts index b31f9ce6..f923fcf3 100644 --- a/js/src/launcher/LocalBrowserLauncher.ts +++ b/js/src/launcher/LocalBrowserLauncher.ts @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/launcher/LocalBrowserLauncher.py +// - ./go/modcdp/launcher/LocalBrowserLauncher.go import { spawn, type ChildProcess } from "node:child_process"; import { once } from "node:events"; import { existsSync, readdirSync, statSync } from "node:fs"; @@ -6,14 +10,20 @@ import net from "node:net"; import type { AddressInfo } from "node:net"; import { homedir, platform, tmpdir } from "node:os"; import path from "node:path"; +import { z } from "zod"; import { BrowserLauncher, - DEFAULT_CHROME_READY_POLL_INTERVAL_MS, - DEFAULT_CHROME_READY_TIMEOUT_MS, resolveCdpWebSocketUrl, - type BrowserLaunchOptions, + type LauncherConfig, type LaunchedBrowser, } from "./BrowserLauncher.js"; +import { ModCDPLauncherConfigSchema } from "../types/modcdp.js"; + +const LocalBrowserLauncherConfigSchema = ModCDPLauncherConfigSchema.extend({ + launcher_local_cdp_transport: z.enum(["ws", "pipe"]).default("ws"), +}); +type LocalBrowserLauncherConfig = z.infer; +type LocalBrowserLauncherInput = z.input; function wildcardToRegExp(value: string) { return new RegExp(`^${value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`); @@ -148,6 +158,28 @@ function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } +function mergeLocalChromeArgs(existing: string[] = [], incoming: string[] = []) { + const args = [...existing, ...incoming]; + const load_extension_paths: string[] = []; + const merged: string[] = []; + for (const arg of args) { + if (!arg.startsWith("--load-extension=")) { + merged.push(arg); + continue; + } + for (const extension_path of arg.slice("--load-extension=".length).split(",")) { + if (extension_path && !load_extension_paths.includes(extension_path)) load_extension_paths.push(extension_path); + } + } + if (load_extension_paths.length > 0) { + const first_url_index = merged.findIndex((arg) => !arg.startsWith("-")); + const load_extension_arg = `--load-extension=${load_extension_paths.join(",")}`; + if (first_url_index === -1) merged.push(load_extension_arg); + else merged.splice(first_url_index, 0, load_extension_arg); + } + return merged; +} + async function terminateProcess(proc: ChildProcess, timeoutMs = 2_000) { if (proc.exitCode !== null || proc.signalCode !== null) return; const signalProcess = (signal: NodeJS.Signals) => { @@ -240,22 +272,6 @@ async function waitForCdpWebSocketUrl(cdp_url: string, timeout_ms: number, poll_ throw new Error(`Chrome at ${cdp_url} did not expose a WebSocket CDP URL within ${timeout_ms}ms`); } -async function readDevToolsActivePort(profile_dir: string) { - const activePortPath = path.join(profile_dir, "DevToolsActivePort"); - let body: string; - try { - body = await readFile(activePortPath, "utf8"); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; - throw error; - } - const [rawPort, websocketPath] = body.trim().split(/\r?\n/); - if (!rawPort || !websocketPath) return null; - const port = Number(rawPort); - if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid DevToolsActivePort port: ${rawPort}`); - return { port, cdp_url: `http://127.0.0.1:${port}`, websocketPath }; -} - async function waitForBrowserSelectedCdpWebSocketUrl( profile_dir: string, timeout_ms: number, @@ -270,7 +286,7 @@ async function waitForBrowserSelectedCdpWebSocketUrl( if (activePort) { try { return { - port: activePort.port, + cdp_listen_port: activePort.cdp_listen_port, cdp_url: await resolveCdpWebSocketUrl(activePort.cdp_url), }; } catch (error) { @@ -287,14 +303,56 @@ async function waitForBrowserSelectedCdpWebSocketUrl( throw new Error(`Chrome did not expose DevToolsActivePort from ${profile_dir} within ${timeout_ms}ms`); } -export class LocalBrowserLauncher extends BrowserLauncher { +async function readDevToolsActivePort(profile_dir: string) { + const activePortPath = path.join(profile_dir, "DevToolsActivePort"); + let body: string; + try { + body = await readFile(activePortPath, "utf8"); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return null; + throw error; + } + const [rawPort, websocketPath] = body.trim().split(/\r?\n/); + if (!rawPort || !websocketPath) return null; + const port = Number(rawPort); + if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid DevToolsActivePort port: ${rawPort}`); + return { cdp_listen_port: port, cdp_url: `http://127.0.0.1:${port}`, websocketPath }; +} + +class LocalBrowserLauncher extends BrowserLauncher { + declare config: LocalBrowserLauncherConfig; + + constructor(config: LocalBrowserLauncherInput = {}) { + const { launcher_local_cdp_transport: _launcher_local_cdp_transport, ...base_config } = config; + super({ ...base_config, launcher_mode: "local" } as LauncherConfig); + this.config = LocalBrowserLauncherConfigSchema.parse({ ...config, launcher_mode: "local" }); + } + + override update(config: LocalBrowserLauncherInput = {}) { + const next_config = LocalBrowserLauncherConfigSchema.parse({ ...this.config, ...config, launcher_mode: "local" }); + if (config.launcher_local_args) { + next_config.launcher_local_args = mergeLocalChromeArgs( + this.config.launcher_local_args, + config.launcher_local_args, + ); + } + if (config.launcher_local_extra_args) { + next_config.launcher_local_extra_args = mergeLocalChromeArgs( + this.config.launcher_local_extra_args, + config.launcher_local_extra_args, + ); + } + this.config = next_config; + return this; + } + static findChromeBinary(explicit?: string | null) { const candidates = [explicit, ...candidatePaths()].filter((candidate): candidate is string => Boolean(candidate)); for (const candidate of candidates) { if (candidate && existsSync(candidate)) return candidate; } throw new Error( - `No Chrome/Chromium binary found. Tried: ${candidates.join(", ")}. Set CHROME_PATH or pass executable_path.`, + `No Chrome/Chromium binary found. Tried: ${candidates.join(", ")}. Set CHROME_PATH or pass launcher_local_executable_path.`, ); } @@ -309,26 +367,17 @@ export class LocalBrowserLauncher extends BrowserLauncher { return port; } - async launch(options: BrowserLaunchOptions = {}): Promise { - const { - executable_path, - port, - user_data_dir, - headless = process.platform === "linux" && !process.env.DISPLAY, - sandbox = process.platform !== "linux", - args = [], - extra_args = [], - remote_debugging = "port", - loopback_cdp = false, - cleanup_user_data_dir = false, - chrome_ready_timeout_ms = DEFAULT_CHROME_READY_TIMEOUT_MS, - chrome_ready_poll_interval_ms = DEFAULT_CHROME_READY_POLL_INTERVAL_MS, - } = { ...this.options, ...options }; - const exe = LocalBrowserLauncher.findChromeBinary(executable_path); - const usePipe = remote_debugging === "pipe"; - const useLoopbackCdp = !usePipe || loopback_cdp || port != null; - const usePort = useLoopbackCdp ? (port ?? 0) : null; - const profile_dir = user_data_dir || (await mkdtemp(path.join(tmpdir(), "modcdp."))); + async launch(config: LauncherConfig = {}): Promise { + const launch_config = LocalBrowserLauncherConfigSchema.parse({ ...this.config, ...config, launcher_mode: "local" }); + const exe = LocalBrowserLauncher.findChromeBinary(launch_config.launcher_local_executable_path); + const usePipe = launch_config.launcher_local_cdp_transport === "pipe"; + const useLoopbackCdp = + !usePipe || launch_config.launcher_local_loopback_cdp || launch_config.launcher_local_cdp_listen_port != null; + const usePort = useLoopbackCdp ? (launch_config.launcher_local_cdp_listen_port ?? 0) : null; + const profile_dir = launch_config.launcher_local_user_data_dir || (await mkdtemp(path.join(tmpdir(), "modcdp."))); + const default_headless = process.platform === "linux" && !process.env.DISPLAY; + const headless = launch_config.launcher_local_headless ?? default_headless; + const sandbox = launch_config.launcher_local_sandbox ?? !default_headless; const flags = [ ...DEFAULT_FLAGS, headless ? "--headless=new" : null, @@ -338,18 +387,18 @@ export class LocalBrowserLauncher extends BrowserLauncher { useLoopbackCdp ? "--remote-debugging-address=127.0.0.1" : null, useLoopbackCdp ? `--remote-debugging-port=${usePort}` : null, usePipe ? "--remote-debugging-pipe" : null, - ...args, - ...extra_args, + ...launch_config.launcher_local_args, + ...launch_config.launcher_local_extra_args, "about:blank", ].filter(Boolean); - const useStdio = (usePipe ? ["ignore", "ignore", "ignore", "pipe", "pipe"] : "ignore") as + const stdio = (usePipe ? ["ignore", "ignore", "ignore", "pipe", "pipe"] : "ignore") as | "ignore" | "inherit" | "pipe" | import("node:child_process").StdioOptions; const proc = spawn(exe, flags, { - stdio: useStdio, + stdio, detached: process.platform !== "win32", }); let spawnError: Error | null = null; @@ -361,7 +410,8 @@ export class LocalBrowserLauncher extends BrowserLauncher { if (closed) return; closed = true; await terminateProcess(proc); - if (!user_data_dir || cleanup_user_data_dir) await removeProfileDir(profile_dir); + if (!launch_config.launcher_local_user_data_dir || launch_config.launcher_local_cleanup_user_data_dir) + await removeProfileDir(profile_dir); }; const assertChromeRunning = () => { if (spawnError) throw spawnError; @@ -378,29 +428,29 @@ export class LocalBrowserLauncher extends BrowserLauncher { throw new Error("Chrome remote-debugging pipe stdio handles were not created."); } assertChromeRunning(); - await waitForPipeReady(pipe_read, pipe_write, chrome_ready_timeout_ms); + await waitForPipeReady(pipe_read, pipe_write, launch_config.launcher_local_chrome_ready_timeout_ms); const loopback = usePort == null ? null : usePort === 0 ? await waitForBrowserSelectedCdpWebSocketUrl( profile_dir, - chrome_ready_timeout_ms, - chrome_ready_poll_interval_ms, + launch_config.launcher_local_chrome_ready_timeout_ms, + launch_config.launcher_local_chrome_ready_poll_interval_ms, assertChromeRunning, ) : { - port: usePort, + cdp_listen_port: usePort, cdp_url: await waitForCdpWebSocketUrl( `http://127.0.0.1:${usePort}`, - chrome_ready_timeout_ms, - chrome_ready_poll_interval_ms, + launch_config.launcher_local_chrome_ready_timeout_ms, + launch_config.launcher_local_chrome_ready_poll_interval_ms, ), }; this.launched = { proc, - ...(loopback == null ? {} : { port: loopback.port }), - cdp_url: `pipe://${proc.pid}`, + ...(loopback == null ? {} : { cdp_listen_port: loopback.cdp_listen_port }), + cdp_url: null, ...(loopback == null ? {} : { loopback_cdp_url: loopback.cdp_url }), pipe_read, pipe_write, @@ -410,7 +460,7 @@ export class LocalBrowserLauncher extends BrowserLauncher { return this.launched; } - const deadline = Date.now() + chrome_ready_timeout_ms; + const deadline = Date.now() + launch_config.launcher_local_chrome_ready_timeout_ms; while (Date.now() < deadline) { try { assertChromeRunning(); @@ -421,9 +471,9 @@ export class LocalBrowserLauncher extends BrowserLauncher { const activePort = usePort === 0 ? await readDevToolsActivePort(profile_dir) - : { port: usePort as number, cdp_url: `http://127.0.0.1:${usePort}` }; + : { cdp_listen_port: usePort as number, cdp_url: `http://127.0.0.1:${usePort}` }; if (!activePort) { - await delay(chrome_ready_poll_interval_ms); + await delay(launch_config.launcher_local_chrome_ready_poll_interval_ms); continue; } try { @@ -433,7 +483,7 @@ export class LocalBrowserLauncher extends BrowserLauncher { // cdp_url is resolved from the HTTP discovery endpoint before returning. this.launched = { proc, - port: activePort.port, + cdp_listen_port: activePort.cdp_listen_port, cdp_url: version.webSocketDebuggerUrl ?? activePort.cdp_url, loopback_cdp_url: version.webSocketDebuggerUrl ?? activePort.cdp_url, profile_dir, @@ -442,9 +492,11 @@ export class LocalBrowserLauncher extends BrowserLauncher { return this.launched; } } catch {} - await new Promise((resolve) => setTimeout(resolve, chrome_ready_poll_interval_ms)); + await new Promise((resolve) => setTimeout(resolve, launch_config.launcher_local_chrome_ready_poll_interval_ms)); } await close(); - throw new Error(`Chrome did not become ready within ${chrome_ready_timeout_ms}ms`); + throw new Error(`Chrome did not become ready within ${launch_config.launcher_local_chrome_ready_timeout_ms}ms`); } } + +export { LocalBrowserLauncher }; diff --git a/js/src/launcher/NoneBrowserLauncher.ts b/js/src/launcher/NoneBrowserLauncher.ts new file mode 100644 index 00000000..bd0de89c --- /dev/null +++ b/js/src/launcher/NoneBrowserLauncher.ts @@ -0,0 +1,18 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/launcher/NoneBrowserLauncher.py +// - ./go/modcdp/launcher/NoneBrowserLauncher.go +import { BrowserLauncher, type LauncherConfig, type LaunchedBrowser } from "./BrowserLauncher.js"; + +class NoneBrowserLauncher extends BrowserLauncher { + constructor(config: LauncherConfig = {}) { + super({ ...config, launcher_mode: "none" }); + } + + async launch(_config: LauncherConfig = {}): Promise { + this.launched = { cdp_url: null, close: async () => {} }; + return this.launched; + } +} + +export { NoneBrowserLauncher }; diff --git a/js/src/launcher/NoopBrowserLauncher.ts b/js/src/launcher/NoopBrowserLauncher.ts deleted file mode 100644 index 0df459ee..00000000 --- a/js/src/launcher/NoopBrowserLauncher.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { BrowserLauncher, type BrowserLaunchOptions, type LaunchedBrowser } from "./BrowserLauncher.js"; - -export class NoopBrowserLauncher extends BrowserLauncher { - async launch(_options: BrowserLaunchOptions = {}): Promise { - this.launched = { cdp_url: null, close: async () => {} }; - return this.launched; - } -} diff --git a/js/src/launcher/RemoteBrowserLauncher.ts b/js/src/launcher/RemoteBrowserLauncher.ts index e79dda0c..236a7ffe 100644 --- a/js/src/launcher/RemoteBrowserLauncher.ts +++ b/js/src/launcher/RemoteBrowserLauncher.ts @@ -1,21 +1,27 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/launcher/RemoteBrowserLauncher.py +// - ./go/modcdp/launcher/RemoteBrowserLauncher.go import { BrowserLauncher, resolveCdpWebSocketUrl, - type BrowserLaunchOptions, + type LauncherConfig, type LaunchedBrowser, } from "./BrowserLauncher.js"; -export class RemoteBrowserLauncher extends BrowserLauncher { - constructor(options: BrowserLaunchOptions = {}, cdp_url: string | null = null) { - super({ ...options, ...(cdp_url == null ? {} : { cdp_url }) }); +class RemoteBrowserLauncher extends BrowserLauncher { + constructor(config: LauncherConfig = {}) { + super({ ...config, launcher_mode: "remote" }); } - async launch(options: BrowserLaunchOptions = {}): Promise { - const endpoint = options.cdp_url ?? this.options.cdp_url; - if (!endpoint) throw new Error("launcher.launcher_mode=remote requires upstream_cdp_url."); + async launch(config: LauncherConfig = {}): Promise { + const endpoint = config.launcher_remote_cdp_url ?? this.config.launcher_remote_cdp_url; + if (!endpoint) throw new Error("launcher_mode=remote requires launcher_remote_cdp_url."); // cdp_url is resolved here so downstream transports can dial it directly. - const cdp_url = await resolveCdpWebSocketUrl(endpoint, "remote cdp_url"); + const cdp_url = await resolveCdpWebSocketUrl(endpoint, "launcher_remote_cdp_url"); this.launched = { cdp_url, close: async () => {} }; return this.launched; } } + +export { RemoteBrowserLauncher }; diff --git a/js/src/proxy/ProxyConnectionState.ts b/js/src/proxy/ProxyConnectionState.ts deleted file mode 100644 index 7857f06c..00000000 --- a/js/src/proxy/ProxyConnectionState.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { z } from "zod"; - -import type { ProtocolResult } from "../types/modcdp.js"; - -export const ProxyPendingSchema = z - .object({ - kind: z.string(), - client_id: z.number().optional(), - client_session_id: z.string().nullable().optional(), - event_name: z.string().optional(), - unwrap: z.enum(["runtime", "runtime_json"]).optional(), - resolve: z.custom<(value: ProtocolResult) => void>().optional(), - reject: z.custom<(error: Error) => void>().optional(), - }) - .passthrough(); -export type ProxyPending = z.infer; - -export const ProxyUpstreamStateSchema = z - .object({ - url: z.string(), - launched: z - .custom>>() - .nullable(), - launch_promise: z - .promise(z.custom>>()) - .nullable() - .optional(), - }) - .passthrough(); -export type ProxyUpstreamState = z.infer; - -export type ProxyRawData = Buffer | ArrayBuffer | Buffer[]; -export type ProxyWebSocketLike = { - CLOSED: number; - CLOSING: number; - readyState: number; - close(code?: number, reason?: string | Buffer): void; - send(data: string): void; -}; - -export const ProxyConnectionStateSchema = z.object({ - client: z.custom(), - upstream: z.custom(), - next_upstream_id: z.number(), - pending: z.custom>(), - ext_session_id: z.string().nullable(), - ext_target_id: z.string().nullable(), - ext_execution_context_id: z.number().nullable(), - hidden_session_ids: z.custom>(), - hidden_target_ids: z.custom>(), - target_session_ids: z.custom>(), - client_session_ids: z.custom>(), - forward_mirrored_upstream_events: z.boolean(), - bootstrapped: z.boolean(), - closing: z.boolean(), - queued_from_client: z.array(z.custom()), -}); -export type ProxyConnectionState = z.infer; diff --git a/js/src/proxy/cli.ts b/js/src/proxy/cli.ts index c131f8f0..a30ac610 100755 --- a/js/src/proxy/cli.ts +++ b/js/src/proxy/cli.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in Node. import { runProxyCli } from "./proxy.js"; diff --git a/js/src/proxy/proxy.ts b/js/src/proxy/proxy.ts index 46186c75..f2e4be80 100644 --- a/js/src/proxy/proxy.ts +++ b/js/src/proxy/proxy.ts @@ -1,1330 +1,295 @@ -// proxy.js: a transparent local CDP proxy that "upgrades" any vanilla CDP -// client to speak Mod.* / Custom.*. By default listens on ws://127.0.0.1:9223 -// and forwards to http://127.0.0.1:9222. -// -// Behavior on each client connection: -// - Connect a ModCDPClient to the existing upstream so auto-attach, -// extension discovery, and injection stay in the main client implementation. -// - For websocket upstreams, reuse that client's upstream websocket + hidden -// extension session to rewrite Mod.* / Custom.* outbound and -// Runtime.bindingCalled inbound; forward everything else unchanged. -// - For pipe, native messaging, and NATS upstreams, let ModCDPClient own the -// selected transport and proxy downstream CDP-shaped messages through it. -// - Keep mirrored upstream events private by default so vanilla CDP clients -// only see native upstream CDP messages. Set forward_mirrored_upstream_events to -// true when debugging the service-worker mirror path itself. -// -// Run as a CLI: -// node proxy.js --port 9223 --upstream-mode=ws --upstream-cdp-url=http://127.0.0.1:9222 -// node proxy.js --port 9223 --launcher-mode=local --upstream-mode=pipe -// node proxy.js --port 9223 --launcher-mode=local --upstream-mode=nativemessaging -// node proxy.js --port 9223 --launcher-mode=local --upstream-mode=reversews --upstream-reversews-bind=127.0.0.1:29292 -// node proxy.js --port 9223 --launcher-mode=local --upstream-mode=nats --upstream-nats-url=ws://127.0.0.1:4223 -// -// Or import { startProxy } and embed. - +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in Node. import http from "node:http"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import type { RawData } from "ws"; -import type { WebSocket } from "ws"; +import type { RawData, WebSocket } from "ws"; -import { ModCDPClient } from "../client/ModCDPClient.js"; -import type { - ClientConfigOptions, - InjectorOptions, - LauncherMode, - LauncherOptions, - UpstreamOptions, -} from "../client/ModCDPClient.js"; import { - UPSTREAM_EVENT_BINDING_NAME, - wrapModCDPEvaluate, - wrapModCDPAddCustomCommand, - wrapModCDPAddMiddleware, - wrapModCDPAddCustomEvent, - wrapCustomCommand, - unwrapResponseIfNeeded, - unwrapEventIfNeeded, -} from "../translate/translate.js"; -import type { - CdpCommandMessage, - CdpEventMessage, - CdpResponseMessage, - CdpMessage, - ModCDPServerOptions, - ProtocolResult, -} from "../types/modcdp.js"; -import type { ProxyConnectionState } from "./ProxyConnectionState.js"; -import { - CdpCommandMessageSchema, - CdpEventMessageSchema, - CdpResponseMessageSchema, - ModCDPAddCustomCommandParamsSchema, - ModCDPAddCustomEventObjectParamsSchema, - ModCDPAddMiddlewareParamsSchema, - ModCDPEvaluateParamsSchema, - normalizeModCDPName, -} from "../types/modcdp.js"; -import { events as RuntimeEvents } from "../types/generated/zod/Runtime.js"; -import { events as TargetEvents } from "../types/generated/zod/Target.js"; + browser_launcher_constructors, + extension_injector_constructors, + ModCDPClient, + upstream_transport_constructors, +} from "../client/ModCDPClient.js"; +import { BBExtensionInjector } from "../injector/BBExtensionInjector.js"; +import { BorrowExtensionInjector } from "../injector/BorrowExtensionInjector.js"; +import { CDPExtensionInjector } from "../injector/CDPExtensionInjector.js"; +import { CLIExtensionInjector } from "../injector/CLIExtensionInjector.js"; +import { DiscoverExtensionInjector } from "../injector/DiscoverExtensionInjector.js"; +import { InjectorConfigSchema } from "../injector/ExtensionInjector.js"; +import { BBBrowserLauncher } from "../launcher/BBBrowserLauncher.js"; +import { LocalBrowserLauncher } from "../launcher/LocalBrowserLauncher.js"; +import type { LauncherConfig } from "../launcher/BrowserLauncher.js"; +import { RemoteBrowserLauncher } from "../launcher/RemoteBrowserLauncher.js"; +import { NATSUpstreamTransport } from "../transport/NATSUpstreamTransport.js"; +import { NativeMessagingUpstreamTransport } from "../transport/NativeMessagingUpstreamTransport.js"; +import { PipeUpstreamTransport } from "../transport/PipeUpstreamTransport.js"; +import { ReverseWSUpstreamTransport } from "../transport/ReverseWSUpstreamTransport.js"; +import { parseHostPort, type UpstreamTransportConfig } from "../transport/UpstreamTransport.js"; +import { CdpCommandMessageSchema } from "../types/modcdp.js"; +import type { ModCDPClientConfig } from "../client/ModCDPClient.js"; + +browser_launcher_constructors.set("local", LocalBrowserLauncher); +browser_launcher_constructors.set("remote", RemoteBrowserLauncher); +browser_launcher_constructors.set("bb", BBBrowserLauncher); +extension_injector_constructors.set("cli", CLIExtensionInjector); +extension_injector_constructors.set("cdp", CDPExtensionInjector); +extension_injector_constructors.set("bb", BBExtensionInjector); +extension_injector_constructors.set("discover", DiscoverExtensionInjector); +extension_injector_constructors.set("borrow", BorrowExtensionInjector); +upstream_transport_constructors.set("pipe", PipeUpstreamTransport); +upstream_transport_constructors.set("reversews", ReverseWSUpstreamTransport); +upstream_transport_constructors.set("nativemessaging", NativeMessagingUpstreamTransport); +upstream_transport_constructors.set("nats", NATSUpstreamTransport); -const DEFAULT_PORT = 9223; const DEFAULT_HOST = "127.0.0.1"; +const DEFAULT_PORT = 9223; const DEFAULT_UPSTREAM = "http://127.0.0.1:9222"; -export const DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS = 1_000; -export const DEFAULT_REVERSE_WAIT_TIMEOUT_MS = 2_000; - -const DEBUG = process.env.PROXY_DEBUG === "1"; -const log = (...args) => console.log("[proxy]", ...args); -const dbg = (...args) => { - if (DEBUG) console.log("[proxy:dbg]", ...args); -}; - -const MAGIC_METHODS = new Set(["Mod.evaluate", "Mod.addCustomCommand", "Mod.addCustomEvent", "Mod.addMiddleware"]); -const ROUTE_TO_SW_RE = /^(Mod|Custom)\./; -const isWebSocketEndpoint = (url) => typeof url === "string" && /^wss?:\/\//i.test(url); - -// --- public API ------------------------------------------------------------- - -export async function startProxy({ - host = DEFAULT_HOST, - port = DEFAULT_PORT, - launcher = { launcher_mode: "remote" }, - upstream = { upstream_mode: "ws", upstream_cdp_url: DEFAULT_UPSTREAM }, - injector = { injector_mode: "auto" }, - client: clientOptions = {}, - server: serverOptions = {}, - forward_mirrored_upstream_events = false, - upstream_monitor_interval_ms = DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS, -}: { - host?: string; - port?: number; - launcher?: LauncherOptions; - upstream?: UpstreamOptions; - injector?: InjectorOptions; - client?: ClientConfigOptions; - server?: ModCDPServerOptions | null; +const DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS = 1_000; +const DEFAULT_REVERSE_WAIT_TIMEOUT_MS = 2_000; +const DEFAULT_PROXY_ROUTER_ROUTES = { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", +} as const; + +type StartProxyConfig = { + proxy_listen_host?: string; + proxy_listen_port?: number; + launcher?: LauncherConfig; + upstream?: UpstreamTransportConfig; + injector?: ModCDPClientConfig["injector"]; + router?: ModCDPClientConfig["router"]; + client_config?: ModCDPClientConfig["client_config"]; + server_config?: ModCDPClientConfig["server_config"]; forward_mirrored_upstream_events?: boolean; upstream_monitor_interval_ms?: number; -} = {}) { - const { WebSocket, WebSocketServer } = await loadWsForProxy(); - const upstreamMode = upstream.upstream_mode ?? "ws"; - const upstream_cdp_url = upstream.upstream_cdp_url ?? (launcher.launcher_mode === "local" ? null : DEFAULT_UPSTREAM); - const clientManagedUpstream = - upstreamMode === "nativemessaging" || upstreamMode === "nats" || upstreamMode === "pipe"; - const managed_reverse_upstream = - upstreamMode === "reversews" && - (launcher.launcher_mode === "local" || - launcher.launcher_mode === "bb" || - (launcher.launcher_mode === "remote" && upstream.upstream_cdp_url != null)); - const reverse_wait_timeout_ms = upstream.upstream_reversews_wait_timeout_ms ?? DEFAULT_REVERSE_WAIT_TIMEOUT_MS; - const reverseOptions = - upstreamMode === "reversews" && !managed_reverse_upstream - ? parseHostPort(upstream.upstream_reversews_bind ?? "127.0.0.1:29292", DEFAULT_HOST, 29292) - : null; - const reversePeer = reverseOptions ? createReversePeerState(reverse_wait_timeout_ms) : null; - const servesLocalDiscovery = Boolean(reversePeer) || managed_reverse_upstream || clientManagedUpstream; - let managed_reverse_cdp: ModCDPClient | null = null; - const httpServer = http.createServer(async (req, res) => { - try { - const requestUrl = req.url === "/json/version/" ? "/json/version" : req.url; - if (requestUrl === "/json/version") { - res.writeHead(200, { "content-type": "application/json" }); - res.end( - JSON.stringify({ - webSocketDebuggerUrl: `ws://${req.headers.host}/devtools/browser/proxy`, - }), - ); - return; - } - if (servesLocalDiscovery) { - if (requestUrl === "/json" || requestUrl === "/json/list" || requestUrl === "/json/list/") { - res.writeHead(200, { "content-type": "application/json" }); - res.end( - JSON.stringify([ - { - id: "proxy", - type: "browser", - title: upstreamMode === "nativemessaging" ? "ModCDP Native Messaging Proxy" : "ModCDP Reverse Proxy", - webSocketDebuggerUrl: `ws://${req.headers.host}/devtools/browser/proxy`, - }, - ]), - ); - return; - } - res.writeHead(404); - res.end("Not found."); - return; - } - if (!upstream_cdp_url || isWebSocketEndpoint(upstream_cdp_url)) { - res.writeHead(404); - res.end("HTTP discovery is unavailable for this upstream."); - return; - } - const upstreamRes = await fetch(`${upstream_cdp_url}${requestUrl}`); - const text = await upstreamRes.text(); - const contentType = upstreamRes.headers.get("content-type") || ""; - if (contentType.includes("application/json")) { - const body = JSON.parse(text); - rewriteWebSocketDebuggerUrls(body, req.headers.host); - res.writeHead(upstreamRes.status, { - "content-type": "application/json", - }); - res.end(JSON.stringify(body)); - } else { - res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers)); - res.end(text); - } - } catch (error) { - res.writeHead(502); - res.end(error.message); - } - }); +}; - let stopUpstreamMonitor: (() => void) | null = null; - let reverseWss: InstanceType | null = null; - const wss = new WebSocketServer({ server: httpServer }); - const activeCdps = new Set(); - let closing = false; - let closePromise: Promise | null = null; - const close = async () => { - if (closePromise) return closePromise; - closing = true; - closePromise = (async () => { - stopUpstreamMonitor?.(); - stopUpstreamMonitor = null; - for (const socket of wss.clients) { - try { - socket.close(); - } catch {} - } - if (reversePeer?.socket) { - try { - reversePeer.socket.close(); - } catch {} - } - await Promise.all([...activeCdps].map((cdp) => cdp.close().catch(() => {}))); - activeCdps.clear(); - await managed_reverse_cdp?.close().catch(() => {}); - managed_reverse_cdp = null; - await new Promise((resolve) => wss.close(() => resolve())); - if (reverseWss) await new Promise((resolve) => reverseWss?.close(() => resolve())); - if (httpServer.listening) await new Promise((resolve) => httpServer.close(() => resolve())); - })(); - return closePromise; - }; - wss.on("connection", (client, req) => { - log("client connected", req.url); - // attach a synchronous early-buffer immediately so we don't lose messages - // sent before bootstrap (e.g. Playwright's first commands). - const earlyBuffer = []; - const earlyHandler = (buf) => earlyBuffer.push(buf); - client.on("message", earlyHandler); - if (reversePeer) { - handleReverseConnection(client, earlyBuffer, earlyHandler, reversePeer).catch((err) => { - log("reverse connection failed:", err.message); - try { - client.close(1011, err.message.slice(0, 120)); - } catch {} - }); +async function startProxy({ + proxy_listen_host = DEFAULT_HOST, + proxy_listen_port = DEFAULT_PORT, + launcher = { launcher_mode: "remote" }, + upstream = { upstream_mode: "ws", upstream_ws_cdp_url: DEFAULT_UPSTREAM }, + injector = { injector_mode: "none" }, + router = {}, + client_config = {}, + server_config = {}, +}: StartProxyConfig = {}) { + const { WebSocketServer } = await loadWsForProxy(); + const active_clients = new Set(); + const http_server = http.createServer((req, res) => { + const request_url = req.url === "/json/version/" ? "/json/version" : req.url; + if (request_url === "/json/version") { + res.writeHead(200, { "content-type": "application/json" }); + res.end(JSON.stringify({ webSocketDebuggerUrl: `ws://${req.headers.host}/devtools/browser/proxy` })); return; } - if (managed_reverse_cdp) { - wireClientManagedConnection(client, earlyBuffer, earlyHandler, managed_reverse_cdp, false); + if (request_url === "/json" || request_url === "/json/list" || request_url === "/json/list/") { + res.writeHead(200, { "content-type": "application/json" }); + res.end( + JSON.stringify([ + { + id: "proxy", + type: "browser", + title: "ModCDP Proxy", + webSocketDebuggerUrl: `ws://${req.headers.host}/devtools/browser/proxy`, + }, + ]), + ); return; } - if (clientManagedUpstream) { - handleClientManagedConnection(client, earlyBuffer, earlyHandler, { + res.writeHead(404); + res.end("Not found."); + }); + const wss = new WebSocketServer({ noServer: true }); + http_server.on("upgrade", (req, socket, head) => { + wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req)); + }); + wss.on("connection", (socket) => { + void connectDownstream( + socket, + { launcher, - upstream: { - ...upstream, - upstream_mode: upstreamMode, - upstream_cdp_url, - }, + upstream, injector, - client: clientOptions, - server: serverOptions, - activeCdps, - }).catch((err) => { - log("client-managed connection failed:", err.message); - try { - client.close(1011, err.message.slice(0, 120)); - } catch {} - }); - return; - } - handleConnection(client, earlyBuffer, earlyHandler, { - launcher, - upstream: { ...upstream, upstream_mode: upstreamMode, upstream_cdp_url }, - injector, - client: clientOptions, - server: serverOptions, - forward_mirrored_upstream_events, - activeCdps, - onUpstreamClosed: () => { - void close().catch((error) => log("proxy close failed:", errorMessage(error))); - }, - }).catch((err) => { - log("connection failed:", err.message); - try { - client.close(1011, err.message.slice(0, 120)); - } catch {} - }); - }); - - if (reversePeer && reverseOptions) { - reverseWss = new WebSocketServer({ - host: reverseOptions.host, - port: reverseOptions.port, - }); - reverseWss.on("connection", (socket, req) => { - log("reverse candidate connected", req.socket.remoteAddress); - acceptReversePeer(reversePeer, socket); - }); - reverseWss.on("error", (error) => log("reverse listener error:", errorMessage(error))); - } - - if (managed_reverse_upstream) { - managed_reverse_cdp = new ModCDPClient({ - launcher: { ...launcher, launcher_mode: launcher.launcher_mode as LauncherMode }, - upstream: { - upstream_mode: "reversews", - upstream_reversews_bind: upstream.upstream_reversews_bind, - upstream_reversews_wait_timeout_ms: reverse_wait_timeout_ms, - }, - injector: proxyInjectorOptions(injector, "auto"), - client: { client_hydrate_aliases: false, ...clientOptions }, - server: serverOptions, - }); - try { - await managed_reverse_cdp.connect(); - } catch (error) { - await managed_reverse_cdp.close().catch(() => {}); - managed_reverse_cdp = null; - throw error; - } - } - - await new Promise((resolve) => httpServer.listen(port, host, () => resolve())); - if ( - !reversePeer && - !managed_reverse_upstream && - !clientManagedUpstream && - upstream_cdp_url && - !isWebSocketEndpoint(upstream_cdp_url) - ) { - stopUpstreamMonitor = monitorUpstream( - upstream_cdp_url, - upstream_monitor_interval_ms, - () => { - void close().catch((error) => log("proxy close failed:", errorMessage(error))); + router: { + ...router, + router_routes: { + ...DEFAULT_PROXY_ROUTER_ROUTES, + ...(router.router_routes ?? {}), + }, + }, + client_config, + server_config, }, - WebSocket, - ); - } - log( - reverseOptions - ? `listening on ws://${host}:${port}/ (reverse: ws://${reverseOptions.host}:${reverseOptions.port})` - : managed_reverse_upstream - ? `listening on ws://${host}:${port}/ (upstream: reversews:${upstream.upstream_reversews_bind ?? "127.0.0.1:29292"})` - : clientManagedUpstream - ? `listening on ws://${host}:${port}/ (upstream: ${upstreamMode})` - : `listening on ws://${host}:${port}/ (upstream: ${upstreamMode}:${upstream_cdp_url ?? "local-launch"})`, - ); - - return { - url: `http://${host}:${port}`, - cdp_url: `ws://${host}:${port}`, - close, - }; -} - -async function loadWsForProxy() { - try { - return await import("ws"); - } catch (error) { - throw new Error( - `The ModCDP proxy requires the optional "ws" package, but it is not installed.\n\n` + - `Install optional dependencies for modcdp, or add ws explicitly:\n` + - ` pnpm add ws\n` + - ` npm install ws\n` + - ` yarn add ws\n\n` + - `The ModCDP client does not require ws; it uses the native WebSocket implementation.`, - { cause: error }, + active_clients, ); - } -} - -function rewriteWebSocketDebuggerUrls(value: unknown, host: string) { - if (!value || typeof value !== "object") return; - if ("webSocketDebuggerUrl" in value && typeof value.webSocketDebuggerUrl === "string") { - value.webSocketDebuggerUrl = value.webSocketDebuggerUrl.replace(/ws:\/\/[^/]+/, `ws://${host}`); - } - for (const child of Array.isArray(value) ? value : Object.values(value)) rewriteWebSocketDebuggerUrls(child, host); -} - -function errorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - if (error && typeof error === "object" && "message" in error && typeof error.message === "string") - return error.message; - return ""; -} - -function monitorUpstream( - upstream: string, - upstream_monitor_interval_ms: number, - onClosed: () => void, - WebSocketCtor: new (url: string) => WebSocket, -) { - let stopped = false; - let socket: WebSocket | null = null; - let interval: NodeJS.Timeout | null = null; - const close = () => { - if (stopped) return; - stopped = true; - if (interval) clearInterval(interval); - try { - socket?.close(); - } catch {} - }; - const upstreamClosed = () => { - if (stopped) return; - close(); - onClosed(); - }; - - if (isWebSocketEndpoint(upstream)) return close; - - const check = async () => { - try { - const response = await fetch(`${upstream}/json/version`); - if (!response.ok) upstreamClosed(); - } catch { - upstreamClosed(); - } - }; - interval = setInterval(() => void check(), upstream_monitor_interval_ms); - void check(); - return close; -} - -type HostPort = { host: string; port: number }; -type ReverseHello = { - type: "modcdp.reverse.hello"; - role?: string; - version?: number; - extension_id?: string | null; -}; -type ReversePeerState = { - socket: WebSocket | null; - info: ReverseHello | null; - wait_timeout_ms: number; - waiters: Set<{ - resolve: (socket: WebSocket) => void; - reject: (error: Error) => void; - timeout: NodeJS.Timeout; - }>; -}; -type ReverseConnectionState = { - client: WebSocket; - reverse: WebSocket; - next_reverse_id: number; - pending: Map; - client_session_ids: Set; - bootstrapped: boolean; - closing: boolean; - queued_from_client: RawData[]; -}; - -function parseHostPort(value: string, defaultHost: string, defaultPort: number): HostPort { - const raw = String(value || ""); - const parsed = new URL(/^[a-z][a-z\d+\-.]*:\/\//i.test(raw) ? raw : `ws://${raw}`); - const host = parsed.hostname || defaultHost; - const port = Number(parsed.port || defaultPort); - if (!Number.isInteger(port) || port <= 0 || port > 65_535) throw new Error(`Invalid host:port ${value}`); - return { host, port }; -} - -function createReversePeerState(wait_timeout_ms: number): ReversePeerState { - return { - socket: null, - info: null, - wait_timeout_ms, - waiters: new Set(), - }; -} - -function isOpenSocket(socket: WebSocket | null) { - return socket != null && socket.readyState === socket.OPEN; -} - -function waitForReversePeer(state: ReversePeerState) { - if (isOpenSocket(state.socket)) return Promise.resolve(state.socket); - return new Promise((resolve, reject) => { - const waiter = { - resolve, - reject, - timeout: setTimeout(() => { - state.waiters.delete(waiter); - reject(new Error(`Timed out waiting ${state.wait_timeout_ms}ms for reverse ModCDP extension connection.`)); - }, state.wait_timeout_ms), - }; - state.waiters.add(waiter); }); -} - -function resolveReverseWaiters(state: ReversePeerState, socket: WebSocket) { - for (const waiter of state.waiters) { - clearTimeout(waiter.timeout); - waiter.resolve(socket); - } - state.waiters.clear(); -} - -function rejectReverseWaiters(state: ReversePeerState, error: Error) { - for (const waiter of state.waiters) { - clearTimeout(waiter.timeout); - waiter.reject(error); - } - state.waiters.clear(); -} - -function acceptReversePeer(state: ReversePeerState, socket: WebSocket) { - const fail = (message: string) => { - try { - socket.close(1008, message.slice(0, 120)); - } catch {} - }; - const timeout = setTimeout(() => fail("reverse hello timeout"), state.wait_timeout_ms); - socket.once("message", (buf) => { - clearTimeout(timeout); - let hello: ReverseHello; - try { - const parsed = JSON.parse(String(buf)); - if (parsed?.type !== "modcdp.reverse.hello") throw new Error("missing hello type"); - hello = parsed; - } catch (error) { - fail(`invalid reverse hello: ${errorMessage(error)}`); - return; - } - - if (isOpenSocket(state.socket) && state.socket !== socket) { - try { - state.socket?.close(1012, "reverse peer replaced"); - } catch {} - } - state.socket = socket; - state.info = hello; - log("reverse extension connected", hello.extension_id || "(unknown extension)"); - socket.addEventListener("close", () => { - if (state.socket !== socket) return; - state.socket = null; - state.info = null; - rejectReverseWaiters(state, new Error("Reverse ModCDP extension connection closed.")); - }); - socket.addEventListener("error", () => { - if (state.socket !== socket) return; - state.socket = null; - state.info = null; - rejectReverseWaiters(state, new Error("Reverse ModCDP extension connection errored.")); - }); - resolveReverseWaiters(state, socket); - }); -} - -// --- per-connection bridging ---------------------------------------------- - -async function handleReverseConnection( - client: WebSocket, - earlyBuffer: RawData[], - earlyHandler: (buf: RawData) => void, - reversePeer: ReversePeerState, -) { - const reverse = await waitForReversePeer(reversePeer); - if (!isOpenSocket(reverse)) throw new Error("Reverse ModCDP extension connection is not open."); - - const state: ReverseConnectionState = { - client, - reverse, - next_reverse_id: 1_000_000, - pending: new Map(), - client_session_ids: new Set(), - bootstrapped: false, - closing: false, - queued_from_client: [], - }; - - const onReverseMessage = (event) => { - let msg: CdpResponseMessage | CdpEventMessage; - try { - const parsed = JSON.parse(String(event.data)); - msg = "id" in parsed ? CdpResponseMessageSchema.parse(parsed) : CdpEventMessageSchema.parse(parsed); - } catch (e) { - log("reverse parse error", e.message); - return; - } - dbg("reverse->", msg.id ?? "", msg.method ?? "(response)", msg.sessionId ?? ""); - handleReverseUpstreamMessage(state, msg); + await new Promise((resolve) => http_server.listen(proxy_listen_port, proxy_listen_host, () => resolve())); + const close = async () => { + for (const client of active_clients) await client.close().catch(() => {}); + active_clients.clear(); + for (const socket of wss.clients) socket.close(); + await new Promise((resolve) => wss.close(() => resolve())); + await new Promise((resolve) => http_server.close(() => resolve())); }; - reverse.addEventListener("message", onReverseMessage); - reverse.addEventListener("close", () => { - reverse.removeEventListener("message", onReverseMessage); - state.closing = true; - try { - client.close(); - } catch {} - }); - reverse.addEventListener("error", () => { - reverse.removeEventListener("message", onReverseMessage); - state.closing = true; - try { - client.close(1011, "reverse upstream error"); - } catch {} - }); - client.on("close", () => { - state.closing = true; - reverse.removeEventListener("message", onReverseMessage); - }); - - client.off("message", earlyHandler); - for (const buf of earlyBuffer) state.queued_from_client.push(buf); - client.on("message", (buf) => { - if (!state.bootstrapped) { - state.queued_from_client.push(buf); - return; - } - handleReverseClientMessage(state, buf); - }); - state.bootstrapped = true; - for (const buf of state.queued_from_client) handleReverseClientMessage(state, buf); - state.queued_from_client = []; -} - -function handleReverseClientMessage(state: ReverseConnectionState, buf: RawData) { - let msg: CdpCommandMessage; - try { - msg = CdpCommandMessageSchema.parse(JSON.parse(String(buf))); - } catch (e) { - log("client parse error", e.message); - return; - } - dbg("client->reverse", msg.id ?? "", msg.method, msg.sessionId ?? ""); - const upId = state.next_reverse_id++; - state.pending.set(upId, { - client_id: msg.id, - client_session_id: msg.sessionId || null, - }); - const out: CdpCommandMessage = { - id: upId, - method: msg.method, - params: msg.params ?? {}, + console.log(`listening on ws://${proxy_listen_host}:${proxy_listen_port}/`); + return { + url: `http://${proxy_listen_host}:${proxy_listen_port}`, + cdp_url: `ws://${proxy_listen_host}:${proxy_listen_port}`, + close, }; - if (msg.sessionId) out.sessionId = msg.sessionId; - state.reverse.send(JSON.stringify(out)); -} - -function handleReverseUpstreamMessage(state: ReverseConnectionState, msg: CdpResponseMessage | CdpEventMessage) { - if ("id" in msg && typeof msg.id === "number") { - const response = CdpResponseMessageSchema.parse(msg); - const pending = state.pending.get(response.id); - if (!pending) return; - state.pending.delete(response.id); - const out: CdpResponseMessage = response.error - ? { id: pending.client_id, error: response.error } - : { id: pending.client_id, result: response.result ?? {} }; - if (pending.client_session_id) out.sessionId = pending.client_session_id; - sendReverseToClient(state, out); - return; - } - - const event = CdpEventMessageSchema.parse(msg); - const eventSessionId = - event.params && - typeof event.params === "object" && - "sessionId" in event.params && - typeof event.params.sessionId === "string" - ? event.params.sessionId - : event.sessionId || null; - if (event.method === "Target.attachedToTarget" && eventSessionId) { - state.client_session_ids.add(eventSessionId); - } else if (event.method === "Target.detachedFromTarget" && eventSessionId) { - state.client_session_ids.delete(eventSessionId); - } - - sendReverseToClient(state, event); - if (!event.sessionId) { - for (const sessionId of state.client_session_ids) sendReverseToClient(state, { ...event, sessionId }); - } -} - -function sendReverseToClient(state: ReverseConnectionState, obj: CdpMessage) { - if (DEBUG) - dbg("client<-reverse", "id" in obj ? obj.id : "", "method" in obj ? obj.method : "(response)", obj.sessionId ?? ""); - state.client.send(JSON.stringify(obj)); } -async function handleConnection( - client: WebSocket, - earlyBuffer: RawData[], - earlyHandler: (buf: RawData) => void, - { - launcher, - upstream, - injector, - client: clientOptions, - server, - forward_mirrored_upstream_events, - onUpstreamClosed, - activeCdps, - }: { - launcher: LauncherOptions; - upstream: UpstreamOptions; - injector: InjectorOptions; - client?: ClientConfigOptions; - server?: ModCDPServerOptions | null; - forward_mirrored_upstream_events: boolean; - activeCdps: Set; - onUpstreamClosed: () => void; - }, -) { +async function connectDownstream(socket: WebSocket, config: ModCDPClientConfig, active_clients: Set) { + const queued_raw_messages: RawData[] = []; + let connected = false; const cdp = new ModCDPClient({ - launcher: { ...launcher, launcher_mode: launcher.launcher_mode as LauncherMode }, - upstream: { upstream_mode: "ws", upstream_cdp_url: upstream.upstream_cdp_url }, - injector: proxyInjectorOptions(injector, "auto"), - client: { client_hydrate_aliases: false, ...clientOptions }, - server, - }); - activeCdps.add(cdp); - try { - await cdp.connect(); - } catch (error) { - activeCdps.delete(cdp); - await cdp.close().catch(() => {}); - throw error; - } - let closeCdpPromise: Promise | null = null; - const closeCdp = () => { - closeCdpPromise ??= cdp - .close() - .catch(() => {}) - .finally(() => activeCdps.delete(cdp)); - return closeCdpPromise; - }; - const upstream_socket = (cdp.transport as unknown as { ws?: WebSocket } | null)?.ws ?? null; - if (!upstream_socket) { - await closeCdp(); - throw new Error("ModCDPClient connected without an upstream websocket."); - } - - // per-connection state - const state: ProxyConnectionState = { - client, - upstream: upstream_socket, - next_upstream_id: 1_000_000, - pending: new Map(), // upstream_id -> { kind, client_id?, client_session_id?, ... } - ext_session_id: cdp.ext_session_id, - ext_target_id: cdp.ext_target_id, - ext_execution_context_id: cdp.ext_execution_context_id, - hidden_session_ids: new Set(), // sessions we attached for ourselves - hidden_target_ids: new Set(), // SW target the client must never see - target_session_ids: cdp.auto_target_sessions, - client_session_ids: new Set(), // session ids the client has attached - forward_mirrored_upstream_events: forward_mirrored_upstream_events, - bootstrapped: false, - closing: false, - queued_from_client: [], - }; - if (cdp.ext_session_id) state.hidden_session_ids.add(cdp.ext_session_id); - if (cdp.ext_target_id) state.hidden_target_ids.add(cdp.ext_target_id); - - upstream_socket.addEventListener("message", (event) => { - let msg: CdpResponseMessage | CdpEventMessage; - try { - const parsed = JSON.parse(String(event.data)); - msg = "id" in parsed ? CdpResponseMessageSchema.parse(parsed) : CdpEventMessageSchema.parse(parsed); - } catch (e) { - log("upstream parse error", e.message); - return; - } - dbg("upstream->", msg.id ?? "", msg.method ?? "(response)", msg.sessionId ?? ""); - handleUpstreamMessage(state, msg); + ...config, + client_config: { client_hydrate_aliases: false, ...(config.client_config ?? {}) }, }); - upstream_socket.addEventListener("close", () => { - const closedDuringDownstreamShutdown = state.closing; - state.closing = true; - try { - client.close(); - } catch {} - if (!closedDuringDownstreamShutdown) onUpstreamClosed(); - }); - upstream_socket.addEventListener("error", (event) => { - if (state.closing || client.readyState === client.CLOSING || client.readyState === client.CLOSED) { - dbg("upstream ws error during shutdown"); + active_clients.add(cdp); + socket.on("message", (raw) => { + if (!connected) { + queued_raw_messages.push(raw); return; } - log("upstream ws error", errorMessage(event)); - try { - client.close(1011, "upstream error"); - } catch {} - onUpstreamClosed(); - }); - client.on("close", () => { - state.closing = true; - void closeCdp(); + void handleDownstreamMessage(socket, cdp, raw); }); - log(`injector ${cdp.connect_timing?.injector_source} (${cdp.extension_id}); ext session ${cdp.ext_session_id}`); - - // Swap the early-buffer handler for the real one. Drain anything that - // arrived before we got here. - client.off("message", earlyHandler); - for (const buf of earlyBuffer) state.queued_from_client.push(buf); - client.on("message", (buf) => { - if (!state.bootstrapped) { - state.queued_from_client.push(buf); - return; - } - handleClientMessage(state, buf); + socket.on("close", () => { + active_clients.delete(cdp); + void cdp.close().catch(() => {}); }); - state.bootstrapped = true; - for (const buf of state.queued_from_client) handleClientMessage(state, buf); - state.queued_from_client = []; -} - -async function handleClientManagedConnection( - client: WebSocket, - earlyBuffer: RawData[], - earlyHandler: (buf: RawData) => void, - { - launcher, - upstream, - injector, - client: clientOptions, - server, - activeCdps, - }: { - launcher: LauncherOptions; - upstream: UpstreamOptions; - injector: InjectorOptions; - client?: ClientConfigOptions; - server?: ModCDPServerOptions | null; - activeCdps: Set; - }, -) { - const cdp = new ModCDPClient({ - launcher: { ...launcher, launcher_mode: (launcher.launcher_mode ?? "none") as LauncherMode }, - upstream: { - upstream_mode: upstream.upstream_mode as "pipe" | "nativemessaging" | "nats", - upstream_cdp_url: upstream.upstream_cdp_url, - upstream_nats_url: upstream.upstream_nats_url, - upstream_nats_subject_prefix: upstream.upstream_nats_subject_prefix, - upstream_nats_wait_timeout_ms: upstream.upstream_nats_wait_timeout_ms, - upstream_nativemessaging_manifest: upstream.upstream_nativemessaging_manifest, - upstream_nativemessaging_manifests: upstream.upstream_nativemessaging_manifests, - upstream_nativemessaging_host_name: upstream.upstream_nativemessaging_host_name, - upstream_nativemessaging_wait_timeout_ms: upstream.upstream_nativemessaging_wait_timeout_ms, - upstream_ws_connect_error_settle_timeout_ms: upstream.upstream_ws_connect_error_settle_timeout_ms, - }, - injector: proxyInjectorOptions(injector, "none"), - client: { client_hydrate_aliases: false, ...clientOptions }, - server, - }); - activeCdps.add(cdp); try { await cdp.connect(); } catch (error) { - activeCdps.delete(cdp); + console.error(`[ModCDP proxy] upstream connect failed: ${errorMessage(error)}`); + active_clients.delete(cdp); await cdp.close().catch(() => {}); - throw error; - } - wireClientManagedConnection(client, earlyBuffer, earlyHandler, cdp, true, activeCdps); -} - -function wireClientManagedConnection( - client: WebSocket, - earlyBuffer: RawData[], - earlyHandler: (buf: RawData) => void, - cdp: ModCDPClient, - close_cdp_on_client_close: boolean, - activeCdps?: Set, -) { - const event_listener = (event_name: string | symbol, payload: unknown, session_id?: string | null) => { - const event: CdpEventMessage = { - method: String(event_name), - params: (payload ?? {}) as Record, - }; - if (typeof session_id === "string" && session_id) event.sessionId = session_id; - sendRawClientMessage(client, event); - }; - cdp.on("*", event_listener); - - client.off("message", earlyHandler); - const handle = (buf: RawData) => { - let msg: CdpCommandMessage; - try { - msg = CdpCommandMessageSchema.parse(JSON.parse(String(buf))); - } catch (e) { - log("client parse error", e.message); - return; - } - const service_worker_params = - msg.sessionId && msg.params && typeof msg.params === "object" && !("cdpSessionId" in msg.params) - ? { ...msg.params, cdpSessionId: msg.sessionId } - : (msg.params ?? {}); - const command_promise = ROUTE_TO_SW_RE.test(msg.method) - ? cdp.send(msg.method, service_worker_params) - : cdp.sendRaw(msg.method, msg.params ?? {}, msg.sessionId ?? null); - void command_promise - .then((result) => - sendRawClientMessage(client, { - id: msg.id, - result: result ?? {}, - ...(msg.sessionId ? { sessionId: msg.sessionId } : {}), - }), - ) - .catch((error) => - sendRawClientMessage(client, { - id: msg.id, - error: { code: -32000, message: errorMessage(error) }, - ...(msg.sessionId ? { sessionId: msg.sessionId } : {}), - }), - ); - }; - client.on("message", handle); - for (const buf of earlyBuffer) handle(buf); - client.on("close", () => { - cdp.off("*", event_listener); - if (close_cdp_on_client_close) - void cdp - .close() - .catch(() => {}) - .finally(() => activeCdps?.delete(cdp)); - }); -} - -function proxyInjectorOptions(injector: InjectorOptions, default_mode: NonNullable) { - return { - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - ...injector, - injector_mode: injector.injector_mode ?? default_mode, - }; -} - -function sendRawClientMessage(client: WebSocket, obj: unknown) { - client.send(JSON.stringify(obj)); -} - -function handleClientMessage(state: ProxyConnectionState, buf: RawData) { - let msg: CdpCommandMessage; - try { - msg = CdpCommandMessageSchema.parse(JSON.parse(String(buf))); - } catch (e) { - log("client parse error", e.message); + socket.close(1011, errorMessage(error).slice(0, 120)); return; } - dbg("client->", msg.id ?? "", msg.method, msg.sessionId ?? ""); - const { id, method, params = {}, sessionId } = msg; - - // route a Mod.* / Custom.* command into a Runtime.callFunctionOn against the - // hidden ext session, while remembering the originating client id+session - // so the response can be steered back to the right Playwright CDPSession. - if (MAGIC_METHODS.has(method) || ROUTE_TO_SW_RE.test(method)) { - const upId = state.next_upstream_id++; - let runtimeParams; - let unwrap: "runtime" | "runtime_json" = "runtime"; - if (method === "Mod.evaluate") { - const evaluateParams = ModCDPEvaluateParamsSchema.parse(params ?? {}); - runtimeParams = wrapModCDPEvaluate({ - ...evaluateParams, - cdpSessionId: evaluateParams.cdpSessionId ?? sessionId ?? null, - }); - } else if (method === "Mod.addCustomCommand") { - runtimeParams = wrapModCDPAddCustomCommand(ModCDPAddCustomCommandParamsSchema.parse(params ?? {})); - } else if (method === "Mod.addCustomEvent") { - const eventParams = ModCDPAddCustomEventObjectParamsSchema.parse(params ?? {}); - runtimeParams = wrapModCDPAddCustomEvent({ - name: normalizeModCDPName(eventParams.name), - }); - } else if (method === "Mod.addMiddleware") { - runtimeParams = wrapModCDPAddMiddleware(ModCDPAddMiddlewareParamsSchema.parse(params ?? {})); - } else { - const cdpSessionId = - params && typeof params === "object" && "cdpSessionId" in params && typeof params.cdpSessionId === "string" - ? params.cdpSessionId - : (sessionId ?? null); - runtimeParams = wrapCustomCommand(method, params, cdpSessionId); - unwrap = "runtime_json"; - } - state.pending.set(upId, { - kind: "modcdp_eval", - client_id: id, - client_session_id: sessionId || null, - unwrap, - }); - if (state.ext_execution_context_id != null) runtimeParams.executionContextId = state.ext_execution_context_id; - state.upstream.send( + cdp.on("*", (event_name, payload, session_id) => { + if (socket.readyState !== socket.OPEN) return; + socket.send( JSON.stringify({ - id: upId, - method: "Runtime.callFunctionOn", - params: runtimeParams, - sessionId: state.ext_session_id, + method: String(event_name), + params: payload ?? {}, + ...(session_id ? { sessionId: session_id } : {}), }), ); - return; - } - - // passthrough - const upId = state.next_upstream_id++; - state.pending.set(upId, { - kind: "passthrough", - client_id: id, - client_session_id: sessionId || null, }); - const out: CdpCommandMessage = { id: upId, method, params }; - if (sessionId) out.sessionId = sessionId; - state.upstream.send(JSON.stringify(out)); + connected = true; + for (const raw of queued_raw_messages.splice(0)) void handleDownstreamMessage(socket, cdp, raw); } -function handleUpstreamMessage(state: ProxyConnectionState, msg: CdpResponseMessage | CdpEventMessage) { - // response - if ("id" in msg && typeof msg.id === "number") { - const response = CdpResponseMessageSchema.parse(msg); - const p = state.pending.get(response.id); - if (!p) return; - state.pending.delete(response.id); - - if (p.kind === "internal") { - if (response.error) p.reject?.(new Error(response.error.message)); - else p.resolve?.((response.result === undefined ? {} : response.result) as ProtocolResult); - return; - } - - const replyToClient = (extra: Omit) => { - const out: CdpResponseMessage = { id: p.client_id ?? 0, ...extra }; - if (p.client_session_id) out.sessionId = p.client_session_id; - sendToClient(state, out); - }; - - if (p.kind === "modcdp_eval") { - try { - replyToClient({ - result: - unwrapResponseIfNeeded( - (response.result === undefined ? {} : response.result) as ProtocolResult, - p.unwrap ?? "runtime", - ) ?? {}, - }); - } catch (e) { - replyToClient({ error: { code: -32000, message: e.message } }); - } - return; - } - // passthrough - if (response.error) replyToClient({ error: response.error }); - else replyToClient({ result: response.result ?? {} }); - return; - } - - const event = CdpEventMessageSchema.parse(msg); - - if (event.method === "Target.attachedToTarget") { - const attached = TargetEvents["Target.attachedToTarget"].parse(event.params || {}); - if (attached.sessionId) { - state.target_session_ids.set(attached.targetInfo.targetId, attached.sessionId); - if (state.hidden_target_ids.has(attached.targetInfo.targetId)) state.hidden_session_ids.add(attached.sessionId); - } - } else if (event.method === "Target.detachedFromTarget") { - const detached = TargetEvents["Target.detachedFromTarget"].parse(event.params || {}); - if (detached.sessionId) { - state.hidden_session_ids.delete(detached.sessionId); - for (const [targetId, sessionId] of state.target_session_ids) { - if (sessionId !== detached.sessionId) continue; - state.target_session_ids.delete(targetId); - break; - } - } - } - - // event - if (event.method === "Runtime.bindingCalled" && event.sessionId === state.ext_session_id) { - const binding = RuntimeEvents["Runtime.bindingCalled"].parse(event.params || {}); - if (binding.name === UPSTREAM_EVENT_BINDING_NAME && !state.forward_mirrored_upstream_events) return; - const u = unwrapEventIfNeeded(event.method, binding, event.sessionId || null, null); - if (!u) return; - // emit to root + every known client session, so any CDPSession listener - // (Playwright per-target sessions) fires. - sendToClient(state, { - method: u.event, - params: (u.data ?? {}) as Record, - }); - for (const sid of state.client_session_ids) { - sendToClient(state, { - method: u.event, - params: (u.data ?? {}) as Record, - sessionId: sid, - }); - } - return; - } - - // hide bridge-attached session traffic from the client - if (event.sessionId && state.hidden_session_ids.has(event.sessionId)) return; - - // If the client's auto-attach creates a fresh orphan session against the - // hidden SW target, hide that session and detach it upstream. This MUST run - // before the generic hidden_target_ids drop below: for an attachedToTarget - // event, msg.params.targetInfo.targetId is the SW target (which we want to - // act on), not a target the client owns. - if (event.method === "Target.attachedToTarget") { - const attached = TargetEvents["Target.attachedToTarget"].parse(event.params || {}); - if (state.hidden_target_ids.has(attached.targetInfo.targetId)) { - const orphan = attached.sessionId; - if (orphan && orphan !== state.ext_session_id) { - state.hidden_session_ids.add(orphan); - const upId = state.next_upstream_id++; - state.pending.set(upId, { - kind: "internal", - resolve: () => {}, - reject: () => {}, - }); - state.upstream.send( - JSON.stringify({ - id: upId, - method: "Target.detachFromTarget", - params: { sessionId: orphan }, - }), - ); - } - return; - } - } - - // hide all other events about the extension SW target. - const targetId = - event.params && - typeof event.params === "object" && - "targetInfo" in event.params && - event.params.targetInfo && - typeof event.params.targetInfo === "object" && - "targetId" in event.params.targetInfo && - typeof event.params.targetInfo.targetId === "string" - ? event.params.targetInfo.targetId - : event.params && - typeof event.params === "object" && - "targetId" in event.params && - typeof event.params.targetId === "string" - ? event.params.targetId - : null; - if (targetId && state.hidden_target_ids.has(targetId)) return; - const eventSessionId = - event.params && - typeof event.params === "object" && - "sessionId" in event.params && - typeof event.params.sessionId === "string" - ? event.params.sessionId - : null; - if (event.method.startsWith("Target.detached") && eventSessionId && state.hidden_session_ids.has(eventSessionId)) - return; - - if (!state.bootstrapped) return; // do not leak bootstrap events - - if (event.method === "Target.attachedToTarget" && eventSessionId) { - state.client_session_ids.add(eventSessionId); - } - if (event.method === "Target.detachedFromTarget" && eventSessionId) { - state.client_session_ids.delete(eventSessionId); +async function handleDownstreamMessage(socket: WebSocket, cdp: ModCDPClient, raw: RawData) { + const message = CdpCommandMessageSchema.parse(JSON.parse(String(raw))); + try { + const result = await cdp.send(message.method, message.params ?? {}, message.sessionId ?? null); + socket.send( + JSON.stringify({ + id: message.id, + result: result ?? {}, + ...(message.sessionId ? { sessionId: message.sessionId } : {}), + }), + ); + } catch (error) { + socket.send( + JSON.stringify({ + id: message.id, + error: { code: -32000, message: errorMessage(error) }, + ...(message.sessionId ? { sessionId: message.sessionId } : {}), + }), + ); } - - sendToClient(state, event); -} - -function sendToClient(state: ProxyConnectionState, obj: CdpMessage) { - if (DEBUG) - dbg("client<-", "id" in obj ? obj.id : "", "method" in obj ? obj.method : "(response)", obj.sessionId ?? ""); - state.client.send(JSON.stringify(obj)); } -// --- CLI ------------------------------------------------------------------- - -export function runProxyCli(args = process.argv.slice(2)) { +function runProxyCli(args = process.argv.slice(2)) { const argv = parseProxyArgs(args); - const listen = argv.listen ? parseHostPort(String(argv.listen), DEFAULT_HOST, DEFAULT_PORT) : null; - const host = listen?.host ?? DEFAULT_HOST; - const port = listen?.port ?? Number(argv.port || DEFAULT_PORT); - const launcher_mode = String(argv["launcher-mode"] || "remote"); - const upstream_mode = String(argv["upstream-mode"] || "ws"); - const explicit_upstream_cdp_url = - typeof argv["upstream-cdp-url"] === "string" && argv["upstream-cdp-url"] !== "true" - ? String(argv["upstream-cdp-url"]) - : null; - const injector_extension_path = - typeof argv["injector-extension-path"] === "string" && argv["injector-extension-path"] !== "true" - ? path.resolve(argv["injector-extension-path"]) - : null; - const forward_mirrored_upstream_events = argv["forward-mirrored-upstream-events"] === "true"; - const clientConfig = - typeof argv.client === "string" && argv.client !== "true" - ? JSON.parse(argv.client) - : typeof argv["client-routes"] === "string" && argv["client-routes"] !== "true" - ? { client_routes: JSON.parse(argv["client-routes"]) } - : {}; - const serverConfig = - typeof argv.server === "string" && argv.server !== "true" - ? JSON.parse(argv.server) - : typeof argv["server-routes"] === "string" && argv["server-routes"] !== "true" - ? { server_routes: JSON.parse(argv["server-routes"]) } - : {}; - const proxyPromise = startProxy({ - host, - port, - launcher: { - launcher_mode: launcher_mode as LauncherOptions["launcher_mode"], - launcher_executable_path: - typeof argv["launcher-executable-path"] === "string" && argv["launcher-executable-path"] !== "true" - ? String(argv["launcher-executable-path"]) - : null, - launcher_user_data_dir: - typeof argv["launcher-user-data-dir"] === "string" && argv["launcher-user-data-dir"] !== "true" - ? String(argv["launcher-user-data-dir"]) - : null, - launcher_options: - typeof argv["launcher-options"] === "string" && argv["launcher-options"] !== "true" - ? JSON.parse(argv["launcher-options"]) - : {}, - }, - upstream: { - upstream_mode: upstream_mode as UpstreamOptions["upstream_mode"], - upstream_cdp_url: - explicit_upstream_cdp_url ?? (upstream_mode === "ws" && launcher_mode !== "local" ? DEFAULT_UPSTREAM : null), - upstream_nats_url: - typeof argv["upstream-nats-url"] === "string" && argv["upstream-nats-url"] !== "true" - ? String(argv["upstream-nats-url"]) - : null, - upstream_nats_subject_prefix: - typeof argv["upstream-nats-subject-prefix"] === "string" && argv["upstream-nats-subject-prefix"] !== "true" - ? String(argv["upstream-nats-subject-prefix"]) - : null, - upstream_nats_wait_timeout_ms: - typeof argv["upstream-nats-wait-timeout-ms"] === "string" && argv["upstream-nats-wait-timeout-ms"] !== "true" - ? Number(argv["upstream-nats-wait-timeout-ms"]) - : undefined, - upstream_reversews_bind: - typeof argv["upstream-reversews-bind"] === "string" && argv["upstream-reversews-bind"] !== "true" - ? String(argv["upstream-reversews-bind"]) - : "127.0.0.1:29292", - upstream_reversews_wait_timeout_ms: - typeof argv["upstream-reversews-wait-timeout-ms"] === "string" && - argv["upstream-reversews-wait-timeout-ms"] !== "true" - ? Number(argv["upstream-reversews-wait-timeout-ms"]) - : null, - upstream_nativemessaging_manifest: - typeof argv["upstream-nativemessaging-manifest"] === "string" && - argv["upstream-nativemessaging-manifest"] !== "true" - ? String(argv["upstream-nativemessaging-manifest"]) - : null, - upstream_nativemessaging_manifests: - typeof argv["upstream-nativemessaging-manifests"] === "string" && - argv["upstream-nativemessaging-manifests"] !== "true" - ? parseStringList(argv["upstream-nativemessaging-manifests"]) - : null, - upstream_nativemessaging_host_name: - typeof argv["upstream-nativemessaging-host-name"] === "string" && - argv["upstream-nativemessaging-host-name"] !== "true" - ? String(argv["upstream-nativemessaging-host-name"]) - : null, - upstream_nativemessaging_wait_timeout_ms: - typeof argv["upstream-nativemessaging-wait-timeout-ms"] === "string" && - argv["upstream-nativemessaging-wait-timeout-ms"] !== "true" - ? Number(argv["upstream-nativemessaging-wait-timeout-ms"]) - : undefined, - }, - injector: { - injector_mode: String(argv["injector-mode"] || "auto") as InjectorOptions["injector_mode"], - injector_extension_path, - injector_extension_id: optionalStringArg(argv, "injector-extension-id"), - injector_service_worker_url_includes: optionalStringListArg(argv, "injector-service-worker-url-includes"), - injector_service_worker_url_suffixes: optionalStringListArg(argv, "injector-service-worker-url-suffixes"), - injector_trust_service_worker_target: optionalBooleanArg(argv, "injector-trust-service-worker-target"), - injector_require_service_worker_target: optionalBooleanArg(argv, "injector-require-service-worker-target"), - injector_service_worker_ready_expression: optionalStringArg(argv, "injector-service-worker-ready-expression"), - injector_execution_context_timeout_ms: optionalNumberArg(argv, "injector-execution-context-timeout-ms"), - injector_service_worker_probe_timeout_ms: optionalNumberArg(argv, "injector-service-worker-probe-timeout-ms"), - injector_service_worker_ready_timeout_ms: optionalNumberArg(argv, "injector-service-worker-ready-timeout-ms"), - injector_service_worker_poll_interval_ms: optionalNumberArg(argv, "injector-service-worker-poll-interval-ms"), - injector_target_session_poll_interval_ms: optionalNumberArg(argv, "injector-target-session-poll-interval-ms"), - }, - client: clientConfig as ClientConfigOptions, - server: serverConfig as ModCDPServerOptions, - forward_mirrored_upstream_events, + const bind = typeof argv.bind === "string" ? parseHostPort(argv.bind, DEFAULT_HOST, DEFAULT_PORT) : null; + const proxy_promise = startProxy({ + proxy_listen_host: (argv.proxy_listen_host as string | undefined) ?? bind?.host ?? DEFAULT_HOST, + proxy_listen_port: + (argv.proxy_listen_port as number | undefined) ?? (argv.port as number | undefined) ?? bind?.port ?? DEFAULT_PORT, + launcher: configGroup(argv, "launcher"), + upstream: configGroup(argv, "upstream"), + injector: configGroup(argv, "injector"), + router: configGroup(argv, "router"), + client_config: configGroup(argv, "client_config", "client"), + server_config: configGroup(argv, "server_config", "server"), }); - let shuttingDown = false; const shutdown = async () => { - if (shuttingDown) return; - shuttingDown = true; try { - const proxy = await proxyPromise; + const proxy = await proxy_promise; await proxy.close(); - } catch {} - process.exit(0); + } finally { + process.exit(0); + } }; process.once("SIGINT", () => void shutdown()); process.once("SIGTERM", () => void shutdown()); - proxyPromise.catch((e) => { - console.error(e); + proxy_promise.catch((error) => { + console.error(error); process.exitCode = 1; }); } -if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - runProxyCli(); +async function loadWsForProxy() { + try { + return await import("ws"); + } catch (error) { + throw new Error(`The ModCDP proxy requires the optional "ws" package: ${errorMessage(error)}`); + } } function parseProxyArgs(args: string[]) { - const result: Record = {}; + const result: Record = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (!arg.startsWith("--")) continue; const raw = arg.slice(2); const equals = raw.indexOf("="); - if (equals >= 0) { - result[raw.slice(0, equals)] = raw.slice(equals + 1); - continue; + if (equals >= 0) result[raw.slice(0, equals).replaceAll("-", "_")] = parseCliValue(raw.slice(equals + 1)); + else { + const next = args[i + 1]; + result[raw.replaceAll("-", "_")] = next && !next.startsWith("--") ? parseCliValue(next) : true; + if (next && !next.startsWith("--")) i += 1; } - const next = args[i + 1]; - result[raw] = next && !next.startsWith("--") ? next : "true"; - if (next && !next.startsWith("--")) i += 1; } return result; } -function optionalStringArg(argv: Record, name: string) { - const value = argv[name]; - return typeof value === "string" && value !== "true" ? value : undefined; +function configGroup(argv: Record, group: string, field_prefix = group) { + const config = + typeof argv[group] === "object" && argv[group] !== null && !Array.isArray(argv[group]) ? argv[group] : {}; + const prefix = `${field_prefix}_`; + const entries = Object.entries(argv) + .filter(([key]) => key !== group && key.startsWith(prefix)) + .map(([key, value]) => [key, value]); + return { ...config, ...Object.fromEntries(entries) }; } -function optionalNumberArg(argv: Record, name: string) { - const value = optionalStringArg(argv, name); - return value == null ? undefined : Number(value); +function parseCliValue(value: string) { + const trimmed = value.trim(); + if ( + trimmed === "true" || + trimmed === "false" || + trimmed === "null" || + trimmed.startsWith("{") || + trimmed.startsWith("[") || + /^-?\d+(\.\d+)?$/.test(trimmed) + ) { + return JSON.parse(trimmed); + } + return value; } -function optionalBooleanArg(argv: Record, name: string) { - const value = argv[name]; - if (value === undefined) return undefined; - if (value === "true") return true; - if (value === "false") return false; - return Boolean(value); +function errorMessage(error: unknown) { + return error instanceof Error ? error.message : String(error); } -function optionalStringListArg(argv: Record, name: string) { - const value = optionalStringArg(argv, name); - return value == null ? undefined : parseStringList(value); -} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) runProxyCli(); -function parseStringList(value: string) { - const trimmed = value.trim(); - if (trimmed.startsWith("[")) return JSON.parse(trimmed) as string[]; - return trimmed - .split(",") - .map((entry) => entry.trim()) - .filter(Boolean); -} +export { DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS, DEFAULT_REVERSE_WAIT_TIMEOUT_MS, startProxy, runProxyCli }; diff --git a/js/src/router/AutoSessionRouter.ts b/js/src/router/AutoSessionRouter.ts index 1315b2ce..a668bed1 100644 --- a/js/src/router/AutoSessionRouter.ts +++ b/js/src/router/AutoSessionRouter.ts @@ -1,124 +1,727 @@ -import type { ProtocolParams, ProtocolResult } from "../types/modcdp.js"; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/router/AutoSessionRouter.py +// - ./go/modcdp/router/AutoSessionRouter.go +import type { cdp } from "../types/generated/cdp.js"; +import type { z } from "zod"; +import * as DOM from "../types/generated/zod/DOM.js"; +import * as Page from "../types/generated/zod/Page.js"; +import * as Runtime from "../types/generated/zod/Runtime.js"; +import * as Target from "../types/generated/zod/Target.js"; +import type { TargetRoute, UpstreamTransport } from "../transport/UpstreamTransport.js"; +import type { CDPTypes } from "../types/CDPTypes.js"; +import { modCDPToJSON } from "../types/toJSON.js"; +import { + CdpDebuggeeCommandParamsSchema, + type CdpDebuggeeCommandParams, + type ModCDPGetTopologyParams, + type ModCDPRouterConfig, + ModCDPRouterConfigSchema, + type ModCDPRoutes, + type ModCDPTopology, + type ModCDPTopologyDomRoot, + type ModCDPTopologyExecutionContext, + type ModCDPTopologyFrame, + type ModCDPTopologyTarget, + type ProtocolParams, + type ProtocolResult, +} from "../types/modcdp.js"; -type SendCDP = (method: string, params?: ProtocolParams, session_id?: string | null) => Promise; +type FrameTree = cdp.types.ts.Page.FrameTree; +type DomNode = cdp.types.ts.DOM.Node; +type TargetInfo = cdp.types.ts.Target.TargetInfo; +type ContextSelector = { + world: string; + worldName?: string; +}; type ExecutionContextWaiter = { - resolve: (context_id: number) => void; + resolve: (context: ModCDPTopologyExecutionContext) => void; reject: (error: Error) => void; timeout: ReturnType; + matches: (context: ModCDPTopologyExecutionContext) => boolean; +}; +type AutoSessionRouterConfig = z.input & { + upstream: UpstreamTransport; + types: CDPTypes; }; -const max_detached_session_guards = 1024; +const topologyConcurrency = 8; +const piercerWorldName = "__modcdp_piercer__"; +const DEFAULT_CLIENT_ROUTER_ROUTES = { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "service_worker", +} satisfies ModCDPRoutes; + +const targetAutoAttachParams = { + autoAttach: true, + waitForDebuggerOnStart: false, + flatten: true, +} satisfies cdp.types.ts.Target.SetAutoAttachParams; + +/** + * Owns ModCDP's browser graph and target/session/context routing policy. + * + * AutoSessionRouter records Target/Page/Runtime events, maintains the current + * target-to-session graph, hydrates target routes on demand, creates execution + * contexts, and builds Mod.getTopology output. It does not know how commands + * are physically delivered. Loopback WebSocket request ids, chrome.debugger + * debuggee selection, native event source normalization, and upstream setup all + * live behind the UpstreamTransport interface. + * + * State machine: + * 1. Target records arrive from Target.getTargets or target-info events. + * 2. ensureRouteForTarget attaches a target and records either a native session id + * or a sessionless attached target supplied by the upstream. + * 3. Runtime/Page events add or invalidate execution context records. + * 4. Frame detach, navigation, target detach, and target destroy events remove + * only the state affected by the browser event. + */ +class AutoSessionRouter { + config: ModCDPRouterConfig; + + // TargetID -> native flattened Target.SessionID. Updated by ensureRouteForTarget + // and Target.attachedToTarget events; read by routing, injectors, and topology. + readonly sessionId_from_targetId = new Map(); + + // Native flattened Target.SessionID -> TargetID. Updated with + // sessionId_from_targetId; read when events arrive with only a session id. + readonly targetId_from_sessionId = new Map(); + + // TargetID -> latest target metadata plus router-owned session metadata. + // Updated from target discovery/events; read by topology and target selection. + readonly targets = new Map(); -export class AutoSessionRouter { - readonly target_sessions = new Map(); - readonly session_targets = new Map>(); - readonly execution_contexts = new Map(); + // Context key -> execution context. The key is Chrome's uniqueId when present, + // otherwise target/session plus context id. Updated by Runtime events and by + // Page.createIsolatedWorld; read by waits, DOM root resolution, and topology. + readonly contexts = new Map(); + + // Context waiters keyed by native session id or by target id for sessionless + // upstreams. Added by waitForExecutionContextMatching and resolved/rejected by + // recordExecutionContext and invalidation methods. private readonly execution_context_waiters = new Map>(); - private readonly detached_sessions = new Map(); - constructor( - private readonly send: SendCDP, - private readonly defaultExecutionContextTimeoutMs: () => number, - ) {} + // Semantic upstream selected by the owner. The router calls methods on this + // object but never mutates transport-owned private state. + private readonly upstream: UpstreamTransport; + + // Protocol registry used only for native command schema lookup/validation + // before routing. The router does not own custom command behavior or aliases. + private readonly types: CDPTypes; + + private subscription_cleanup: (() => void) | null = null; + + constructor({ upstream, types, ...config }: AutoSessionRouterConfig) { + this.upstream = upstream; + this.types = types; + this.config = ModCDPRouterConfigSchema.parse({ + ...config, + router_routes: { + ...DEFAULT_CLIENT_ROUTER_ROUTES, + ...(config.router_routes ?? {}), + }, + }); + } + + /** Install routing event listeners and enable browser-side target discovery. */ + async start() { + if (this.subscription_cleanup) return; + const subscription_cleanup = this.listen(); + this.subscription_cleanup = subscription_cleanup; + try { + await Promise.all([ + this.upstream.send(Target.SetAutoAttachCommand, targetAutoAttachParams), + this.upstream.send(Target.SetDiscoverTargetsCommand, { discover: true }), + ]); + } catch (error) { + if (this.subscription_cleanup === subscription_cleanup) { + subscription_cleanup(); + this.subscription_cleanup = null; + } + throw error; + } + } + + stop() { + this.subscription_cleanup?.(); + this.subscription_cleanup = null; + } + + toJSON() { + return modCDPToJSON(this, { + config: { + router_routes: this.config.router_routes, + loopback_execution_context_timeout_ms: this.config.loopback_execution_context_timeout_ms, + }, + state: { + started: this.subscription_cleanup != null, + sessions: this.sessionId_from_targetId.size, + targets: this.targets.size, + contexts: this.contexts.size, + execution_context_waiters: this.execution_context_waiters.size, + }, + }); + } + + /** Route a CDP command using router-owned target/session policy. */ + async send( + method: string, + params: ProtocolParams = {}, + requestedSessionId: cdp.types.ts.Target.SessionID | null = null, + ): Promise { + const command = this.types.nativeCommandSchema(method); + if (!command) throw new Error(`AutoSessionRouter cannot route unknown CDP command ${method}.`); + const domain = command.id.split(".")[0] ?? ""; + if (requestedSessionId != null) { + const targetId = this.targetId_from_sessionId.get(requestedSessionId); + if (!targetId) throw new Error(`No target is recorded for sessionId=${requestedSessionId}.`); + const route = { + targetId, + sessionId: requestedSessionId, + }; + const routed_params = + command.id === Runtime.CallFunctionOnCommand.id + ? await this.callFunctionOnParamsForRoute(params, route) + : params; + return await this.upstream.send(command, routed_params, route); + } + if (domain === "Browser" || domain === "Target" || domain === "SystemInfo") + return await this.upstream.send(command, params); + const route = await this.ensureRouteForTarget( + await this.resolveTargetId(CdpDebuggeeCommandParamsSchema.parse(params)), + ); + const routed_params = + command.id === Runtime.CallFunctionOnCommand.id ? await this.callFunctionOnParamsForRoute(params, route) : params; + return await this.upstream.send(command, routed_params, route); + } + + /** Ensure a target has a real native flattened CDP session id. */ + async ensureSessionForTarget(targetId: cdp.types.ts.Target.TargetID): Promise { + const route = await this.ensureRouteForTarget(targetId); + if (route.sessionId == null) throw new Error(`Upstream attached targetId=${targetId} without a CDP session id.`); + return route.sessionId; + } + + /** Ensure a target is addressable by the selected upstream. */ + async ensureRouteForTarget(targetId: cdp.types.ts.Target.TargetID | null): Promise { + targetId ??= await this.resolveTargetId(CdpDebuggeeCommandParamsSchema.parse({})); + const sessionId = targetId ? this.sessionId_from_targetId.get(targetId) : null; + if (targetId && sessionId != null) return { targetId, sessionId }; + const target = targetId ? this.targets.get(targetId) : null; + if (targetId && target?.sessionId === null) return { targetId, sessionId: null }; + targetId ??= await this.upstream.createTarget("about:blank#modcdp"); + const attachedSessionId = await this.upstream.attachToTarget(targetId); + if (attachedSessionId == null) { + this.recordTargetSessionlessAttachment(targetId); + return { targetId, sessionId: null }; + } + this.recordTargetSession(targetId, attachedSessionId, this.targets.get(targetId)); + return { targetId, sessionId: attachedSessionId }; + } + + private listen() { + const subscriptions = [ + this.upstream.on(Target.AttachedToTargetEvent, (event) => + this.recordTargetSession(event.targetInfo.targetId, event.sessionId, event.targetInfo), + ), + this.upstream.on(Target.DetachedFromTargetEvent, (event) => this.forgetSession(event.sessionId)), + this.upstream.on(Target.TargetInfoChangedEvent, (event) => this.recordTarget(event.targetInfo)), + this.upstream.on(Target.TargetDestroyedEvent, (event) => this.forgetTarget(event.targetId)), + this.upstream.on(Runtime.ExecutionContextCreatedEvent, (event, targetId, sessionId) => { + this.recordExecutionContext(targetId, sessionId, event.context); + }), + this.upstream.on(Runtime.ExecutionContextDestroyedEvent, (event, _targetId, sessionId) => { + if (sessionId) this.forgetExecutionContextById(sessionId, event.executionContextId); + }), + this.upstream.on(Runtime.ExecutionContextsClearedEvent, (_event, _targetId, sessionId) => { + if (sessionId) this.forgetExecutionContextsForRoute(sessionId); + }), + this.upstream.on(Page.FrameNavigatedEvent, (event, targetId, sessionId) => { + this.forgetExecutionContextsForFrame(sessionId, targetId, event.frame.id); + }), + this.upstream.on(Page.FrameDetachedEvent, (event, targetId, sessionId) => { + this.forgetExecutionContextsForFrame(sessionId, targetId, event.frameId); + }), + ]; + return () => subscriptions.forEach((subscription) => subscription.remove()); + } + + /** Wait for the first execution context associated with a real session id. */ + waitForExecutionContext(sessionId: string | null, { timeout_ms }: { timeout_ms?: number } = {}): Promise { + return this.waitForExecutionContextMatching( + (context) => context.sessionId === sessionId, + sessionId, + timeout_ms, + ).then((context) => context.id); + } + + private async callFunctionOnParamsForRoute(params: ProtocolParams, route: TargetRoute): Promise { + const call_params = Runtime.CallFunctionOnCommand.params.parse(params); + // objectId and uniqueContextId already pin the call to a browser-owned + // context. Only inject executionContextId for global calls that need the + // route's current Runtime context. + if (call_params.executionContextId != null || call_params.uniqueContextId != null || call_params.objectId != null) + return call_params; + const context = await this.waitForExecutionContextMatching( + (current_context) => + current_context.targetId === route.targetId && + (route.sessionId == null || current_context.sessionId === route.sessionId), + route.sessionId ?? route.targetId, + ); + return { + ...call_params, + executionContextId: context.id, + }; + } + + /** Ensure the requested execution context exists for a frame. */ + async ensureExecutionContext( + frame: { + frameId: cdp.types.ts.Page.FrameId; + targetId: cdp.types.ts.Target.TargetID; + }, + selector: ContextSelector = { world: "main" }, + ): Promise { + const route = await this.ensureRouteForTarget(frame.targetId); + const existing = this.findExecutionContext(route.targetId, route.sessionId, frame.frameId, selector); + if (existing) return existing; + + await this.upstream.send(Runtime.EnableCommand, {}, route); + if (selector.world === "isolated" || selector.world === "piercer") { + const created = await this.upstream.send( + Page.CreateIsolatedWorldCommand, + { + frameId: frame.frameId, + worldName: selector.worldName ?? (selector.world === "piercer" ? piercerWorldName : undefined), + grantUniveralAccess: true, + }, + route, + ); + const createdContext = this.findExecutionContext(route.targetId, route.sessionId, frame.frameId, selector); + if (createdContext?.id === created.executionContextId) return createdContext; + const context: ModCDPTopologyExecutionContext = { + id: created.executionContextId, + sessionId: route.sessionId, + targetId: route.targetId, + frameId: frame.frameId, + world: selector.world === "piercer" ? "piercer" : selector.worldName || "isolated", + name: selector.worldName, + }; + this.contexts.set(this.contextKey(route.targetId, route.sessionId, context.id, context.uniqueId), context); + return context; + } - sessionIdForTarget(target_id: string) { - return this.target_sessions.get(target_id) ?? null; + return await this.waitForExecutionContextMatching( + (context) => + context.targetId === route.targetId && + context.sessionId === route.sessionId && + context.frameId === frame.frameId && + context.world === selector.world, + route.sessionId ?? route.targetId, + ); } - async attachToTarget(target_id: string) { - const existing_session_id = this.sessionIdForTarget(target_id); - if (existing_session_id != null) return existing_session_id; - const result = await this.send("Target.attachToTarget", { - targetId: target_id, - flatten: true, + /** Build the current target/frame/DOM-root/execution-context topology. */ + async getTopology(params: ModCDPGetTopologyParams = {}): Promise { + const objectGroup = `modcdp-topology-${Date.now()}-${Math.random().toString(16).slice(2)}`; + const targetInfos = await this.upstream.getTargets(); + for (const targetInfo of targetInfos) this.recordTarget(targetInfo); + + const rootTarget = this.resolveRootTarget(params, targetInfos); + if (rootTarget == null) throw new Error("Mod.getTopology could not resolve a page target."); + const frames = new Map(); + const rootRoute = await this.enableTarget(rootTarget.targetId); + const rootTree = (await this.upstream.send(Page.GetFrameTreeCommand, {}, rootRoute)).frameTree; + const rootFrameId = rootTree.frame.id; + this.recordFrameTree(rootTree, rootTarget.targetId, null, frames); + + const oopifTargets = targetInfos.filter( + (target) => target.type === "iframe" && target.parentFrameId && !frames.has(target.targetId), + ); + await runTopologyQueue(oopifTargets, async (target) => { + const route = await this.enableTarget(target.targetId); + const frameTree = (await this.upstream.send(Page.GetFrameTreeCommand, {}, route)).frameTree; + this.recordFrameTree(frameTree, target.targetId, target.parentFrameId ?? null, frames); }); - const session_id = result && typeof result === "object" ? (result as Record).sessionId : null; - return typeof session_id === "string" && session_id.length > 0 ? session_id : null; - } - - recordProtocolEvent(method: string, data: unknown, session_id: string | null) { - const event_data = - data && typeof data === "object" && !Array.isArray(data) ? (data as Record) : {}; - if (method === "Target.attachedToTarget") { - const attached_session_id = typeof event_data.sessionId === "string" ? event_data.sessionId : session_id; - const target_info = - event_data.targetInfo && typeof event_data.targetInfo === "object" - ? (event_data.targetInfo as Record) + + await runTopologyQueue([...frames.entries()], async ([frameId, frame]) => { + if (!frame.parentFrameId) return; + const parent = frames.get(frame.parentFrameId); + if (!parent) return; + const parentRoute = await this.ensureRouteForTarget(parent.targetId); + const owner = await this.upstream.send(DOM.GetFrameOwnerCommand, { frameId }, parentRoute); + if (owner.backendNodeId != null) frame.outerBackendNodeId = owner.backendNodeId; + }); + + const contexts = new Map(); + const roots = new Map(); + await runTopologyQueue([...frames.entries()], async ([frameId, frame]) => { + const context = await this.ensureExecutionContext({ frameId, targetId: frame.targetId }, { world: "piercer" }); + contexts.set(this.contextKey(context.targetId, context.sessionId ?? null, context.id, context.uniqueId), context); + const rootObject = await this.upstream.send( + Runtime.EvaluateCommand, + { + expression: "document.documentElement", + objectGroup, + ...(context.uniqueId ? { uniqueContextId: context.uniqueId } : { contextId: context.id }), + }, + context, + ); + const objectId = rootObject.result.objectId; + if (!objectId) throw new Error(`Mod.getTopology could not resolve document root for frameId=${frameId}.`); + const node = ( + await this.upstream.send( + DOM.DescribeNodeCommand, + { + objectId, + }, + context, + ) + ).node; + roots.set(objectId, { + kind: "document", + frameId, + outerBackendNodeId: frame.outerBackendNodeId ?? null, + innerBackendNodeId: node.backendNodeId ?? null, + executionContextId: context.id, + ...(context.uniqueId ? { uniqueContextId: context.uniqueId } : {}), + }); + }); + + await runTopologyQueue([...new Set([...frames.values()].map((frame) => frame.targetId))], async (targetId) => { + const route = await this.ensureRouteForTarget(targetId); + const document = await this.upstream.send( + DOM.GetDocumentCommand, + { + depth: -1, + pierce: true, + }, + route, + ); + await this.recordShadowRoots(document.root, frames, roots, objectGroup); + }); + + for (const context of this.contexts.values()) { + if ([...frames.values()].some((frame) => frame.targetId === context.targetId)) { + contexts.set( + this.contextKey(context.targetId, context.sessionId ?? null, context.id, context.uniqueId), + context, + ); + } + } + + return { + objectGroup, + rootFrameId, + frames: Object.fromEntries(frames), + roots: Object.fromEntries(roots), + targets: Object.fromEntries( + [...this.targets].filter(([targetId]) => targetInfos.some((target) => target.targetId === targetId)), + ), + contexts: Object.fromEntries(contexts), + }; + } + + private resolveRootTarget(params: ModCDPGetTopologyParams, targetInfos: TargetInfo[]): TargetInfo | null { + const requestedTargetId = params.rootTargetId ?? params.targetId ?? null; + if (requestedTargetId) return targetInfos.find((target) => target.targetId === requestedTargetId) ?? null; + return targetInfos.find((target) => target.type === "page" && !target.url.startsWith("devtools://")) ?? null; + } + + private async resolveTargetId(params: CdpDebuggeeCommandParams): Promise { + const explicitTargetId = await this.upstream.resolveTargetId(params); + if (explicitTargetId) return explicitTargetId; + const targetInfos = await this.upstream.getTargets(); + for (const targetInfo of targetInfos) this.recordTarget(targetInfo); + const tabId = params.debuggee?.tabId ?? params.tabId ?? null; + if (typeof tabId === "number" && globalThis.chrome?.tabs?.get) { + const tab = await globalThis.chrome.tabs.get(tabId); + const tabUrl = tab.url || tab.pendingUrl || null; + if (tabUrl) { + const targetId = targetInfos.find((target) => target.type === "page" && target.url === tabUrl)?.targetId; + if (targetId) return targetId; + } + } + return ( + targetInfos.find((target) => target.type === "page" && !target.url.startsWith("devtools://"))?.targetId ?? null + ); + } + + private async enableTarget(targetId: cdp.types.ts.Target.TargetID): Promise { + const route = await this.ensureRouteForTarget(targetId); + await Promise.all([ + this.upstream.send(Page.EnableCommand, {}, route), + this.upstream.send(DOM.EnableCommand, {}, route), + this.upstream.send(Runtime.EnableCommand, {}, route), + this.upstream.send(Target.SetAutoAttachCommand, targetAutoAttachParams, route).catch(() => ({})), + ]); + return route; + } + + private recordFrameTree( + tree: FrameTree, + targetId: cdp.types.ts.Target.TargetID, + parentFrameId: cdp.types.ts.Page.FrameId | null, + frames: Map, + ): void { + const frameId = tree.frame.id; + frames.set(frameId, { + targetId, + url: tree.frame.url ?? null, + parentFrameId: tree.frame.parentId ?? parentFrameId ?? null, + }); + for (const child of tree.childFrames ?? []) this.recordFrameTree(child, targetId, frameId, frames); + } + + private async recordShadowRoots( + node: DomNode, + frames: Map, + roots: Map, + objectGroup: string, + frameId: cdp.types.ts.Page.FrameId | null = null, + hostBackendNodeId: cdp.types.ts.DOM.BackendNodeId | null = null, + ): Promise { + const currentFrameId = node.frameId ?? frameId; + for (const shadowRoot of node.shadowRoots ?? []) { + if (currentFrameId) { + const frame = frames.get(currentFrameId); + const context = frame + ? this.findExecutionContext(frame.targetId, null, currentFrameId, { + world: "piercer", + }) : null; - const target_id = typeof target_info?.targetId === "string" ? target_info.targetId : null; - if (attached_session_id && target_id && target_info) { - this.detached_sessions.delete(attached_session_id); - this.target_sessions.set(target_id, attached_session_id); - this.session_targets.set(attached_session_id, target_info); + if (frame && context) { + const objectId = ( + await this.upstream.send( + DOM.ResolveNodeCommand, + { + backendNodeId: shadowRoot.backendNodeId, + executionContextId: context.id, + objectGroup, + }, + context, + ) + ).object.objectId; + if (objectId) { + roots.set(objectId, { + kind: "shadow", + frameId: currentFrameId, + outerBackendNodeId: hostBackendNodeId ?? node.backendNodeId ?? null, + innerBackendNodeId: shadowRoot.backendNodeId ?? null, + mode: shadowRoot.shadowRootType, + executionContextId: context.id, + ...(context.uniqueId ? { uniqueContextId: context.uniqueId } : {}), + }); + } + } } - } else if (method === "Runtime.executionContextCreated") { - const context = event_data.context && typeof event_data.context === "object" ? event_data.context : null; - const context_id = context && "id" in context && typeof context.id === "number" ? context.id : null; - if (session_id && context_id != null) this.recordExecutionContext(session_id, context_id); - } else if (method === "Target.detachedFromTarget") { - const detached_session_id = typeof event_data.sessionId === "string" ? event_data.sessionId : session_id; - if (detached_session_id) this.forgetSession(detached_session_id); - } - } - - waitForExecutionContext(session_id: string | null, { timeout_ms }: { timeout_ms?: number } = {}) { - const effective_timeout_ms = timeout_ms ?? this.defaultExecutionContextTimeoutMs(); - if (!session_id) return Promise.reject(new Error("Cannot wait for a Runtime execution context without a session.")); - const existing = this.execution_contexts.get(session_id); - if (existing != null) return Promise.resolve(existing); - return new Promise((resolve, reject) => { + await this.recordShadowRoots(shadowRoot, frames, roots, objectGroup, currentFrameId, node.backendNodeId ?? null); + } + for (const child of node.children ?? []) { + await this.recordShadowRoots(child, frames, roots, objectGroup, currentFrameId, hostBackendNodeId); + } + if (node.contentDocument) { + await this.recordShadowRoots( + node.contentDocument, + frames, + roots, + objectGroup, + node.contentDocument.frameId ?? currentFrameId, + hostBackendNodeId, + ); + } + } + + private recordTarget(targetInfo: TargetInfo): void { + const sessionId = this.sessionId_from_targetId.get(targetInfo.targetId); + const existing = this.targets.get(targetInfo.targetId); + const target: ModCDPTopologyTarget = { + ...targetInfo, + targetId: targetInfo.targetId, + type: targetInfo.type, + }; + if (sessionId !== undefined) target.sessionId = sessionId; + else if (existing?.sessionId === null) target.sessionId = null; + this.targets.set(targetInfo.targetId, target); + } + + private recordTargetSession( + targetId: cdp.types.ts.Target.TargetID, + sessionId: cdp.types.ts.Target.SessionID, + targetInfo: TargetInfo | ModCDPTopologyTarget | null | undefined, + ): void { + this.sessionId_from_targetId.set(targetId, sessionId); + this.targetId_from_sessionId.set(sessionId, targetId); + const target = targetInfo + ? { ...targetInfo, targetId, type: targetInfo.type, sessionId } + : { + targetId, + type: this.targets.get(targetId)?.type ?? "page", + sessionId, + }; + this.targets.set(targetId, target); + } + + private recordTargetSessionlessAttachment(targetId: cdp.types.ts.Target.TargetID): void { + const existing = this.targets.get(targetId); + this.targets.set( + targetId, + existing ? { ...existing, sessionId: null } : { targetId, type: "page", sessionId: null }, + ); + } + + private recordExecutionContext( + eventTargetId: cdp.types.ts.Target.TargetID | null, + sessionId: cdp.types.ts.Target.SessionID | null, + context: cdp.types.ts.Runtime.ExecutionContextDescription, + ): void { + const targetId = eventTargetId ?? (sessionId ? (this.targetId_from_sessionId.get(sessionId) ?? null) : null); + if (!targetId) return; + const auxData = context.auxData && typeof context.auxData === "object" ? context.auxData : {}; + const frameId = typeof auxData.frameId === "string" ? auxData.frameId : null; + const topologyContext: ModCDPTopologyExecutionContext = { + ...context, + id: context.id, + sessionId, + targetId, + frameId, + world: + context.name === piercerWorldName + ? "piercer" + : auxData.type === "default" + ? "main" + : context.name || String(auxData.type ?? "isolated"), + }; + this.contexts.set(this.contextKey(targetId, sessionId, context.id, context.uniqueId), topologyContext); + const waiterKey = sessionId ?? targetId; + const waiters = this.execution_context_waiters.get(waiterKey); + if (!waiters) return; + for (const waiter of [...waiters]) { + if (!waiter.matches(topologyContext)) continue; + waiters.delete(waiter); + clearTimeout(waiter.timeout); + waiter.resolve(topologyContext); + } + if (waiters.size === 0) this.execution_context_waiters.delete(waiterKey); + } + + private findExecutionContext( + targetId: cdp.types.ts.Target.TargetID, + sessionId: cdp.types.ts.Target.SessionID | null, + frameId: cdp.types.ts.Page.FrameId, + selector: ContextSelector, + ): ModCDPTopologyExecutionContext | null { + for (const context of this.contexts.values()) { + if (context.targetId !== targetId || context.frameId !== frameId) continue; + if (sessionId != null && context.sessionId !== sessionId) continue; + if (selector.world === "piercer" && context.world === "piercer") return context; + if (selector.world === "isolated" && context.name === selector.worldName) return context; + if (selector.world === "main" && context.world === "main") return context; + if (context.world === selector.world) return context; + } + return null; + } + + private waitForExecutionContextMatching( + matches: (context: ModCDPTopologyExecutionContext) => boolean, + waiterKey: string | null, + timeoutMs = this.config.loopback_execution_context_timeout_ms, + ): Promise { + for (const context of this.contexts.values()) { + if (matches(context)) return Promise.resolve(context); + } + if (!waiterKey) return Promise.reject(new Error("Cannot wait for a Runtime execution context without a route.")); + return new Promise((resolve, reject) => { const waiter: ExecutionContextWaiter = { resolve, reject, + matches, timeout: setTimeout(() => { - const waiters = this.execution_context_waiters.get(session_id); + const waiters = this.execution_context_waiters.get(waiterKey); waiters?.delete(waiter); - if (waiters?.size === 0) this.execution_context_waiters.delete(session_id); - reject(new Error(`Timed out waiting for Runtime.executionContextCreated for session ${session_id}.`)); - }, effective_timeout_ms), + if (waiters?.size === 0) this.execution_context_waiters.delete(waiterKey); + reject(new Error(`Timed out waiting for Runtime.executionContextCreated for route ${waiterKey}.`)); + }, timeoutMs), }; - const waiters = this.execution_context_waiters.get(session_id); + const waiters = this.execution_context_waiters.get(waiterKey); if (waiters) waiters.add(waiter); - else this.execution_context_waiters.set(session_id, new Set([waiter])); + else this.execution_context_waiters.set(waiterKey, new Set([waiter])); }); } - private recordExecutionContext(session_id: string, context_id: number) { - if (this.detached_sessions.has(session_id)) return; - this.execution_contexts.set(session_id, context_id); - const waiters = this.execution_context_waiters.get(session_id); - if (!waiters) return; - this.execution_context_waiters.delete(session_id); - for (const waiter of waiters) { - clearTimeout(waiter.timeout); - waiter.resolve(context_id); - } + private forgetTarget(targetId: cdp.types.ts.Target.TargetID): void { + const sessionId = this.sessionId_from_targetId.get(targetId); + if (sessionId) this.forgetSession(sessionId); + this.targets.delete(targetId); + this.forgetExecutionContextsForRoute(targetId); } - private forgetSession(session_id: string) { - const target_info = this.session_targets.get(session_id); - const target_id = typeof target_info?.targetId === "string" ? target_info.targetId : null; - if (target_id) this.target_sessions.delete(target_id); - this.session_targets.delete(session_id); - this.execution_contexts.delete(session_id); - this.markDetachedSession(session_id); - const waiters = this.execution_context_waiters.get(session_id); + private forgetSession(sessionId: cdp.types.ts.Target.SessionID): void { + const targetId = this.targetId_from_sessionId.get(sessionId); + if (targetId) this.sessionId_from_targetId.delete(targetId); + this.targetId_from_sessionId.delete(sessionId); + this.forgetExecutionContextsForRoute(sessionId); + const waiters = this.execution_context_waiters.get(sessionId); if (!waiters) return; - this.execution_context_waiters.delete(session_id); - const error = new Error(`Runtime execution context wait cancelled because session ${session_id} detached.`); + this.execution_context_waiters.delete(sessionId); + const error = new Error(`Runtime execution context wait cancelled because session ${sessionId} detached.`); for (const waiter of waiters) { clearTimeout(waiter.timeout); waiter.reject(error); } } - private markDetachedSession(session_id: string) { - this.detached_sessions.delete(session_id); - this.detached_sessions.set(session_id, true); - while (this.detached_sessions.size > max_detached_session_guards) { - const oldest_session_id = this.detached_sessions.keys().next().value; - if (!oldest_session_id) break; - this.detached_sessions.delete(oldest_session_id); + private forgetExecutionContextById( + routeKey: string, + executionContextId: cdp.types.ts.Runtime.ExecutionContextId, + ): void { + for (const [contextKey, context] of this.contexts) { + if ((context.sessionId === routeKey || context.targetId === routeKey) && context.id === executionContextId) { + this.contexts.delete(contextKey); + } } } + + private forgetExecutionContextsForRoute(routeKey: string): void { + for (const [contextKey, context] of this.contexts) { + if (context.sessionId === routeKey || context.targetId === routeKey) this.contexts.delete(contextKey); + } + } + + private forgetExecutionContextsForFrame( + sessionId: cdp.types.ts.Target.SessionID | null, + targetId: cdp.types.ts.Target.TargetID | null, + frameId: cdp.types.ts.Page.FrameId, + ): void { + for (const [contextKey, context] of this.contexts) { + if (context.frameId !== frameId) continue; + if (sessionId != null && context.sessionId === sessionId) this.contexts.delete(contextKey); + else if (targetId != null && context.targetId === targetId) this.contexts.delete(contextKey); + } + } + + private contextKey( + targetId: cdp.types.ts.Target.TargetID, + sessionId: cdp.types.ts.Target.SessionID | null, + contextId: cdp.types.ts.Runtime.ExecutionContextId, + uniqueId: string | undefined, + ): string { + return uniqueId ?? `${sessionId ?? targetId}:${contextId}`; + } +} + +async function runTopologyQueue(items: Iterable, worker: (item: T) => Promise): Promise { + const queue = [...items]; + const workers = Array.from({ length: Math.min(topologyConcurrency, queue.length) }, async () => { + for (;;) { + const item = queue.shift(); + if (item == null) return; + await worker(item); + } + }); + await Promise.all(workers); } + +export { DEFAULT_CLIENT_ROUTER_ROUTES, AutoSessionRouter }; +export type { AutoSessionRouterConfig }; diff --git a/js/src/server/ModCDPServer.ts b/js/src/server/ModCDPServer.ts index 28de0f79..cb23b8bf 100644 --- a/js/src/server/ModCDPServer.ts +++ b/js/src/server/ModCDPServer.ts @@ -1,802 +1,354 @@ -// ModCDPServer: lives inside an extension service worker. Owns the registry -// of custom commands and event bindings, and emits events through the binding -// API installed by the client (Runtime.addBinding -> globalThis[__ModCDP_custom_event__]). -// -// The installer is intentionally self-contained so the bridge can inject the -// same server implementation into an already-running extension service worker -// when Chrome refuses Extensions.loadUnpacked. - -import type { cdp } from "../types/generated/cdp.js"; -import { commands as nativeCommandSchemas, events as nativeEventSchemas } from "../types/generated/zod.js"; -import { normalizeModCDPPayloadSchema } from "../types/modcdp.js"; +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. +// ModCDPServer: lives inside an extension service worker. Owns custom command +// handlers, event bindings, downstream delivery, and the browser-target client. +// Shape metadata belongs to ModCDPServer.client.types so the server and its +// upstream client validate against one registry object. + +import * as Browser from "../types/generated/zod/Browser.js"; +import * as Runtime from "../types/generated/zod/Runtime.js"; +import type { z } from "zod"; +import { ModCDPClient, upstream_transport_constructors } from "../client/ModCDPClient.js"; +import { ModCDPConfigureParamsSchema, ModCDPServerConfigSchema } from "../types/modcdp.js"; +import { routeFor } from "../translate/translate.js"; +import { ChromeDebuggerUpstreamTransport } from "../transport/ChromeDebuggerUpstreamTransport.js"; +import { DownstreamTransportSet } from "../transport/DownstreamTransportSet.js"; +import { NativeMessagingDownstreamTransport } from "../transport/NativeMessagingDownstreamTransport.js"; +import { NATSDownstreamTransport } from "../transport/NATSDownstreamTransport.js"; +import { ReverseWSDownstreamTransport } from "../transport/ReverseWSDownstreamTransport.js"; +import { modCDPToJSON } from "../types/toJSON.js"; import type { - CdpCommandMessage, - CdpEventMessage, - CdpDebuggeeCommandParams, + CdpResponseMessage, ModCDPConfigureParams, ModCDPCustomCommandRegistration, ModCDPCustomEventRegistration, ModCDPMiddlewareRegistration, - ModCDPPingParams, ModCDPRoutes, + ModCDPServerConfig, ProtocolParams, ProtocolPayload, ProtocolResult, } from "../types/modcdp.js"; -export const DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000; -export const DEFAULT_LOOPBACK_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; -export const DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS = 250; -export const DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; -export const DEFAULT_NATIVE_BRIDGE_HOST_NAME = "com.modcdp.bridge"; -export const DEFAULT_NATIVE_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; -export const DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; -export const DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX = "modcdp.default"; -export const DEFAULT_DOWNSTREAM_CLIENT_TIMEOUT_MS = 1_000; - type MiddlewarePhase = "request" | "response" | "event"; -type ProtocolCommandSchema = { - params: { parse(value: unknown): ProtocolParams }; - result: { parse(value: unknown): ProtocolResult }; -}; -type ProtocolEventSchema = { - parse(value: unknown): ProtocolPayload; -}; + type ModCDPGlobalScope = typeof globalThis & Record & { - ModCDP?: { - __ModCDPServerVersion?: number; - addCustomEvent?: unknown; - handleCommand?: unknown; - }; - }; - -export function installModCDPServer(globalScope: ModCDPGlobalScope = globalThis as ModCDPGlobalScope) { - const MODCDP_SERVER_VERSION = 2; - const DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000; - const DEFAULT_LOOPBACK_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; - const DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS = 250; - const DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; - const DEFAULT_NATIVE_BRIDGE_HOST_NAME = "com.modcdp.bridge"; - const DEFAULT_NATIVE_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; - const DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; - const DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX = "modcdp.default"; - if ( - globalScope.ModCDP?.__ModCDPServerVersion === MODCDP_SERVER_VERSION && - globalScope.ModCDP?.handleCommand && - globalScope.ModCDP?.addCustomEvent - ) - return globalScope.ModCDP; - - const UPSTREAM_EVENT_BINDING_NAME = "__ModCDP_event_from_upstream__"; - const CUSTOM_EVENT_BINDING_NAME = "__ModCDP_custom_event__"; - const encodeBindingPayload = ({ - event, - data, - cdpSessionId = null, - }: { - event: string; - data: ProtocolPayload; - cdpSessionId?: string | null; - }) => JSON.stringify({ event, data, cdpSessionId }); - - const commandHandlers = new Map(); - const eventBindings = new Map(); - const eventListeners = new Set<(event: string, data: ProtocolPayload, cdpSessionId: string | null) => void>(); - const middlewares: Record = { - request: [], - response: [], - event: [], + ModCDP?: ModCDPServer; }; - const attachedDebuggees = new Set(); - let runtime_types_promise: Promise | null = null; - let downstream_client_registered = false; - let downstream_client_lease: { - cdpSessionId: string | null; - last_seen_at: number; - timer: ReturnType; - } | null = null; - function registerDownstreamClient() { - downstream_client_registered = true; - } - - function clearDownstreamClientLease() { - const lease = downstream_client_lease; - if (!lease) return null; - clearTimeout(lease.timer); - downstream_client_lease = null; - return lease; - } - - function touchDownstreamClientLease(cdpSessionId: string | null) { - const timeout_ms = ModCDPServer.downstream_client_timeout_ms; - if (!(timeout_ms > 0)) return; - if (!downstream_client_registered) return; - const last_seen_at = Date.now(); - clearDownstreamClientLease(); - const timer = setTimeout(() => { - const expired = clearDownstreamClientLease(); - if (!expired) return; - if (ModCDPServer.close_browser_on_downstream_disconnect !== true) return; - void ModCDPServer.sendLoopback("Browser.close", {}, null).catch(() => {}); - }, timeout_ms); - downstream_client_lease = { - cdpSessionId, - last_seen_at, - timer, - }; - } - - function nativeCommandSchema(method: string) { - return (nativeCommandSchemas as Record)[method]; - } - - function nativeEventSchema(eventName: string) { - return (nativeEventSchemas as Record)[eventName]; - } - - function commandParamsSchema(method: string, command: ModCDPCustomCommandRegistration | null) { - return ( - (command?.params_schema as ProtocolCommandSchema["params"] | null) ?? nativeCommandSchema(method)?.params ?? null - ); - } - - function commandResultSchema(method: string, command: ModCDPCustomCommandRegistration | null) { - return ( - (command?.result_schema as ProtocolCommandSchema["result"] | null) ?? nativeCommandSchema(method)?.result ?? null - ); - } - - function eventPayloadSchema(eventName: string, event: ModCDPCustomEventRegistration | null) { - return (event?.event_schema as ProtocolEventSchema | null) ?? nativeEventSchema(eventName) ?? null; - } - - async function publishEvent(eventName: string, payload: ProtocolPayload = {}, cdpSessionId: string | null = null) { - payload = await ModCDPServer.runMiddleware("event", eventName, payload, { - cdpSessionId, - event: { name: eventName, payload }, +const DEFAULT_ROUTES = { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "auto", +} satisfies ModCDPRoutes; + +const OFFSCREEN_KEEP_ALIVE_PORT_NAME = "ModCDPOffscreenKeepAlive"; +const OFFSCREEN_KEEP_ALIVE_PATH = "offscreen/keepalive.html"; + +upstream_transport_constructors.set("chromedebugger", ChromeDebuggerUpstreamTransport); + +/** + * Extension-side ModCDP server. + * + * The server owns client-facing downstream transports, service-worker command + * handlers, custom command/event/middleware registries, and one ModCDPClient + * pointed upstream at browser targets. It does not implement a browser-target + * transport itself; target/session/context routing stays inside the upstream + * ModCDPClient.router. + */ +class ModCDPServer { + // sub-services + client!: ModCDPClient; + downstream: DownstreamTransportSet; + + // runtime state + started_at: string | null; + // Server-only secret used to verify that a discovered loopback endpoint is + // this same service worker. Browser routing/downstream config live on + // client.router, client.upstream, client.config, and downstream. + server_browser_token: string | null; + + private creating_offscreen_keep_alive: Promise | null = null; + private offscreen_keep_alive_port: chrome.runtime.Port | null = null; + + constructor(config: z.input = {}) { + config = ModCDPServerConfigSchema.parse(config); + this.server_browser_token = config.server_browser_token ?? null; + this.started_at = null; + this.downstream = new DownstreamTransportSet({ + ...(config.downstream ?? {}), + closeBrowser: () => this.client.router.send(Browser.CloseCommand.id, {}).then(() => {}), }); - if (payload === undefined) return { event: eventName, emitted: false, reason: "middleware_dropped" }; - const event = registryMatch(eventBindings, eventName); - payload = eventPayloadSchema(eventName, event)?.parse(payload) ?? payload; - - for (const listener of eventListeners) { - try { - listener(eventName, payload, cdpSessionId); - } catch (error) { - console.error("[ModCDPServer] event listener failed", error); - } - } - let emittedThroughReverseBridge = false; - if (reverseBridgeSocket?.readyState === WebSocket.OPEN) { - const message: CdpEventMessage = { - method: eventName, - params: (payload ?? {}) as CdpEventMessage["params"], - }; - if (cdpSessionId) message.sessionId = cdpSessionId; - reverseBridgeSocket.send(JSON.stringify(message)); - emittedThroughReverseBridge = true; - } - let emittedThroughNativeBridge = false; - if (nativeBridgePort) { - const message: CdpEventMessage = { - method: eventName, - params: (payload ?? {}) as CdpEventMessage["params"], - }; - if (cdpSessionId) message.sessionId = cdpSessionId; - nativeBridgePort.postMessage(message); - emittedThroughNativeBridge = true; - } - let emittedThroughNatsBridge = false; - if (nats_bridge_socket?.readyState === WebSocket.OPEN) { - const message: CdpEventMessage = { - method: eventName, - params: (payload ?? {}) as CdpEventMessage["params"], - }; - if (cdpSessionId) message.sessionId = cdpSessionId; - publishNats(`${nats_bridge_subject_prefix}.browser_to_client`, { - type: "modcdp.nats.message", - message, - }); - emittedThroughNatsBridge = true; - } - - const isCustomEvent = registryMatch(eventBindings, eventName) != null; - let emittedThroughBinding = false; - if (isCustomEvent) { - const customBinding = globalScope[CUSTOM_EVENT_BINDING_NAME]; - if (typeof customBinding === "function") { - customBinding( - encodeBindingPayload({ - event: eventName, - data: payload, - cdpSessionId, - }), - ); - emittedThroughBinding = true; - } - } else { - const mirrorBinding = globalScope[UPSTREAM_EVENT_BINDING_NAME]; - if (typeof mirrorBinding === "function") { - mirrorBinding( - encodeBindingPayload({ - event: eventName, - data: payload, - cdpSessionId, - }), - ); - emittedThroughBinding = true; - } - } - return emittedThroughBinding || - emittedThroughReverseBridge || - emittedThroughNativeBridge || - emittedThroughNatsBridge - ? { event: eventName, emitted: true } - : { event: eventName, emitted: false, reason: "binding_not_installed" }; - } - - const targetAutoAttachParams = { - autoAttach: true, - waitForDebuggerOnStart: false, - flatten: true, - } satisfies cdp.types.ts.Target.SetAutoAttachParams; - - const defaultRoutes = { - "Mod.*": "service_worker", - "Custom.*": "service_worker", - "*.*": "auto", - } satisfies ModCDPRoutes; - - const browserLevelDomains = new Set(["Browser", "Target", "SystemInfo"]); - - let nextLoopbackId = 1; - const loopbackSockets = new Map(); - const loopbackSocketPromises = new Map>(); - const loopbackTargetSessions = new Map(); - const loopbackSessionTargets = new Map(); - const loopbackSessionContexts = new Map(); - const loopbackContextWaiters = new Map void>>(); - const initializedLoopbackSockets = new WeakSet(); - const loopbackPending = new Map< - number, - { resolve: (value: ProtocolResult) => void; reject: (error: Error) => void } - >(); - let reverseBridgeSocket: WebSocket | null = null; - let reverseBridgeUrl: string | null = null; - let reverseBridgeReconnectIntervalMs = DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS; - let reverseBridgeReconnectTimer: ReturnType | null = null; - let nativeBridgePort: chrome.runtime.Port | null = null; - let nativeBridgeHostName: string | null = null; - let nativeBridgeReconnectIntervalMs = DEFAULT_NATIVE_BRIDGE_RECONNECT_INTERVAL_MS; - let nativeBridgeReconnectTimer: ReturnType | null = null; - let nats_bridge_socket: WebSocket | null = null; - let nats_bridge_url: string | null = null; - let nats_bridge_subject_prefix = DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX; - let nats_bridge_reconnect_interval_ms = DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS; - let nats_bridge_reconnect_timer: ReturnType | null = null; - let nats_bridge_buffer = ""; - let selfDebuggee: chrome.debugger.Debuggee | null = null; - const offscreenKeepAlivePortName = "ModCDPOffscreenKeepAlive"; - const offscreenKeepAlivePath = "offscreen/keepalive.html"; - let creatingOffscreenKeepAlive: Promise | null = null; - let offscreenKeepAlivePort: chrome.runtime.Port | null = null; - - function registryMatch(registry: Map, name: string): T | null { - const exact = registry.get(name); - if (exact) return exact; - let match: T | null = null; - let matchPrefixLength = -1; - for (const [pattern, value] of registry) { - if (!pattern.endsWith(".*")) continue; - const prefix = pattern.slice(0, -1); - if (!name.startsWith(prefix) || prefix.length <= matchPrefixLength) continue; - match = value; - matchPrefixLength = prefix.length; - } - return match; - } - - function normalizeModCDPName( - value: - | { - cdp_command_name?: string; - cdp_event_name?: string; - id?: string; - name?: string; - meta?: () => - | { - cdp_command_name?: unknown; - cdp_event_name?: unknown; - id?: unknown; - name?: unknown; - } - | undefined; - } - | string, - ) { - if (typeof value === "string") return value; - const meta = typeof value?.meta === "function" ? value.meta() : undefined; - const name = - value?.cdp_command_name ?? - value?.cdp_event_name ?? - (typeof meta?.cdp_command_name === "string" ? meta.cdp_command_name : undefined) ?? - (typeof meta?.cdp_event_name === "string" ? meta.cdp_event_name : undefined) ?? - value?.id ?? - (typeof meta?.id === "string" ? meta.id : undefined) ?? - (typeof meta?.name === "string" ? meta.name : undefined) ?? - value?.name; - if (typeof name !== "string" || !name) throw new Error("Expected a CDP name string or a named CDP schema/alias."); - return name; - } - - function errorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); - } - - function compactDebuggee(input: { - [Key in keyof chrome.debugger.Debuggee]?: chrome.debugger.Debuggee[Key] | null; - }): chrome.debugger.Debuggee { - return { - ...(typeof input.tabId === "number" ? { tabId: input.tabId } : {}), - ...(typeof input.targetId === "string" ? { targetId: input.targetId } : {}), - ...(typeof input.extensionId === "string" ? { extensionId: input.extensionId } : {}), - }; - } - - async function resolveCDPEndpoint(endpoint: string | null) { - if (!endpoint || /^wss?:\/\//i.test(endpoint)) return endpoint; - if (!/^https?:\/\//i.test(endpoint)) { - throw new Error(`loopback_cdp_url must be a ws://, wss://, http://, or https:// CDP endpoint, got ${endpoint}.`); - } - const { webSocketDebuggerUrl } = await fetch(`${endpoint}/json/version`).then((r) => r.json()); - if (!webSocketDebuggerUrl) throw new Error(`loopback_cdp_url HTTP discovery returned no webSocketDebuggerUrl.`); - return webSocketDebuggerUrl; - } - - async function openCDPSocket(endpoint: string): Promise { - if (!/^wss?:\/\//i.test(endpoint)) { - throw new Error(`loopback_cdp_url must be a ws:// or wss:// CDP websocket URL, got ${endpoint}.`); - } - return new Promise((resolve, reject) => { - const w = new WebSocket(endpoint); - let settled = false; - let errorEvent: Event | null = null; - const describe = (prefix: string, closeEvent?: CloseEvent) => { - const parts = [`${prefix} ${endpoint}`, `readyState=${w.readyState}`]; - if (errorEvent) parts.push(`error.type=${errorEvent.type}`); - if (closeEvent) { - parts.push(`close.code=${closeEvent.code}`); - parts.push(`close.reason=${closeEvent.reason || ""}`); - parts.push(`close.wasClean=${closeEvent.wasClean}`); - } - return parts.join(" "); - }; - const fail = (error: Error) => { - if (settled) return; - settled = true; - reject(error); - }; - w.addEventListener( - "open", - () => { - if (settled) return; - settled = true; - resolve(w); - }, - { once: true }, - ); - w.addEventListener( - "error", - (event) => { - errorEvent = event; - setTimeout( - () => fail(new Error(describe("CDP socket error"))), - ModCDPServer.ws_connect_error_settle_timeout_ms, - ); + this.client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + injector: { injector_mode: "none" }, + upstream: config.upstream ?? ({ upstream_mode: "chromedebugger" } as Record), + router: { + ...(config.router ?? {}), + router_routes: { + ...DEFAULT_ROUTES, + ...(config.router?.router_routes ?? {}), }, - { once: true }, - ); - w.addEventListener("close", (event) => fail(new Error(describe("CDP socket closed", event))), { once: true }); + }, + client_config: config.client_config ?? {}, + server_config: null, + types: { + custom_commands: config.custom_commands ?? [], + custom_events: config.custom_events ?? [], + custom_middlewares: config.custom_middlewares ?? [], + }, }); } - function startOffscreenKeepAlive() { - void ensureOffscreenKeepAlive().catch(() => {}); + // server.types == server.client.types, they share one registry to avoid confusion with drifting registries + get types() { + return this.client.types; } - function rejectLoopbackPending(error: Error) { - for (const pending of loopbackPending.values()) pending.reject(error); - loopbackPending.clear(); - } - - function scheduleReverseBridgeReconnect(delayMs: number) { - if (!reverseBridgeUrl) return; - if (reverseBridgeReconnectTimer) return; - reverseBridgeReconnectTimer = setTimeout(() => { - reverseBridgeReconnectTimer = null; - void connectReverseBridge(reverseBridgeUrl).catch(() => {}); - }, delayMs); - } - - function stopReverseBridge(reason = "stopped") { - const upstream_reversews_url = reverseBridgeUrl; - reverseBridgeUrl = null; - if (reverseBridgeReconnectTimer) { - clearTimeout(reverseBridgeReconnectTimer); - reverseBridgeReconnectTimer = null; - } - const socket = reverseBridgeSocket; - reverseBridgeSocket = null; - if (socket?.readyState === WebSocket.OPEN || socket?.readyState === WebSocket.CONNECTING) { - socket.close(1000, reason); - } - return { upstream_reversews_url, stopped: true, reason }; - } - - function scheduleNativeBridgeReconnect(delayMs: number) { - if (!nativeBridgeHostName) return; - if (nativeBridgeReconnectTimer) return; - nativeBridgeReconnectTimer = setTimeout(() => { - nativeBridgeReconnectTimer = null; - connectNativeBridge(nativeBridgeHostName); - }, delayMs); - } - - function scheduleNatsBridgeReconnect(delayMs: number) { - if (!nats_bridge_url) return; - if (nats_bridge_reconnect_timer) return; - nats_bridge_reconnect_timer = setTimeout(() => { - nats_bridge_reconnect_timer = null; - void connectNatsBridge(nats_bridge_url).catch(() => {}); - }, delayMs); - } - - async function handleReverseBridgeMessage(ws: WebSocket, data: unknown) { - let message: CdpCommandMessage; - try { - const parsed = JSON.parse(typeof data === "string" ? data : String(data)); - if (typeof parsed?.id !== "number" || typeof parsed?.method !== "string") return; - message = parsed as CdpCommandMessage; - } catch { - return; - } - - try { - const result = await ModCDPServer.handleCommand(message.method, message.params ?? {}, message.sessionId ?? null); - ws.send(JSON.stringify({ id: message.id, result })); - } catch (error) { - ws.send( - JSON.stringify({ - id: message.id, - error: { - code: -32000, - message: errorMessage(error), - }, - }), - ); - } + toJSON() { + return modCDPToJSON(this, { + state: { + started_at: this.started_at, + creating_offscreen_keep_alive: this.creating_offscreen_keep_alive != null, + offscreen_keep_alive_port: this.offscreen_keep_alive_port != null, + }, + children: { + client: this.client, + downstream: this.downstream, + }, + }); } - async function handleNativeBridgeMessage(port: chrome.runtime.Port, data: unknown) { - let message: CdpCommandMessage; - try { - if ( - typeof (data as CdpCommandMessage)?.id !== "number" || - typeof (data as CdpCommandMessage)?.method !== "string" - ) - return; - message = data as CdpCommandMessage; - } catch { - return; - } - - try { - const result = await ModCDPServer.handleCommand(message.method, message.params ?? {}, message.sessionId ?? null); - port.postMessage({ id: message.id, result }); - } catch (error) { - port.postMessage({ - id: message.id, - error: { - code: -32000, - message: errorMessage(error), + /** Install transports/default commands/listeners and return this server. */ + async start(): Promise { + if (this.started_at === null) { + this.started_at = new Date().toISOString(); + for (const transport of [ + new ReverseWSDownstreamTransport(), + new NativeMessagingDownstreamTransport(), + new NATSDownstreamTransport(), + ]) { + this.downstream.add(transport); + } + this.downstream.mirrorEventsFrom(this.client, { + transformEvent: async (message) => { + const cdpSessionId = message.sessionId ?? null; + const params = await this.runMiddleware("event", message.method, message.params ?? {}, { + cdpSessionId, + event: message, + }); + return { + ...message, + params: this.types.parseEventPayload(message.method, params ?? {}), + }; }, }); - } - } - - async function handleNatsBridgePayload(payload: string) { - let parsed: unknown; - try { - parsed = JSON.parse(payload); - } catch { - return; - } - const record = parsed && typeof parsed === "object" ? (parsed as Record) : null; - if (record?.type === "modcdp.nats.hello") { - publishNats(`${nats_bridge_subject_prefix}.browser_to_client`, { - type: "modcdp.nats.hello", - role: "extension-service-worker", - version: 1, - extension_id: globalScope.chrome?.runtime?.id ?? null, - }); - return; - } - const candidate = record?.type === "modcdp.nats.message" ? record.message : parsed; - if ( - !candidate || - typeof candidate !== "object" || - typeof (candidate as CdpCommandMessage).id !== "number" || - typeof (candidate as CdpCommandMessage).method !== "string" - ) - return; - const message = candidate as CdpCommandMessage; - try { - const result = await ModCDPServer.handleCommand(message.method, message.params ?? {}, message.sessionId ?? null); - publishNats(`${nats_bridge_subject_prefix}.browser_to_client`, { - type: "modcdp.nats.message", - message: { id: message.id, result }, - }); - } catch (error) { - publishNats(`${nats_bridge_subject_prefix}.browser_to_client`, { - type: "modcdp.nats.message", - message: { - id: message.id, - error: { - code: -32000, - message: errorMessage(error), - }, - }, + this.downstream.onRequest(async (message): Promise => { + try { + return { + id: message.id, + result: await this.handleCommand(message.method, message.params ?? {}, message.sessionId ?? null), + }; + } catch (error) { + return { + id: message.id, + error: { + code: -32000, + message: error instanceof Error ? error.message : String(error), + }, + }; + } }); + (globalThis as ModCDPGlobalScope).ModCDP = this; + this.registerChromeLifecycleEvents(); + await this.client.connect(); + if (this.client.upstream.config.upstream_mode === "ws") await this.client.router.start(); + await this.client.upstream.getTargets(); } + void this.ensureOffscreenKeepAlive(); + this.downstream.startPollingForClients(); + return this; } - async function connectReverseBridge(endpoint: string) { - if ( - reverseBridgeSocket?.readyState === WebSocket.OPEN || - reverseBridgeSocket?.readyState === WebSocket.CONNECTING - ) { - return { - upstream_reversews_url: endpoint, - connected: reverseBridgeSocket.readyState === WebSocket.OPEN, - }; - } + /** Ensure the offscreen keepalive document exists when Chrome exposes it. */ + async ensureOffscreenKeepAlive() { + const chrome_api = globalThis.chrome; + const offscreen = chrome_api?.offscreen; + if (!offscreen || !chrome_api?.runtime?.getURL) return { started: false, reason: "offscreen_unavailable" }; - const ws = new WebSocket(endpoint); - reverseBridgeSocket = ws; - ws.addEventListener("open", () => { - startOffscreenKeepAlive(); - ws.send( - JSON.stringify({ - type: "modcdp.reverse.hello", - role: "extension-service-worker", - version: 1, - extension_id: globalScope.chrome?.runtime?.id ?? null, - }), - ); - }); - ws.addEventListener("message", (event) => { - void handleReverseBridgeMessage(ws, event.data); - }); - ws.addEventListener("error", () => { - if (reverseBridgeSocket === ws) reverseBridgeSocket = null; - scheduleReverseBridgeReconnect(reverseBridgeReconnectIntervalMs); - }); - ws.addEventListener("close", () => { - if (reverseBridgeSocket === ws) reverseBridgeSocket = null; - scheduleReverseBridgeReconnect(reverseBridgeReconnectIntervalMs); - }); - return { upstream_reversews_url: endpoint, connected: false }; - } - - function connectNativeBridge(hostName: string) { - const chromeApi = globalScope.chrome; - if (!chromeApi?.runtime?.connectNative) { - scheduleNativeBridgeReconnect(nativeBridgeReconnectIntervalMs); - return { - upstream_nativemessaging_host_name: hostName, - connected: false, - reason: "native_messaging_unavailable", - }; - } - if (nativeBridgePort) return { upstream_nativemessaging_host_name: hostName, connected: true }; + const offscreen_url = chrome_api.runtime.getURL(OFFSCREEN_KEEP_ALIVE_PATH); try { - ModCDPServer.native_bridge_attempts += 1; - ModCDPServer.native_bridge_last_error = null; - const port = chromeApi.runtime.connectNative(hostName); - nativeBridgePort = port; - ModCDPServer.native_bridge_connected = true; - startOffscreenKeepAlive(); - port.postMessage({ - type: "modcdp.native.hello", - role: "extension-service-worker", - version: 1, - extension_id: globalScope.chrome?.runtime?.id ?? null, - }); - port.onMessage.addListener((message) => { - void handleNativeBridgeMessage(port, message); - }); - port.onDisconnect.addListener(() => { - if (nativeBridgePort === port) nativeBridgePort = null; - ModCDPServer.native_bridge_connected = false; - ModCDPServer.native_bridge_last_error = - chromeApi.runtime.lastError?.message ?? "Native messaging port disconnected."; - scheduleNativeBridgeReconnect(nativeBridgeReconnectIntervalMs); - }); - return { upstream_nativemessaging_host_name: hostName, connected: true }; + const existing_contexts = chrome_api.runtime.getContexts + ? await chrome_api.runtime.getContexts({ + contextTypes: ["OFFSCREEN_DOCUMENT"], + documentUrls: [offscreen_url], + }) + : []; + if (existing_contexts.length > 0) return { started: true, existing: true }; + + this.creating_offscreen_keep_alive ??= offscreen + .createDocument({ + url: OFFSCREEN_KEEP_ALIVE_PATH, + reasons: ["BLOBS"], + justification: "Keep ModCDP service worker active while CDP clients route commands through it.", + }) + .finally(() => { + this.creating_offscreen_keep_alive = null; + }); + await this.creating_offscreen_keep_alive; + return { started: true }; } catch (error) { - nativeBridgePort = null; - ModCDPServer.native_bridge_connected = false; - ModCDPServer.native_bridge_last_error = errorMessage(error); - scheduleNativeBridgeReconnect(nativeBridgeReconnectIntervalMs); return { - upstream_nativemessaging_host_name: hostName, - connected: false, - reason: errorMessage(error), + started: false, + reason: error instanceof Error ? error.message : String(error), }; } } - async function connectNatsBridge(endpoint: string) { - if (!/^wss?:\/\//i.test(endpoint)) { - throw new Error(`nats bridge endpoint must be a ws:// or wss:// URL for extension transport, got ${endpoint}.`); - } - if (nats_bridge_socket?.readyState === WebSocket.OPEN || nats_bridge_socket?.readyState === WebSocket.CONNECTING) { - return { - upstream_nats_url: endpoint, - upstream_nats_subject_prefix: nats_bridge_subject_prefix, - connected: nats_bridge_socket.readyState === WebSocket.OPEN, - }; - } - const ws = new WebSocket(endpoint); - nats_bridge_socket = ws; - nats_bridge_buffer = ""; - ws.addEventListener("open", () => { - startOffscreenKeepAlive(); - writeNats(`CONNECT ${JSON.stringify(natsConnectOptions())}\r\nPING\r\n`); - writeNats(`SUB ${nats_bridge_subject_prefix}.client_to_browser 1\r\n`); - publishNats(`${nats_bridge_subject_prefix}.browser_to_client`, { - type: "modcdp.nats.hello", - role: "extension-service-worker", - version: 1, - extension_id: globalScope.chrome?.runtime?.id ?? null, - }); - }); - ws.addEventListener("message", (event) => { - void readNatsWebSocketData(event.data); - }); - ws.addEventListener("error", () => { - if (nats_bridge_socket === ws) nats_bridge_socket = null; - scheduleNatsBridgeReconnect(nats_bridge_reconnect_interval_ms); - }); - ws.addEventListener("close", () => { - if (nats_bridge_socket === ws) nats_bridge_socket = null; - scheduleNatsBridgeReconnect(nats_bridge_reconnect_interval_ms); + addCustomCommand({ + name, + params_schema = null, + result_schema = null, + expression = null, + }: ModCDPCustomCommandRegistration) { + const registered_name = this.types.addCustomCommand({ + name, + params_schema, + result_schema, + expression, }); - return { - upstream_nats_url: endpoint, - upstream_nats_subject_prefix: nats_bridge_subject_prefix, - connected: false, - }; - } - - function writeNats(data: string) { - if (nats_bridge_socket?.readyState === WebSocket.OPEN) nats_bridge_socket.send(data); + return { name: registered_name, registered: true }; } - function publishNats(subject: string, message: unknown) { - const body = JSON.stringify(message); - writeNats(`PUB ${subject} ${new TextEncoder().encode(body).byteLength}\r\n${body}\r\n`); + addCustomEvent({ name, event_schema = null }: ModCDPCustomEventRegistration) { + const registered_name = this.types.addCustomEvent({ name, event_schema }); + return { name: registered_name, registered: true }; } - async function readNatsWebSocketData(data: unknown) { - if (typeof data === "string") nats_bridge_buffer += data; - else if (data instanceof ArrayBuffer) nats_bridge_buffer += new TextDecoder().decode(data); - else if (ArrayBuffer.isView(data)) nats_bridge_buffer += new TextDecoder().decode(data); - else if (typeof Blob !== "undefined" && data instanceof Blob) nats_bridge_buffer += await data.text(); - else return; - nats_bridge_buffer = consumeNatsProtocol(nats_bridge_buffer); + addMiddleware({ name = "*", phase, expression }: ModCDPMiddlewareRegistration) { + const registered_name = this.types.addCustomMiddleware({ name, phase, expression }); + return { name: registered_name, phase, registered: true }; } - function consumeNatsProtocol(buffer: string) { - for (;;) { - const lineEnd = buffer.indexOf("\r\n"); - if (lineEnd < 0) return buffer; - const line = buffer.slice(0, lineEnd); - const upper = line.toUpperCase(); - if (upper.startsWith("MSG ")) { - const parts = line.split(/\s+/); - const size = Number(parts[parts.length - 1]); - const payloadStart = lineEnd + 2; - const payloadEnd = payloadStart + size; - if (!Number.isInteger(size) || buffer.length < payloadEnd + 2) return buffer; - const payload = buffer.slice(payloadStart, payloadEnd); - buffer = buffer.slice(payloadEnd + 2); - void handleNatsBridgePayload(payload); - continue; + async runMiddleware(phase: MiddlewarePhase, name: string, payload: ProtocolPayload, context: ProtocolPayload = {}) { + const matching = this.types.customMiddlewareRegistrations(phase, name); + const dispatch = async (index: number, value: ProtocolPayload): Promise => { + const middleware = matching[index]; + if (!middleware) return value; + let next_called = false; + const next = async (nextValue = value) => { + if (next_called) + throw new Error(`Middleware ${middleware.name}:${middleware.phase} called next() more than once.`); + next_called = true; + return dispatch(index + 1, nextValue); + }; + const ctx = + context && typeof context === "object" && !Array.isArray(context) ? (context as Record) : {}; + const context_object: Record = { ...ctx, name, phase }; + const cdpSessionId = typeof context_object.cdpSessionId === "string" ? context_object.cdpSessionId : null; + const result = (await this.evaluateInServiceWorker({ + expression: ` + async (params) => { + const payload = params.payload || {}; + const context = params.context || {}; + const next = async (nextValue = payload) => ({ __ModCDP_middleware_next__: true, value: nextValue }); + const middleware = (${middleware.expression}); + return await middleware(payload, next, context); + } + `, + params: { payload: value, context: context_object }, + cdpSessionId, + })) as Record; + if (result?.__ModCDP_middleware_next__ === true) { + const next_result = await next(result.value as ProtocolPayload); + const { __ModCDP_middleware_next__, value: _value, ...overrides } = result; + if (Object.keys(overrides).length === 0) return next_result; + return next_result != null && typeof next_result === "object" && !Array.isArray(next_result) + ? { ...(next_result as Record), ...overrides } + : overrides; } - buffer = buffer.slice(lineEnd + 2); - if (upper === "PING") writeNats("PONG\r\n"); - } - } - - function natsConnectOptions() { - return { - verbose: false, - pedantic: false, - lang: "modcdp-extension", - version: "1", - protocol: 1, + return result; }; - } - - function debuggerSendCommand( - debuggee: chrome.debugger.Debuggee, - method: string, - params: Record = {}, - ): Promise { - const chromeApi = globalScope.chrome; - return new Promise((resolve, reject) => - chromeApi.debugger.sendCommand(debuggee, method, params, (result) => { - const error = chromeApi.runtime.lastError; - if (error) reject(new Error(error.message)); - else resolve(result as ProtocolResult); - }), - ); - } - - async function getSelfDebuggee(): Promise { - if (selfDebuggee) return selfDebuggee; - const chromeApi = globalScope.chrome; - if (!chromeApi?.debugger?.getTargets || !chromeApi?.debugger?.attach) { - throw new Error("chrome.debugger is unavailable for reverse expression evaluation."); + return dispatch(0, payload); + } + + async handleCommand(method: string, params: ProtocolParams = {}, cdpSessionId: string | null = null) { + const request = { method, params, cdpSessionId }; + const middleware_params = await this.runMiddleware("request", method, params, { cdpSessionId, request }); + if (middleware_params == null) throw new Error(`Request middleware for ${method} returned no params.`); + params = middleware_params as ProtocolParams; + + const types = this.types; + params = types.parseCommandParams(method, params); + if (method === "Mod.configure") { + /* + * Mod.configure is the bootstrap command for the service-worker server. + * It may be the first request received over a downstream-only transport + * such as reversews/nativemessaging/nats, before this server has applied + * the caller's upstream/router config. Params and results still go + * through CDPTypes exactly like every other command; only execution is + * handled directly so the configure payload can install the remote + * registry entries used by later service-worker commands. + */ + await this.configure(ModCDPConfigureParamsSchema.parse(params)); + return types.parseCommandResult(method, params) as ProtocolResult; + } + let result; + const command = types.custom_commands.get(method); + if (command) { + if (typeof command.expression !== "string" || command.expression.length === 0) + throw new Error(`Service-worker command ${method} was registered without an expression.`); + result = await this.evaluateInServiceWorker({ + expression: command.expression, + params, + cdpSessionId, + method, + }); + result = await this.runMiddleware("response", method, result, { + cdpSessionId, + request: { ...request, params }, + response: { result }, + }); + return types.parseCommandResult(method, result) as ProtocolResult; } - const serviceWorkerUrl = currentServiceWorkerUrl(); - const targets = await chromeApi.debugger.getTargets(); - const target = targets.find((candidate) => candidate.url === serviceWorkerUrl); - if (!target?.id) throw new Error(`Could not find ModCDP service worker debugger target ${serviceWorkerUrl}.`); - const debuggee = { targetId: target.id }; - await new Promise((resolve, reject) => - chromeApi.debugger.attach(debuggee, "1.3", () => { - const error = chromeApi.runtime.lastError; - if (!error || error.message?.includes("Another debugger is already attached")) resolve(); - else reject(new Error(error.message)); - }), - ); - selfDebuggee = debuggee; - return debuggee; - } - - function currentServiceWorkerUrl() { - const chromeApi = globalScope.chrome; - const manifest = chromeApi?.runtime?.getManifest?.(); - const service_worker = - manifest && typeof manifest === "object" && "background" in manifest - ? (manifest.background as { service_worker?: unknown } | undefined)?.service_worker - : null; - const service_worker_path = - typeof service_worker === "string" && service_worker.length > 0 - ? service_worker.replace(/^\//, "") - : "modcdp/service_worker.js"; - return chromeApi.runtime.getURL(service_worker_path); - } - async function evaluateInSelf(expression: string): Promise { - const debuggee = await getSelfDebuggee(); - const result = (await debuggerSendCommand(debuggee, "Runtime.evaluate", { - expression, - awaitPromise: true, - returnByValue: true, - })) as cdp.types.ts.Runtime.EvaluateResult; - if (result.exceptionDetails) { - const ex = result.exceptionDetails; - throw new Error(ex.exception?.description || ex.text || "Runtime evaluation failed"); + const upstream = routeFor(method, this.client.router.config.router_routes); + if (upstream === "service_worker") throw new Error(`No service-worker command registered for ${method}.`); + if (upstream !== "auto" && upstream !== "loopback_cdp" && upstream !== "chromedebugger") + throw new Error(`No service-worker command registered for ${method}.`); + const client = this.client; + result = await client.router.send(method, params, cdpSessionId); + result = await this.runMiddleware("response", method, result, { + cdpSessionId, + request: { ...request, params }, + response: { result }, + }); + return client.types.parseCommandResult(method, result) as ProtocolResult; + } + + /** Apply Mod.configure settings, server-owned transport config, and custom registry entries through one path. */ + async configure(params: z.input = {}) { + params = ModCDPConfigureParamsSchema.parse(params); + const custom_commands = params.custom_commands ?? []; + const custom_events = params.custom_events ?? []; + const custom_middlewares = params.custom_middlewares ?? []; + + this.server_browser_token = params.server_browser_token ?? this.server_browser_token; + this.downstream.update(params.downstream ?? {}); + this.client = this.client.configure(params); + for (const command of custom_commands) this.addCustomCommand(command as ModCDPCustomCommandRegistration); + for (const event of custom_events) this.addCustomEvent(event as ModCDPCustomEventRegistration); + for (const middleware of custom_middlewares) this.addMiddleware(middleware as ModCDPMiddlewareRegistration); + if (this.started_at !== null) { + await this.client.connect(); + if (this.client.upstream.config.upstream_mode === "ws") await this.client.router.start(); + await this.client.upstream.getTargets(); } - return (result.result?.value ?? {}) as ProtocolResult; + return this; } - async function evaluateUserExpression({ + private async evaluateInServiceWorker({ expression, params = {}, cdpSessionId = null, @@ -807,842 +359,119 @@ export function installModCDPServer(globalScope: ModCDPGlobalScope = globalThis cdpSessionId?: string | null; method?: string | null; }): Promise { - return evaluateInSelf(` - (async () => { - const params = ${JSON.stringify(params ?? {})}; - const method = ${JSON.stringify(method)}; - const cdp = globalThis.ModCDP.attachToSession(${JSON.stringify(cdpSessionId)}); - const ModCDP = globalThis.ModCDP; - const chrome = globalThis.chrome; - const value = (${expression}); - return typeof value === "function" ? await value(params || {}, method) : value; - })() - `); + const client = this.client; + const service_worker_url = this.currentServiceWorkerUrl(); + const service_worker_target = (await client.upstream.getTargets()).find( + (target) => target.url === service_worker_url, + ); + if (!service_worker_target) throw new Error(`Could not find ModCDP service worker target ${service_worker_url}.`); + const route = await client.router.ensureRouteForTarget(service_worker_target.targetId); + + /* + * MV3 extension service workers cannot opt into arbitrary string eval with + * content_security_policy; Chrome rejects `eval`/`new Function` in extension + * service-worker JavaScript even when the manifest tries to loosen CSP. The + * user-facing Mod.evaluate/custom-command/middleware APIs intentionally take + * JavaScript source strings, so direct in-process execution is not viable. + * + * The workaround is to execute the source as a DevTools Protocol operation + * against this same service-worker target. CDP Runtime.evaluate runs in the + * browser's inspector/evaluation path instead of through service-worker JS + * string eval, so it can evaluate the supplied expression while still seeing + * `globalThis.ModCDP`, `chrome`, and the service-worker global scope. This + * must go through the currently configured browser-target upstream transport + * (`loopback_cdp` or `chromedebugger`) via the generic upstream interface. + */ + const result = await client.upstream.send( + Runtime.EvaluateCommand, + { + expression: ` + (async () => { + const params = ${JSON.stringify(params ?? {})}; + const method = ${JSON.stringify(method)}; + const cdpSessionId = ${JSON.stringify(cdpSessionId)}; + const upstream = globalThis.ModCDP.client; + const downstream = globalThis.ModCDP.downstream; + const ModCDP = globalThis.ModCDP; + const cdp = { + upstream, + client: upstream, + downstream, + send: (method, params = {}, targetCdpSessionId = cdpSessionId) => + ModCDP.handleCommand(method, params, targetCdpSessionId), + }; + const chrome = globalThis.chrome; + const value = (${expression}); + return typeof value === "function" ? await value(params || {}, method) : value; + })() + `, + awaitPromise: true, + returnByValue: true, + }, + route, + ); + if (result.exceptionDetails) { + const exception = result.exceptionDetails; + throw new Error(exception.exception?.description || exception.text || "Runtime evaluation failed"); + } + return (result.result?.value ?? {}) as ProtocolResult; } - async function loopbackWS(endpoint: string): Promise { - const existing = loopbackSockets.get(endpoint); - if (existing?.readyState === WebSocket.OPEN) return existing; - const pending = loopbackSocketPromises.get(endpoint); - if (pending) return pending; - - const nextSocket = openCDPSocket(endpoint).then((ws) => { - loopbackSockets.set(endpoint, ws); - loopbackSocketPromises.delete(endpoint); - ws.addEventListener("message", (event) => { - const msg = JSON.parse(event.data); - const id = typeof msg.id === "number" ? msg.id : null; - if (id == null) { - const method = typeof msg.method === "string" ? msg.method : null; - if (!method) return; - const payload = - msg.params && typeof msg.params === "object" && !Array.isArray(msg.params) - ? (msg.params as ProtocolPayload) - : {}; - const cdpSessionId = typeof msg.sessionId === "string" ? msg.sessionId : null; - const payloadRecord = payload as Record; - const targetInfo = - payloadRecord.targetInfo && - typeof payloadRecord.targetInfo === "object" && - !Array.isArray(payloadRecord.targetInfo) - ? (payloadRecord.targetInfo as Record) - : null; - const attachedSessionId = typeof payloadRecord.sessionId === "string" ? payloadRecord.sessionId : null; - const attachedTargetId = typeof targetInfo?.targetId === "string" ? targetInfo.targetId : null; - if (method === "Target.attachedToTarget" && attachedSessionId != null && attachedTargetId != null) { - loopbackTargetSessions.set(attachedTargetId, attachedSessionId); - loopbackSessionTargets.set(attachedSessionId, attachedTargetId); - } else if (method === "Target.detachedFromTarget") { - const detachedSessionId = - typeof payloadRecord.sessionId === "string" ? payloadRecord.sessionId : cdpSessionId; - const detachedTargetId = - typeof payloadRecord.targetId === "string" - ? payloadRecord.targetId - : detachedSessionId == null - ? null - : (loopbackSessionTargets.get(detachedSessionId) ?? null); - if (detachedTargetId != null) loopbackTargetSessions.delete(detachedTargetId); - if (detachedSessionId != null) loopbackSessionTargets.delete(detachedSessionId); - if (detachedSessionId != null) loopbackSessionContexts.delete(detachedSessionId); - } else if (method === "Runtime.executionContextCreated" && cdpSessionId != null) { - const context = payloadRecord.context; - const contextId = - context && typeof context === "object" && "id" in context && typeof context.id === "number" - ? context.id - : null; - if (contextId != null) { - loopbackSessionContexts.set(cdpSessionId, contextId); - const waiters = loopbackContextWaiters.get(cdpSessionId); - if (waiters) { - loopbackContextWaiters.delete(cdpSessionId); - for (const resolve of waiters) resolve(contextId); - } - } - } - void (async () => { - if ( - method === "Target.attachedToTarget" && - attachedSessionId != null && - (targetInfo?.type === "page" || targetInfo?.type === "iframe") - ) { - await ModCDPServer.handleCommand("Page.enable", {}, attachedSessionId).catch((error) => - console.error("[ModCDPServer] Page.enable failed for attached target", error), - ); - await ModCDPServer.handleCommand( - "Page.setLifecycleEventsEnabled", - { enabled: true }, - attachedSessionId, - ).catch((error) => - console.error("[ModCDPServer] Page.setLifecycleEventsEnabled failed for attached target", error), - ); - } - await publishEvent(method, payload, cdpSessionId); - })().catch((error) => console.error("[ModCDPServer] loopback event listener failed", error)); - return; - } - const pending = loopbackPending.get(id); - if (!pending) return; - loopbackPending.delete(id); - if (msg.error) pending.reject(new Error(msg.error.message)); - else pending.resolve(msg.result || {}); - }); - ws.addEventListener("error", () => { - if (loopbackSockets.get(endpoint) === ws) loopbackSockets.delete(endpoint); - loopbackTargetSessions.clear(); - loopbackSessionTargets.clear(); - loopbackSessionContexts.clear(); - rejectLoopbackPending(new Error(`CDP socket error ${endpoint}`)); - }); - ws.addEventListener("close", (event) => { - if (loopbackSockets.get(endpoint) === ws) loopbackSockets.delete(endpoint); - loopbackTargetSessions.clear(); - loopbackSessionTargets.clear(); - loopbackSessionContexts.clear(); - rejectLoopbackPending( - new Error( - `CDP socket closed ${endpoint} close.code=${event.code} close.reason=${event.reason || ""} close.wasClean=${ - event.wasClean - }`, - ), - ); - }); - return ws; + registerChromeLifecycleEvents() { + chrome.runtime.onStartup.addListener(() => { + void this.start(); }); - loopbackSocketPromises.set(endpoint, nextSocket); - return nextSocket; - } - - async function callLoopbackWS(method: string, params: ProtocolParams = {}, sessionId: string | null = null) { - if (!ModCDPServer.loopback_cdp_url) throw new Error(`No loopback_cdp_url configured for ${method}.`); - const ws = await loopbackWS(ModCDPServer.loopback_cdp_url); - const id = nextLoopbackId++; - const message: { - id: number; - method: string; - params: ProtocolParams; - sessionId?: string; - } = { - id, - method, - params, - }; - if (sessionId) message.sessionId = sessionId; - ws.send(JSON.stringify(message)); - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - if (!loopbackPending.delete(id)) return; - reject(new Error(`${method} timed out after ${ModCDPServer.cdp_send_timeout_ms}ms`)); - }, ModCDPServer.cdp_send_timeout_ms); - loopbackPending.set(id, { - resolve: (value) => { - clearTimeout(timeout); - resolve(value); - }, - reject: (error) => { - clearTimeout(timeout); - reject(error); - }, - }); + chrome.runtime.onInstalled.addListener(() => { + void this.start(); + }); + chrome.tabs.onCreated.addListener(() => { + void this.start(); + }); + chrome.runtime.onConnect.addListener((port) => { + this.handleChromeRuntimeConnect(port); + }); + chrome.action?.onClicked.addListener(() => { + void chrome.runtime.openOptionsPage(); + }); + chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message?.type !== "modcdp.config.status") return false; + sendResponse(this.toJSON()); + return false; }); } - async function initializeLoopbackCDP() { - if (!ModCDPServer.loopback_cdp_url) return; - const ws = await loopbackWS(ModCDPServer.loopback_cdp_url); - if (initializedLoopbackSockets.has(ws)) return; - await callLoopbackWS("Target.setAutoAttach", targetAutoAttachParams); - await callLoopbackWS("Target.setDiscoverTargets", { discover: true }); - initializedLoopbackSockets.add(ws); - } - - function waitForLoopbackExecutionContext( - sessionId: string, - timeoutMs = ModCDPServer.loopback_execution_context_timeout_ms, - ) { - const existing = loopbackSessionContexts.get(sessionId); - if (existing != null) return Promise.resolve(existing); - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - const waiters = loopbackContextWaiters.get(sessionId); - waiters?.delete(complete); - if (waiters?.size === 0) loopbackContextWaiters.delete(sessionId); - reject(new Error(`Timed out waiting for Runtime.executionContextCreated for session ${sessionId}.`)); - }, timeoutMs); - const complete = (contextId: number) => { - clearTimeout(timeout); - resolve(contextId); - }; - const waiters = loopbackContextWaiters.get(sessionId); - if (waiters) waiters.add(complete); - else loopbackContextWaiters.set(sessionId, new Set([complete])); + private handleChromeRuntimeConnect(port: chrome.runtime.Port) { + if (port.name !== OFFSCREEN_KEEP_ALIVE_PORT_NAME) return; + this.offscreen_keep_alive_port = port; + port.onMessage.addListener(() => {}); + port.onDisconnect.addListener(() => { + if (this.offscreen_keep_alive_port === port) this.offscreen_keep_alive_port = null; }); } - async function ensureOffscreenKeepAlive() { - const chromeApi = globalScope.chrome; - const offscreen = chromeApi?.offscreen; - if (!offscreen || !chromeApi?.runtime?.getURL) return { started: false, reason: "offscreen_unavailable" }; - - const offscreenUrl = chromeApi.runtime.getURL(offscreenKeepAlivePath); - try { - const existingContexts = chromeApi.runtime.getContexts - ? await chromeApi.runtime.getContexts({ - contextTypes: ["OFFSCREEN_DOCUMENT"], - documentUrls: [offscreenUrl], - }) - : []; - if (existingContexts.length > 0) return { started: true, existing: true }; - - creatingOffscreenKeepAlive ??= offscreen - .createDocument({ - url: offscreenKeepAlivePath, - reasons: ["BLOBS"], - justification: "Keep ModCDP service worker active while CDP clients route commands through it.", - }) - .finally(() => { - creatingOffscreenKeepAlive = null; - }); - await creatingOffscreenKeepAlive; - return { started: true }; - } catch (error) { - return { started: false, reason: errorMessage(error) }; - } + private currentServiceWorkerUrl() { + const chrome_api = globalThis.chrome; + const manifest = chrome_api?.runtime?.getManifest?.(); + const service_worker = + manifest && typeof manifest === "object" && "background" in manifest + ? (manifest.background as { service_worker?: unknown } | undefined)?.service_worker + : null; + const service_worker_path = + typeof service_worker === "string" && service_worker.length > 0 + ? service_worker.replace(/^\//, "") + : "modcdp/service_worker.js"; + return chrome_api.runtime.getURL(service_worker_path); } - - const ModCDPServer = { - __ModCDPServerVersion: MODCDP_SERVER_VERSION, - routes: { ...defaultRoutes }, - loopback_cdp_url: null as string | null, - browser_token: null as string | null, - native_bridge_attempts: 0, - native_bridge_last_error: null as string | null, - native_bridge_connected: false, - cdp_send_timeout_ms: DEFAULT_CDP_SEND_TIMEOUT_MS, - loopback_execution_context_timeout_ms: DEFAULT_LOOPBACK_EXECUTION_CONTEXT_TIMEOUT_MS, - ws_connect_error_settle_timeout_ms: DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS, - downstream_client_timeout_ms: DEFAULT_DOWNSTREAM_CLIENT_TIMEOUT_MS, - close_browser_on_downstream_disconnect: false, - types: null as (typeof import("../types/generated/zod.js"))["types"] | null, - commands: null as (typeof import("../types/generated/zod.js"))["commands"] | null, - events: null as (typeof import("../types/generated/zod.js"))["events"] | null, - startOffscreenKeepAlive, - startReverseBridge( - endpoint: string, - { - reconnect_interval_ms = DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS, - }: { - reconnect_interval_ms?: number; - } = {}, - ) { - if (!/^wss?:\/\//i.test(endpoint)) { - throw new Error(`reverse proxy endpoint must be a ws:// or wss:// URL, got ${endpoint}.`); - } - reverseBridgeUrl = endpoint; - reverseBridgeReconnectIntervalMs = reconnect_interval_ms; - void connectReverseBridge(endpoint).catch(() => { - scheduleReverseBridgeReconnect(reverseBridgeReconnectIntervalMs); - }); - return { - upstream_reversews_url: endpoint, - reconnect_interval_ms, - connecting: true, - }; - }, - stopReverseBridge, - startNativeBridge( - hostName = DEFAULT_NATIVE_BRIDGE_HOST_NAME, - { - reconnect_interval_ms = DEFAULT_NATIVE_BRIDGE_RECONNECT_INTERVAL_MS, - }: { - reconnect_interval_ms?: number; - } = {}, - ) { - nativeBridgeHostName = hostName; - nativeBridgeReconnectIntervalMs = reconnect_interval_ms; - return connectNativeBridge(hostName); - }, - startNatsBridge( - endpoint: string, - { - upstream_nats_subject_prefix = DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX, - reconnect_interval_ms = DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS, - }: { - upstream_nats_subject_prefix?: string; - reconnect_interval_ms?: number; - } = {}, - ) { - if (!upstream_nats_subject_prefix || /[\s*>]/.test(upstream_nats_subject_prefix)) - throw new Error(`Invalid NATS subject prefix ${upstream_nats_subject_prefix}`); - nats_bridge_url = endpoint; - nats_bridge_subject_prefix = upstream_nats_subject_prefix; - nats_bridge_reconnect_interval_ms = reconnect_interval_ms; - void connectNatsBridge(endpoint).catch(() => { - scheduleNatsBridgeReconnect(nats_bridge_reconnect_interval_ms); - }); - return { - upstream_nats_url: endpoint, - upstream_nats_subject_prefix, - reconnect_interval_ms, - connecting: true, - }; - }, - ensureOffscreenKeepAlive, - - async loadTypes() { - runtime_types_promise ??= import("../types/generated/zod.js").then((module) => { - this.types = module.types; - this.commands = module.commands; - this.events = module.events; - return module.types; - }); - return runtime_types_promise; - }, - - async configure(params: ModCDPConfigureParams = {}) { - const upstream = params.upstream ?? {}; - const server = params.server ?? {}; - const { - server_loopback_cdp_url = this.loopback_cdp_url, - server_routes, - server_browser_token = this.browser_token, - server_cdp_send_timeout_ms = this.cdp_send_timeout_ms, - server_loopback_execution_context_timeout_ms = this.loopback_execution_context_timeout_ms, - server_ws_connect_error_settle_timeout_ms = this.ws_connect_error_settle_timeout_ms, - server_downstream_client_timeout_ms = this.downstream_client_timeout_ms, - server_close_browser_on_downstream_disconnect = this.close_browser_on_downstream_disconnect, - } = server; - const { custom_commands = [], custom_events = [], custom_middlewares = [] } = params; - this.loopback_cdp_url = await resolveCDPEndpoint(server_loopback_cdp_url); - this.browser_token = server_browser_token; - this.cdp_send_timeout_ms = server_cdp_send_timeout_ms; - this.loopback_execution_context_timeout_ms = server_loopback_execution_context_timeout_ms; - this.ws_connect_error_settle_timeout_ms = server_ws_connect_error_settle_timeout_ms; - this.downstream_client_timeout_ms = server_downstream_client_timeout_ms; - this.close_browser_on_downstream_disconnect = server_close_browser_on_downstream_disconnect; - if (upstream.upstream_mode === "nats" && upstream.upstream_nats_url) { - this.startNatsBridge(upstream.upstream_nats_url, { - upstream_nats_subject_prefix: upstream.upstream_nats_subject_prefix ?? DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX, - }); - } - if (server_routes) this.routes = { ...defaultRoutes, ...server_routes }; - else { - this.routes = { ...defaultRoutes }; - await this.discoverLoopbackCDP(); - } - for (const command of custom_commands) this.addCustomCommand(command as ModCDPCustomCommandRegistration); - for (const event of custom_events) this.addCustomEvent(event as ModCDPCustomEventRegistration); - for (const middleware of custom_middlewares) this.addMiddleware(middleware as ModCDPMiddlewareRegistration); - await initializeLoopbackCDP(); - return { loopback_cdp_url: this.loopback_cdp_url, routes: this.routes }; - }, - - addCustomCommand({ - name, - params_schema = null, - result_schema = null, - expression = null, - handler, - }: ModCDPCustomCommandRegistration) { - name = normalizeModCDPName(name); - if (!/^[^.]+\.[^.]+$/.test(name)) throw new Error("name must be in Domain.method form."); - if (typeof handler !== "function" && typeof expression === "string") { - handler = async (params: ProtocolParams = {}, cdpSessionId: string | null = null, method: string = name) => { - return await evaluateUserExpression({ - expression, - params, - cdpSessionId, - method, - }); - }; - } - if (typeof handler !== "function") throw new Error(`Custom command ${name} was registered without a handler.`); - commandHandlers.set(name, { - name, - handler, - params_schema: normalizeModCDPPayloadSchema(params_schema), - result_schema: normalizeModCDPPayloadSchema(result_schema), - expression, - }); - return { name, registered: true }; - }, - - addCustomEvent({ name, event_schema = null }: ModCDPCustomEventRegistration) { - name = normalizeModCDPName(name); - if (!/^[^.]+\.[^.]+$/.test(name)) throw new Error("name must be in Domain.event form."); - eventBindings.set(name, { - name, - event_schema: normalizeModCDPPayloadSchema(event_schema), - }); - return { name, registered: true }; - }, - - addEventListener(listener: (event: string, data: ProtocolPayload, cdpSessionId: string | null) => void) { - eventListeners.add(listener); - return { remove: () => eventListeners.delete(listener) }; - }, - - addMiddleware({ name = "*", phase, expression = null, handler }: ModCDPMiddlewareRegistration) { - name = normalizeModCDPName(name); - if (!["request", "response", "event"].includes(phase)) - throw new Error("phase must be request, response, or event."); - if (name !== "*" && (!name || !name.includes("."))) throw new Error("name must be '*' or Domain.name form."); - if (typeof handler !== "function" && typeof expression === "string") { - handler = async (payload: ProtocolPayload, next: unknown, context: ProtocolPayload = {}) => { - const context_object = context && typeof context === "object" ? (context as Record) : {}; - const cdpSessionId = typeof context_object.cdpSessionId === "string" ? context_object.cdpSessionId : null; - const result = (await evaluateInSelf(` - (async () => { - const payload = ${JSON.stringify(payload ?? {})}; - const context = ${JSON.stringify(context ?? {})}; - const cdp = globalThis.ModCDP.attachToSession(${JSON.stringify(cdpSessionId)}); - const ModCDP = globalThis.ModCDP; - const chrome = globalThis.chrome; - const next = async (nextValue = payload) => ({ __ModCDP_middleware_next__: true, value: nextValue }); - const handler = (${expression}); - return await handler(payload, next, context); - })() - `)) as Record; - if (result?.__ModCDP_middleware_next__ === true && typeof next === "function") { - const nextResult = await next(result.value); - const { __ModCDP_middleware_next__, value, ...overrides } = result; - if (Object.keys(overrides).length === 0) return nextResult; - return nextResult != null && typeof nextResult === "object" && !Array.isArray(nextResult) - ? { ...(nextResult as Record), ...overrides } - : overrides; - } - return result; - }; - } - if (typeof handler !== "function") { - throw new Error(`Middleware ${name}:${phase} was registered without a handler.`); - } - middlewares[phase].push({ name, phase, expression, handler }); - return { name, phase, registered: true }; - }, - - async runMiddleware(phase: MiddlewarePhase, name: string, payload: ProtocolPayload, context: ProtocolPayload = {}) { - const matching = (middlewares[phase] || []).filter( - (middleware) => middleware.name === "*" || middleware.name === name, - ); - const dispatch = async (index: number, value: ProtocolPayload): Promise => { - const middleware = matching[index]; - if (!middleware) return value; - let nextCalled = false; - const next = async (nextValue = value) => { - if (nextCalled) - throw new Error(`Middleware ${middleware.name}:${middleware.phase} called next() more than once.`); - nextCalled = true; - return dispatch(index + 1, nextValue); - }; - const ctx = context && typeof context === "object" ? context : {}; - return middleware.handler(value, next, { ...ctx, name, phase }); - }; - return dispatch(0, payload); - }, - - async handleCommand(method: string, params: ProtocolParams = {}, cdpSessionId: string | null = null) { - if (method === "Mod.configure") registerDownstreamClient(); - touchDownstreamClientLease(cdpSessionId); - const request = { method, params, cdpSessionId }; - const middlewareParams = await this.runMiddleware("request", method, params, { cdpSessionId, request }); - if (middlewareParams == null) throw new Error(`Request middleware for ${method} returned no params.`); - params = middlewareParams as ProtocolParams; - - const command = registryMatch(commandHandlers, method); - params = commandParamsSchema(method, command)?.parse(params) ?? params; - let result; - if (command) { - result = await command.handler(params, cdpSessionId, method); - result = await this.runMiddleware("response", method, result, { - cdpSessionId, - request: { ...request, params }, - response: { result }, - }); - return commandResultSchema(method, command)?.parse(result) ?? result; - } - - let upstream = "chrome_debugger"; - for (const [pattern, route] of Object.entries(this.routes || {}) as [string, string][]) { - if (pattern === "*.*") { - upstream = route; - continue; - } - if (pattern.endsWith(".*") && method.startsWith(pattern.slice(0, -1))) { - upstream = route; - break; - } - if (pattern === method) { - upstream = route; - break; - } - } - - if (upstream === "auto") { - if (this.loopback_cdp_url) { - try { - result = await this.sendLoopback(method, params, cdpSessionId); - } catch { - result = await this.sendChromeDebugger(method, params); - } - } else { - result = await this.sendChromeDebugger(method, params); - } - } else if (upstream === "loopback_cdp") result = await this.sendLoopback(method, params, cdpSessionId); - else if (upstream === "chrome_debugger") result = await this.sendChromeDebugger(method, params); - else throw new Error(`No ModCDP command registered for ${method}.`); - - result = await this.runMiddleware("response", method, result, { - cdpSessionId, - request: { ...request, params }, - response: { result }, - }); - return commandResultSchema(method, null)?.parse(result) ?? result; - }, - - attachToSession(cdpSessionId: string | null = null) { - return { - sessionId: cdpSessionId, - get types() { - return ModCDPServer.types; - }, - get commands() { - return ModCDPServer.commands; - }, - get events() { - return ModCDPServer.events; - }, - send: (method: string, params: ProtocolParams = {}) => this.handleCommand(method, params, cdpSessionId), - emit: (eventName: string, payload: ProtocolPayload = {}) => this.emit(eventName, payload, cdpSessionId), - }; - }, - - async emit(eventName: string, payload: ProtocolPayload = {}, cdpSessionId: string | null = null) { - const event = registryMatch(eventBindings, eventName); - if (!event) - return { - event: eventName, - emitted: false, - reason: "event_not_registered", - }; - payload = eventPayloadSchema(eventName, event)?.parse(payload) ?? payload; - const customBinding = globalScope[CUSTOM_EVENT_BINDING_NAME]; - if ( - typeof customBinding !== "function" && - reverseBridgeSocket?.readyState !== WebSocket.OPEN && - !nativeBridgePort && - nats_bridge_socket?.readyState !== WebSocket.OPEN - ) - return { - event: eventName, - emitted: false, - reason: "binding_not_installed", - }; - return publishEvent(eventName, payload, cdpSessionId); - }, - - async discoverLoopbackCDP(): Promise<{ - loopback_cdp_url: string | null; - verified: boolean; - version?: unknown; - }> { - if (!this.browser_token) return { loopback_cdp_url: null as null, verified: false }; - - const url = "http://127.0.0.1:9222"; - const previousLoopbackUrl = this.loopback_cdp_url; - const fail = (version?: unknown) => { - this.loopback_cdp_url = previousLoopbackUrl ?? null; - return { - loopback_cdp_url: null as null, - verified: false, - ...(version ? { version } : {}), - }; - }; - try { - const version = await fetch(`${url}/json/version`).then((response) => response.ok && response.json()); - if (!version?.webSocketDebuggerUrl) return fail(); - - this.loopback_cdp_url = version.webSocketDebuggerUrl; - const { targetInfos } = (await callLoopbackWS("Target.getTargets")) as cdp.types.ts.Target.GetTargetsResult; - const serviceWorkerUrl = currentServiceWorkerUrl(); - const worker = targetInfos.find( - (target) => target.type === "service_worker" && target.url === serviceWorkerUrl, - ); - if (!worker) return fail(version); - - const { sessionId } = (await callLoopbackWS("Target.attachToTarget", { - targetId: worker.targetId, - flatten: true, - })) as cdp.types.ts.Target.AttachToTargetResult; - loopbackTargetSessions.set(worker.targetId, sessionId); - loopbackSessionTargets.set(sessionId, worker.targetId); - const contextIdPromise = waitForLoopbackExecutionContext(sessionId); - await callLoopbackWS("Runtime.enable", {}, sessionId); - const executionContextId = await contextIdPromise; - const result = (await callLoopbackWS( - "Runtime.callFunctionOn", - { - functionDeclaration: `function() { return globalThis.ModCDP?.browser_token === ${JSON.stringify(this.browser_token)}; }`, - executionContextId, - returnByValue: true, - }, - sessionId, - )) as cdp.types.ts.Runtime.EvaluateResult; - if (result.result?.value !== true) return fail(version); - - await initializeLoopbackCDP(); - return { - loopback_cdp_url: this.loopback_cdp_url, - verified: true, - version, - }; - } catch { - return fail(); - } - }, - - async sendLoopback(method: string, params: ProtocolParams = {}, cdpSessionId: string | null = null) { - if (!this.loopback_cdp_url) throw new Error(`No loopback_cdp_url configured for ${method}.`); - - await initializeLoopbackCDP(); - - const domain = method.split(".")[0] ?? ""; - if (browserLevelDomains.has(domain)) return await callLoopbackWS(method, params); - if (cdpSessionId) return await callLoopbackWS(method, params, cdpSessionId); - - const { - debuggee = null, - tabId = null, - targetId = null, - extensionId = null, - ...commandParams - } = params as CdpDebuggeeCommandParams; - const resolvedDebuggee = debuggee ?? compactDebuggee({ tabId, targetId, extensionId }); - - const chromeApi = globalScope.chrome; - let resolvedTargetId = resolvedDebuggee.targetId || null; - if (!resolvedTargetId) { - let resolvedTabId = resolvedDebuggee.tabId || null; - let resolvedTabUrl: string | null = null; - if (!resolvedTabId) { - const [tab] = chromeApi.tabs?.query - ? await chromeApi.tabs.query({ - active: true, - lastFocusedWindow: true, - }) - : []; - resolvedTabId = tab?.id || null; - resolvedTabUrl = tab?.url || tab?.pendingUrl || null; - } else if (chromeApi.tabs?.get) { - const tab = await chromeApi.tabs.get(resolvedTabId).catch((): null => null); - resolvedTabUrl = tab?.url || tab?.pendingUrl || null; - } - if (resolvedTabId && chromeApi.debugger?.getTargets) { - const targets = await chromeApi.debugger.getTargets(); - resolvedTargetId = - targets.find((target) => target.tabId === resolvedTabId && target.type === "page")?.id || null; - } - if (!resolvedTargetId) { - const { targetInfos } = (await callLoopbackWS("Target.getTargets")) as cdp.types.ts.Target.GetTargetsResult; - const pageTargets = targetInfos.filter((target) => target.type === "page"); - resolvedTargetId = - pageTargets.find((target) => resolvedTabUrl && target.url === resolvedTabUrl)?.targetId || - pageTargets[0]?.targetId || - null; - } - if (!resolvedTargetId) { - const created = (await callLoopbackWS("Target.createTarget", { - url: "about:blank#modcdp", - })) as cdp.types.ts.Target.CreateTargetResult; - resolvedTargetId = created.targetId || null; - } - } - if (!resolvedTargetId) throw new Error(`loopback_cdp route for ${method} could not resolve a page target.`); - - const existingSessionId = loopbackTargetSessions.get(resolvedTargetId); - if (existingSessionId) return await callLoopbackWS(method, commandParams, existingSessionId); - - const attached = (await callLoopbackWS("Target.attachToTarget", { - targetId: resolvedTargetId, - flatten: true, - })) as cdp.types.ts.Target.AttachToTargetResult; - const sessionId = attached.sessionId; - loopbackTargetSessions.set(resolvedTargetId, sessionId); - loopbackSessionTargets.set(sessionId, resolvedTargetId); - await callLoopbackWS("Target.setAutoAttach", targetAutoAttachParams, sessionId).catch(() => {}); - return await callLoopbackWS(method, commandParams, sessionId); - }, - - async sendChromeDebugger(method: string, params: ProtocolParams = {}) { - const chromeApi = globalScope.chrome; - if (!chromeApi?.debugger?.sendCommand) throw new Error("chrome.debugger is unavailable."); - - const { - debuggee = null, - tabId = null, - targetId = null, - extensionId = null, - ...commandParams - } = params as CdpDebuggeeCommandParams; - const resolvedDebuggee = debuggee ?? compactDebuggee({ tabId, targetId, extensionId }); - if (Object.keys(resolvedDebuggee).length === 0) { - let tab: chrome.tabs.Tab | undefined; - [tab] = await chromeApi.tabs.query({ - active: true, - lastFocusedWindow: true, - }); - if (!tab?.id) [tab] = await chromeApi.tabs.query({}); - if (!tab?.id) { - try { - tab = await chromeApi.tabs.create({ - url: "https://example.com/#modcdp", - active: true, - }); - } catch { - const win = await chromeApi.windows.create({ - url: "https://example.com/#modcdp", - focused: true, - }); - tab = win?.tabs?.[0]; - } - } - if (!tab?.id) throw new Error(`chrome_debugger route for ${method} could not find an active tab.`); - resolvedDebuggee.tabId = tab.id; - } - - const key = JSON.stringify(resolvedDebuggee); - if (!attachedDebuggees.has(key)) { - try { - await new Promise((resolve, reject) => - chromeApi.debugger.attach(resolvedDebuggee, "1.3", () => { - const error = chromeApi.runtime.lastError; - if (error) reject(new Error(error.message)); - else resolve(); - }), - ); - } catch (error) { - if (!errorMessage(error).includes("Another debugger is already attached")) throw error; - } - await new Promise((resolve, reject) => - chromeApi.debugger.sendCommand(resolvedDebuggee, "Target.setAutoAttach", targetAutoAttachParams, () => { - const error = chromeApi.runtime.lastError; - if (error) reject(new Error(error.message)); - else resolve(); - }), - ); - attachedDebuggees.add(key); - } - - return new Promise((resolve, reject) => - chromeApi.debugger.sendCommand(resolvedDebuggee, method, commandParams, (result) => { - const error = chromeApi.runtime.lastError; - if (error) reject(new Error(error.message)); - else resolve(result as ProtocolResult); - }), - ); - }, - }; - - globalScope.ModCDP = ModCDPServer; - - ModCDPServer.addCustomEvent({ - name: "Mod.pong", - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.ping", - handler: async (raw_params: ProtocolParams = {}, cdpSessionId: string | null = null) => { - const params = raw_params as ModCDPPingParams; - const received_at = Date.now(); - await ModCDPServer.emit( - "Mod.pong", - { - sent_at: typeof params.sent_at === "number" ? params.sent_at : received_at, - received_at, - from: "extension-service-worker", - }, - cdpSessionId, - ); - return { ok: true }; - }, - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.configure", - handler: async (params: ProtocolParams = {}) => ModCDPServer.configure(params as ModCDPConfigureParams), - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.evaluate", - handler: async (raw_params: ProtocolParams = {}) => { - const { expression, params = {}, cdpSessionId = null } = raw_params as Record; - return await evaluateUserExpression({ - expression: String(expression), - params: params as ProtocolPayload, - cdpSessionId: typeof cdpSessionId === "string" ? cdpSessionId : null, - }); - }, - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.addCustomCommand", - handler: async (params: ProtocolParams = {}) => - ModCDPServer.addCustomCommand(params as ModCDPCustomCommandRegistration), - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.addCustomEvent", - handler: async (params: ProtocolParams = {}) => - ModCDPServer.addCustomEvent(params as ModCDPCustomEventRegistration), - }); - - ModCDPServer.addCustomCommand({ - name: "Mod.addMiddleware", - handler: async (params: ProtocolParams = {}) => ModCDPServer.addMiddleware(params as ModCDPMiddlewareRegistration), - }); - - const chromeApi = globalScope.chrome; - try { - chromeApi?.runtime?.onStartup?.addListener(startOffscreenKeepAlive); - } catch {} - try { - chromeApi?.runtime?.onInstalled?.addListener(startOffscreenKeepAlive); - } catch {} - try { - chromeApi?.tabs?.onCreated?.addListener(startOffscreenKeepAlive); - } catch {} - try { - chromeApi?.runtime?.onConnect?.addListener((port) => { - if (port.name !== offscreenKeepAlivePortName) return; - offscreenKeepAlivePort = port; - port.onMessage.addListener(() => {}); - port.onDisconnect.addListener(() => { - if (offscreenKeepAlivePort === port) offscreenKeepAlivePort = null; - }); - }); - } catch {} - startOffscreenKeepAlive(); - - return ModCDPServer; } -export const ModCDPServer = installModCDPServer(globalThis); +export { ModCDPServer }; +export { + DEFAULT_NATIVEMESSAGING_BRIDGE_HOST_NAME, + DEFAULT_NATIVEMESSAGING_BRIDGE_RECONNECT_INTERVAL_MS, +} from "../transport/NativeMessagingDownstreamTransport.js"; +export { + DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS, + DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX, +} from "../transport/NATSDownstreamTransport.js"; +export { DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS } from "../transport/ReverseWSDownstreamTransport.js"; +export type { ModCDPServerConfig } from "../types/modcdp.js"; diff --git a/js/src/translate/translate.ts b/js/src/translate/translate.ts index da94e2a7..c4937902 100644 --- a/js/src/translate/translate.ts +++ b/js/src/translate/translate.ts @@ -1,3 +1,7 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/translate/translate.py +// - ./go/modcdp/translate/translate.go // @ts-nocheck // Pure stateless translation between ModCDP and raw CDP messages. // No I/O, no maps, no classes. Trivial to port to any language. @@ -5,12 +9,8 @@ // side, so the binding payload format only has one definition. import type { - ModCDPAddCustomCommandParams, - ModCDPAddMiddlewareParams, ModCDPBindingPayload, ModCDPCustomPayload, - ModCDPEvaluateParams, - ModCDPPingParams, ModCDPRoutes, ProtocolParams, ProtocolResult, @@ -19,17 +19,18 @@ import type { UnwrappedModCDPEvent, } from "../types/modcdp.js"; import type { cdp } from "../types/generated/cdp.js"; +import * as Runtime from "../types/generated/zod/Runtime.js"; -export const UPSTREAM_EVENT_BINDING_NAME = "__ModCDP_event_from_upstream__"; -export const CUSTOM_EVENT_BINDING_NAME = "__ModCDP_custom_event__"; +const UPSTREAM_EVENT_BINDING_NAME = "__ModCDP_event_from_upstream__"; +const CUSTOM_EVENT_BINDING_NAME = "__ModCDP_custom_event__"; -export const DEFAULT_CLIENT_ROUTES = { +const DEFAULT_CLIENT_ROUTES = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "service_worker", } satisfies ModCDPRoutes; -type TranslateOptions = { routes?: ModCDPRoutes; cdpSessionId?: string | null; targetCdpSessionId?: string | null }; +type TranslateConfig = { routes?: ModCDPRoutes; cdpSessionId?: string | null }; function normalizeModCDPName( value: @@ -38,7 +39,12 @@ function normalizeModCDPName( cdp_event_name?: string; id?: string; name?: string; - meta?: () => { cdp_command_name?: unknown; cdp_event_name?: unknown; id?: unknown; name?: unknown }; + meta?: () => { + cdp_command_name?: unknown; + cdp_event_name?: unknown; + id?: unknown; + name?: unknown; + }; } | string, ) { @@ -57,7 +63,7 @@ function normalizeModCDPName( return name; } -export function routeFor(method: string, routes: ModCDPRoutes = {}) { +function routeFor(method: string, routes: ModCDPRoutes = {}) { if (Object.prototype.hasOwnProperty.call(routes, method)) return routes[method]; let bestPrefixLen = -1; let bestRoute: string | null = null; @@ -76,100 +82,7 @@ export function routeFor(method: string, routes: ModCDPRoutes = {}) { // --- outbound: ModCDP method -> Runtime.* params on the extension session -- -export function wrapModCDPEvaluate({ - expression, - params = {}, - cdpSessionId = null, -}: ModCDPEvaluateParams): cdp.types.ts.Runtime.EvaluateParams { - return { - functionDeclaration: ` - async function() { - const params = ${JSON.stringify(params)}; - const cdp = globalThis.ModCDP.attachToSession(${JSON.stringify(cdpSessionId)}); - const ModCDP = globalThis.ModCDP; - const chrome = globalThis.chrome; - const value = (${expression}); - return typeof value === "function" ? await value(params) : value; - } - `, - awaitPromise: true, - returnByValue: true, - }; -} - -export function wrapModCDPAddCustomCommand({ - name, - expression, -}: ModCDPAddCustomCommandParams): cdp.types.ts.Runtime.EvaluateParams { - const commandName = normalizeModCDPName(name); - return { - functionDeclaration: ` - function() { - return globalThis.ModCDP.addCustomCommand({ - name: ${JSON.stringify(commandName)}, - params_schema: null, - result_schema: null, - expression: ${JSON.stringify(expression)}, - handler: async (params, cdpSessionId, method) => { - const cdp = globalThis.ModCDP.attachToSession(cdpSessionId); - const ModCDP = globalThis.ModCDP; - const chrome = globalThis.chrome; - const handler = (${expression}); - return await handler(params || {}, method); - }, - }); - } - `, - awaitPromise: true, - returnByValue: true, - }; -} - -export function wrapModCDPAddCustomEvent({ name }: { name: string }): cdp.types.ts.Runtime.EvaluateParams { - const eventName = normalizeModCDPName(name); - return { - functionDeclaration: ` - function() { - return globalThis.ModCDP.addCustomEvent({ - name: ${JSON.stringify(eventName)}, - event_schema: null, - }); - } - `, - awaitPromise: true, - returnByValue: true, - }; -} - -export function wrapModCDPAddMiddleware({ - name = "*", - phase, - expression, -}: ModCDPAddMiddlewareParams): cdp.types.ts.Runtime.EvaluateParams { - const middlewareName = normalizeModCDPName(name); - return { - functionDeclaration: ` - function() { - return globalThis.ModCDP.addMiddleware({ - name: ${JSON.stringify(middlewareName)}, - phase: ${JSON.stringify(phase)}, - expression: ${JSON.stringify(expression)}, - handler: async (payload, next, context = {}) => { - const cdp = globalThis.ModCDP.attachToSession(context.cdpSessionId ?? null); - const ModCDP = globalThis.ModCDP; - const chrome = globalThis.chrome; - const middleware = (${expression}); - return await middleware(payload, next, context); - }, - }); - } - `, - awaitPromise: true, - returnByValue: true, - }; -} - -export function wrapCustomCommand( +function wrapCustomCommand( method: string, params: ProtocolParams = {}, cdpSessionId: string | null = null, @@ -184,56 +97,23 @@ export function wrapCustomCommand( } function wrapServiceWorkerCommand(method: string, params: ProtocolParams = {}, cdpSessionId: string | null = null) { - if (method === "Mod.ping" && !Object.prototype.hasOwnProperty.call(params, "sent_at")) { - params = { ...(params as ModCDPPingParams), sent_at: Date.now() }; - } - - if (method === "Mod.addCustomEvent") { - const eventParams = params as { name: any }; - const eventName = normalizeModCDPName(eventParams.name); - return [ - { - method: "Runtime.callFunctionOn", - params: wrapModCDPAddCustomEvent({ name: eventName }), - unwrap: "runtime" as const, - }, - ]; - } - - let runtimeParams; - let unwrap: "runtime" | "runtime_json" = "runtime"; - if (method === "Mod.evaluate") { - const evaluateParams = params as ModCDPEvaluateParams; - runtimeParams = wrapModCDPEvaluate({ - ...evaluateParams, - cdpSessionId: evaluateParams.cdpSessionId ?? cdpSessionId, - }); - } else if (method === "Mod.addCustomCommand") { - runtimeParams = wrapModCDPAddCustomCommand(params as ModCDPAddCustomCommandParams); - } else if (method === "Mod.addMiddleware") { - runtimeParams = wrapModCDPAddMiddleware(params as ModCDPAddMiddlewareParams); - } else { - runtimeParams = wrapCustomCommand( - method, - params, - ((params as ModCDPCustomPayload).cdpSessionId as string) ?? cdpSessionId, - ); - unwrap = "runtime_json"; - } - return [ { - method: "Runtime.callFunctionOn", - params: runtimeParams, - unwrap, + method: Runtime.CallFunctionOnCommand.id, + params: wrapCustomCommand( + method, + params, + ((params as ModCDPCustomPayload).cdpSessionId as string) ?? cdpSessionId, + ), + unwrap: "runtime_json" as const, }, ]; } -export function wrapCommandIfNeeded( +function wrapCommandIfNeeded( method: string, params: ProtocolParams = {}, - { routes = DEFAULT_CLIENT_ROUTES, cdpSessionId = null, targetCdpSessionId = null }: TranslateOptions = {}, + { routes = DEFAULT_CLIENT_ROUTES, cdpSessionId = null }: TranslateConfig = {}, ): TranslatedCommand { params = params ?? {}; const route = routeFor(method, routes); @@ -241,7 +121,13 @@ export function wrapCommandIfNeeded( return { route, target: "direct_cdp", - steps: [{ method, params, ...(targetCdpSessionId ? { sessionId: targetCdpSessionId } : {}) }], + steps: [ + { + method, + params, + ...(cdpSessionId ? { sessionId: cdpSessionId } : {}), + }, + ], }; } if (route === "service_worker") { @@ -269,7 +155,7 @@ function unwrapRuntimeJsonResponse(result: cdp.types.ts.Runtime.EvaluateResult) return typeof value === "string" ? JSON.parse(value) : value; } -export function unwrapResponseIfNeeded( +function unwrapResponseIfNeeded( result: ProtocolResult | cdp.types.ts.Runtime.EvaluateResult, unwrap: string | null = null, ) { @@ -280,13 +166,13 @@ export function unwrapResponseIfNeeded( // Returns { event, data } or null when the binding is not a ModCDP event, // when a custom binding payload is scoped to a different cdpSessionId than // ourSessionId, or when the payload string is not valid JSON. -export function unwrapEventIfNeeded( +function unwrapEventIfNeeded( method: string, params: RuntimeBindingCalledEvent, sessionId: string | null = null, ourSessionId: string | null = null, ): UnwrappedModCDPEvent | null { - if (method !== "Runtime.bindingCalled") return null; + if (method !== Runtime.BindingCalledEvent.id) return null; let payload: ModCDPBindingPayload; try { payload = JSON.parse(params.payload || "{}"); @@ -308,6 +194,18 @@ export function unwrapEventIfNeeded( // --- shared encoder used by the extension service worker -------------------- -export function encodeBindingPayload({ event, data, cdpSessionId = null }: ModCDPBindingPayload) { +function encodeBindingPayload({ event, data, cdpSessionId = null }: ModCDPBindingPayload) { return JSON.stringify({ event, data, cdpSessionId }); } + +export { + UPSTREAM_EVENT_BINDING_NAME, + CUSTOM_EVENT_BINDING_NAME, + DEFAULT_CLIENT_ROUTES, + routeFor, + wrapCustomCommand, + wrapCommandIfNeeded, + unwrapResponseIfNeeded, + unwrapEventIfNeeded, + encodeBindingPayload, +}; diff --git a/js/src/transport/ChromeDebuggerUpstreamTransport.ts b/js/src/transport/ChromeDebuggerUpstreamTransport.ts new file mode 100644 index 00000000..10d773f9 --- /dev/null +++ b/js/src/transport/ChromeDebuggerUpstreamTransport.ts @@ -0,0 +1,323 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. +import { z } from "zod"; +import type { cdp } from "../types/generated/cdp.js"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import * as Target from "../types/generated/zod/Target.js"; +import type { CdpCommandMessage, CdpDebuggeeCommandParams, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS } from "../types/modcdp.js"; +import { UpstreamTransport, type TargetRoute } from "./UpstreamTransport.js"; + +const ChromeDebuggerUpstreamTransportConfigSchema = z.object({ + upstream_mode: z.literal("chromedebugger").default("chromedebugger"), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), +}); +type ChromeDebuggerUpstreamTransportConfig = z.infer; + +const target_auto_attach_params = { + autoAttach: true, + waitForDebuggerOnStart: false, + flatten: true, +} satisfies cdp.types.ts.Target.SetAutoAttachParams; + +/** + * Owns browser-target upstream traffic sent through chrome.debugger. + * + * This class owns chrome.debugger debuggee selection, attach lifecycle, + * chrome.debugger event normalization, and target/session bookkeeping needed by + * debugger routing. It does not choose ModCDP routes, manage custom command registries, run + * middleware, publish ModCDP events, or own loopback WebSocket state. + * + * Lifecycle: + * 1. The server constructs the transport with an extension service-worker + * global scope. + * 2. `getTargets()` reads chrome.debugger targets and refreshes tab-to-target + * facts. + * 3. `attachToTarget()` attaches the debuggee for the requested target and + * enables flattened auto-attach. + * 4. chrome.debugger events update debugger-local session maps and dispatch to + * typed `on(event, listener)` subscriptions. + */ +class ChromeDebuggerUpstreamTransport extends UpstreamTransport { + declare config: ChromeDebuggerUpstreamTransportConfig; + // JSON(debuggee) values attached in this service worker. Updated by + // attachDebuggee/onDetach; read before attach to avoid duplicate native + // chrome.debugger.attach calls. + private readonly attached_debuggees = new Set(); + + // Native Target.SessionID -> TargetID from debugger Target.attachedToTarget + // events. Updated by installEventListener; read when sending a command that + // already carries a child session id. + private readonly targetId_from_sessionId = new Map(); + + // chrome.tabs tab id -> CDP TargetID. Refreshed by getTargets and + // Target.attachedToTarget events; read by resolveTargetId/createTarget. + private readonly targetId_from_tabId = new Map(); + + // TargetID -> chrome.debugger.Debuggee selected for that target. Updated by + // attachToTarget; read by send so subsequent commands use the same native + // debuggee shape. + private readonly debuggee_from_targetId = new Map(); + + // Installed chrome.debugger event listener for this service-worker lifetime. + // Non-null means chrome.debugger events are being normalized into upstream + // CDP events. + private debugger_onEvent_listener: + | ((source: chrome.debugger.Debuggee, method: string, params?: object) => void) + | null = null; + + // Installed chrome.debugger detach listener for this service-worker lifetime. + // Non-null means native detach events are clearing attached-debuggee state. + private debugger_onDetach_listener: ((source: chrome.debugger.Debuggee, reason?: string) => void) | null = null; + + constructor(config: z.input = {}) { + super(); + this.config = ChromeDebuggerUpstreamTransportConfigSchema.parse({ ...config, upstream_mode: "chromedebugger" }); + } + + override update(config: z.input = {}) { + this.config = ChromeDebuggerUpstreamTransportConfigSchema.parse({ + ...this.config, + ...config, + upstream_mode: "chromedebugger", + }); + return this; + } + + /** Install chrome.debugger listeners for this service-worker lifetime. */ + override async connect() { + this.installEventListener(); + } + + /** Return current browser targets through chrome.debugger target discovery. */ + async getTargets() { + const chrome_api = globalThis.chrome; + this.installEventListener(); + if (!chrome_api?.debugger?.getTargets) throw new Error("chrome.debugger is unavailable."); + const targetInfos = (await chrome_api.debugger.getTargets()).map((target) => { + if (typeof target.tabId === "number") { + this.targetId_from_tabId.set(target.tabId, target.id); + this.debuggee_from_targetId.set(target.id, { tabId: target.tabId }); + } + return { + targetId: target.id, + type: target.type, + title: target.title, + url: target.url, + attached: target.attached, + canAccessOpener: false, + }; + }); + return Target.GetTargetsResult.parse({ targetInfos }).targetInfos; + } + + /** Resolve a target id from target id, debuggee target id, or chrome tab id. */ + async resolveTargetId(params: CdpDebuggeeCommandParams) { + if (typeof params.targetId === "string" && params.targetId.length > 0) return params.targetId; + if (params.debuggee?.targetId) return params.debuggee.targetId; + if (typeof params.tabId === "number") { + await this.getTargets(); + return this.targetId_from_tabId.get(params.tabId) ?? null; + } + return null; + } + + /** Create a new foreground tab and return the corresponding CDP target id. */ + async createTarget(url: string) { + const tab = await globalThis.chrome.tabs.create({ url, active: true }); + if (!tab.id) throw new Error(`chromedebugger could not create a tab for ${url}.`); + await this.getTargets(); + const targetId = this.targetId_from_tabId.get(tab.id); + if (!targetId) throw new Error(`chromedebugger could not resolve target for created tab ${tab.id}.`); + return targetId; + } + + /** Attach chrome.debugger to a target; debugger transport has no native flattened session id for the parent. */ + async attachToTarget(targetId: cdp.types.ts.Target.TargetID) { + const debuggee = await this.debuggeeForTarget(targetId); + await this.attachDebuggee(debuggee); + this.debuggee_from_targetId.set(targetId, debuggee); + return null; + } + + /** Forget a debugger child-session mapping after detach. */ + async detachFromTarget(sessionId: cdp.types.ts.Target.SessionID) { + this.targetId_from_sessionId.delete(sessionId); + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + throw new Error("chromedebugger does not support raw CDP command messages."); + } + if (typeof command === "string") { + throw new Error("chromedebugger raw string sends must go through ModCDPClient.router."); + } + let route: TargetRoute | undefined; + if (typeof route_or_sessionId === "string") { + const targetId = this.targetId_from_sessionId.get(route_or_sessionId); + if (!targetId) throw new Error(`No target is recorded for sessionId=${route_or_sessionId}.`); + route = { targetId, sessionId: route_or_sessionId }; + } else { + route = route_or_sessionId && typeof route_or_sessionId === "object" ? route_or_sessionId : undefined; + } + return this.sendCommand(command, params as z.input, route); + } + + private async sendCommand< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params: z.input, + route?: TargetRoute, + ): Promise> { + if (command.id === Target.GetTargetsCommand.id) + return command.result.parse({ targetInfos: await this.getTargets() }); + if (!route) { + const debuggee = await this.defaultDebuggee(); + await this.attachDebuggee(debuggee); + return command.result.parse(await this.sendToDebugger(debuggee, command.id, command.params.parse(params))); + } + const routedTargetId = route.sessionId + ? (this.targetId_from_sessionId.get(route.sessionId) ?? route.targetId) + : route.targetId; + const debuggee = this.debuggee_from_targetId.get(routedTargetId) ?? (await this.debuggeeForTarget(routedTargetId)); + await this.attachDebuggee(debuggee); + return command.result.parse(await this.sendToDebugger(debuggee, command.id, command.params.parse(params))); + } + + private async debuggeeForTarget(targetId: cdp.types.ts.Target.TargetID) { + const targets = await this.getTargets(); + const target = targets.find((candidate) => candidate.targetId === targetId); + if (!target) throw new Error(`chromedebugger could not resolve targetId=${targetId}.`); + return this.debuggee_from_targetId.get(targetId) ?? { targetId }; + } + + private async defaultDebuggee() { + const targetId = + (await this.resolveTargetId({})) ?? (await this.getTargets()).find((target) => target.type === "page")?.targetId; + if (!targetId) return await this.debuggeeForTarget(await this.createTarget("about:blank#modcdp")); + return await this.debuggeeForTarget(targetId); + } + + private async attachDebuggee(debuggee: chrome.debugger.Debuggee) { + const key = JSON.stringify(debuggee); + if (this.attached_debuggees.has(key)) return; + const chrome_api = globalThis.chrome; + await new Promise((resolve, reject) => + chrome_api.debugger.attach(debuggee, "1.3", () => { + const error = chrome_api.runtime.lastError; + if (!error || error.message?.includes("Another debugger is already attached")) resolve(); + else reject(new Error(error.message)); + }), + ); + await new Promise((resolve, reject) => + chrome_api.debugger.sendCommand( + debuggee, + Target.SetAutoAttachCommand.id, + Target.SetAutoAttachCommand.params.parse(target_auto_attach_params), + () => { + const error = chrome_api.runtime.lastError; + if (error) reject(new Error(error.message)); + else resolve(); + }, + ), + ); + this.attached_debuggees.add(key); + } + + private installEventListener() { + const chrome_api = globalThis.chrome; + if (this.debugger_onEvent_listener || !chrome_api?.debugger?.onEvent?.addListener) return; + this.debugger_onEvent_listener = (source, method, params) => { + const payload = (params ?? {}) as ProtocolPayload; + const sourceTargetId = + source.targetId ?? + (typeof source.tabId === "number" ? (this.targetId_from_tabId.get(source.tabId) ?? null) : null); + const cdpSessionId = (source as chrome.debugger.Debuggee & { sessionId?: string }).sessionId ?? null; + if (method === Target.AttachedToTargetEvent.id) { + const attached = Target.AttachedToTargetEvent.parse(payload); + if (typeof source.tabId === "number") this.targetId_from_tabId.set(source.tabId, attached.targetInfo.targetId); + this.targetId_from_sessionId.set(attached.sessionId, attached.targetInfo.targetId); + } else if (method === Target.DetachedFromTargetEvent.id) { + const detached = Target.DetachedFromTargetEvent.parse(payload); + this.targetId_from_sessionId.delete(detached.sessionId); + } + this.emitUpstreamEvent(method, payload, sourceTargetId, cdpSessionId); + }; + chrome_api.debugger.onEvent.addListener(this.debugger_onEvent_listener); + this.debugger_onDetach_listener = (source) => { + this.attached_debuggees.delete(JSON.stringify(this.compactDebuggee(source))); + }; + chrome_api.debugger.onDetach?.addListener?.(this.debugger_onDetach_listener); + } + + private compactDebuggee(input: { + [Key in keyof chrome.debugger.Debuggee]?: chrome.debugger.Debuggee[Key] | null; + }): chrome.debugger.Debuggee { + return { + ...(typeof input.tabId === "number" ? { tabId: input.tabId } : {}), + ...(typeof input.targetId === "string" ? { targetId: input.targetId } : {}), + ...(typeof input.extensionId === "string" ? { extensionId: input.extensionId } : {}), + }; + } + + private sendToDebugger( + debuggee: chrome.debugger.Debuggee, + method: string, + params: Record = {}, + ): Promise { + const chrome_api = globalThis.chrome; + return new Promise((resolve, reject) => + chrome_api.debugger.sendCommand(debuggee, method, params, (result) => { + const error = chrome_api.runtime.lastError; + if (error) reject(new Error(error.message)); + else resolve(result as ProtocolResult); + }), + ); + } + + override toJSON() { + const json = super.toJSON(); + return { + ...json, + state: { + ...json.state, + attached_debuggees: this.attached_debuggees.size, + targetId_from_sessionId: this.targetId_from_sessionId.size, + targetId_from_tabId: this.targetId_from_tabId.size, + debuggee_from_targetId: this.debuggee_from_targetId.size, + debugger_onEvent_listener: this.debugger_onEvent_listener != null, + debugger_onDetach_listener: this.debugger_onDetach_listener != null, + }, + }; + } +} + +export { ChromeDebuggerUpstreamTransport, ChromeDebuggerUpstreamTransportConfigSchema }; +export type { ChromeDebuggerUpstreamTransportConfig }; diff --git a/js/src/transport/DownstreamTransport.ts b/js/src/transport/DownstreamTransport.ts new file mode 100644 index 00000000..1b8d92e3 --- /dev/null +++ b/js/src/transport/DownstreamTransport.ts @@ -0,0 +1,95 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. +import { + CdpResponseMessageSchema, + type CdpCommandMessage, + type CdpEventMessage, + type CdpResponseMessage, + type ProtocolPayload, +} from "../types/modcdp.js"; +import { modCDPToJSON } from "../types/toJSON.js"; + +type DownstreamTransportName = "reversews" | "nativemessaging" | "nats"; + +type DownstreamTransportStatus = { + connected: boolean; + last_error?: string | null; + attempts?: number; + config?: ProtocolPayload; +}; + +type DownstreamRequestHandler = ( + message: CdpCommandMessage, +) => CdpResponseMessage | null | Promise; + +/** + * Base contract for SDK/client-facing server transports. + * + * From ModCDPServer's point of view, downstream means the connection from an SDK + * client into the extension service worker. Concrete transports own their + * native connection lifecycle and request origin routing. ModCDPServer registers + * request handlers, sends explicit responses for advanced asynchronous flows, + * and broadcasts CDP event messages through this generic surface. + * + * Returning `{ id, result: {} }` from an onRequest handler sends a normal empty + * CDP success response. Returning `null` sends no response; the caller is then + * responsible for calling `sendResponse` later with the original request. + */ +abstract class DownstreamTransport { + /** Stable implementation name used as the server status-map key. */ + abstract readonly name: DownstreamTransportName; + + // Request handlers installed by ModCDPServer. Updated by onRequest and read + // by transport message handlers when a downstream client sends a command. + private readonly request_handlers = new Set(); + + /** Start this transport's built-in client polling/listening path. */ + abstract startPollingForClients(): ProtocolPayload | null; + + /** Stop accepting or reconnecting downstream clients for this transport. */ + abstract stop(reason?: string): ProtocolPayload | null; + + /** Send one CDP response to the downstream client that originated request. */ + abstract sendResponse(request: CdpCommandMessage, response: CdpResponseMessage): boolean; + + /** Send one CDP event to connected downstream clients and return send count. */ + abstract sendEvent(message: CdpEventMessage): number; + + /** Return protocol-agnostic status for UI/debug surfaces. */ + abstract status(): DownstreamTransportStatus; + + /** Register a request handler for CDP command messages from downstream clients. */ + onRequest(handler: DownstreamRequestHandler): { remove: () => boolean } { + this.request_handlers.add(handler); + return { remove: () => this.request_handlers.delete(handler) }; + } + + /** + * Run registered handlers for one downstream request. + * + * Concrete transports call this after parsing a native CDP command message. + * The first non-null native CDP response is sent back to the request origin. + */ + protected async handleRequest(message: CdpCommandMessage): Promise { + for (const handler of this.request_handlers) { + const response = await handler(message); + if (response === null) continue; + this.sendResponse(message, CdpResponseMessageSchema.parse(response)); + return; + } + } + + toJSON() { + const { config = {}, ...state } = this.status(); + return modCDPToJSON(this, { + config, + state: { + ...state, + request_handlers: this.request_handlers.size, + }, + }); + } +} + +export { DownstreamTransport }; +export type { DownstreamTransportName, DownstreamTransportStatus, DownstreamRequestHandler }; diff --git a/js/src/transport/DownstreamTransportSet.ts b/js/src/transport/DownstreamTransportSet.ts new file mode 100644 index 00000000..50ff8fe6 --- /dev/null +++ b/js/src/transport/DownstreamTransportSet.ts @@ -0,0 +1,183 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: only runs in browser. +import type { ModCDPClient } from "../client/ModCDPClient.js"; +import type { z } from "zod"; +import { events as nativeEventSchemas } from "../types/generated/zod.js"; +import { + type CdpCommandMessage, + type CdpEventMessage, + type CdpResponseMessage, + type ModCDPDownstreamConfig, + ModCDPDownstreamConfigSchema, + type ProtocolPayload, +} from "../types/modcdp.js"; +import { modCDPToJSON } from "../types/toJSON.js"; +import { CUSTOM_EVENT_BINDING_NAME, UPSTREAM_EVENT_BINDING_NAME } from "../translate/translate.js"; +import { + type DownstreamRequestHandler, + type DownstreamTransportName, + type DownstreamTransportStatus, + DownstreamTransport, +} from "./DownstreamTransport.js"; + +/** + * Owns the SDK/client-facing transports installed in the extension service worker. + * + * From ModCDPServer's point of view, downstream means client connections into + * the service worker. This set owns fan-out, request-handler + * registration, status aggregation, and lifecycle calls across the configured + * downstream transports. It does not route commands to browser targets, choose + * target sessions, evaluate custom commands, or know how any individual + * transport talks to its peers. + */ +class DownstreamTransportSet { + config: ModCDPDownstreamConfig; + + // Transport name -> concrete downstream transport. Written during service + // worker setup; read for fan-out, status, and lifecycle transitions. + private readonly transports = new Map(); + private downstream_client_lease: ReturnType | null = null; + + constructor(config: z.input = {}) { + this.config = ModCDPDownstreamConfigSchema.parse({ closeBrowser: () => {}, ...config }); + } + + update(config: z.input = {}) { + this.config = ModCDPDownstreamConfigSchema.parse({ ...this.config, ...config }); + return this; + } + + clearClientLease() { + const lease = this.downstream_client_lease; + if (!lease) return false; + clearTimeout(lease); + this.downstream_client_lease = null; + return true; + } + + hasClientLease() { + return this.downstream_client_lease != null; + } + + touchClientLease() { + const timeout_ms = this.config.downstream_client_timeout_ms; + if (!(timeout_ms > 0)) return; + this.clearClientLease(); + this.downstream_client_lease = setTimeout(() => { + const expired = this.clearClientLease(); + if (!expired) return; + if (this.config.downstream_close_browser_on_disconnect !== true) return; + void this.config.closeBrowser(); + }, timeout_ms); + } + + /** Add one downstream transport implementation to the set. */ + add(transport: DownstreamTransport) { + this.transports.set(transport.name, transport); + } + + /** Register one request handler on every downstream transport. */ + onRequest(handler: DownstreamRequestHandler) { + const subscriptions = [...this.transports.values()].map((transport) => + transport.onRequest((message) => { + this.touchClientLease(); + return handler(message); + }), + ); + return { remove: () => subscriptions.every((subscription) => subscription.remove()) }; + } + + /** Start every downstream transport's built-in client polling/listening path. */ + startPollingForClients() { + const results: Partial> = {}; + for (const [name, transport] of this.transports) { + const result = transport.startPollingForClients(); + if (result != null) results[name] = result; + } + return results; + } + + /** Stop every downstream transport. */ + stop(reason = "stopped") { + const results: Partial> = {}; + for (const [name, transport] of this.transports) { + const result = transport.stop(reason); + if (result != null) results[name] = result; + } + return results; + } + + /** Return status for every downstream transport keyed by transport name. */ + status() { + const status: Partial> = {}; + for (const [name, transport] of this.transports) status[name] = transport.status(); + return status; + } + + /** Send one response to the downstream transport that owns the request. */ + sendResponse(request: CdpCommandMessage, response: CdpResponseMessage) { + return [...this.transports.values()].some((transport) => transport.sendResponse(request, response)); + } + + /** Broadcast one CDP event to connected downstream clients. */ + sendEvent(message: CdpEventMessage) { + const binding_name = nativeEventSchemas[message.method] ? UPSTREAM_EVENT_BINDING_NAME : CUSTOM_EVENT_BINDING_NAME; + const binding = Reflect.get(globalThis, binding_name); + let sent_count = 0; + if (typeof binding === "function") { + binding( + JSON.stringify({ + event: message.method, + data: message.params ?? {}, + cdpSessionId: message.sessionId ?? null, + }), + ); + sent_count += 1; + } + return [...this.transports.values()].reduce((count, transport) => count + transport.sendEvent(message), sent_count); + } + + /** Mirror all CDP-shaped ModCDPClient events into downstream transports. */ + mirrorEventsFrom( + client: ModCDPClient, + { + transformEvent, + }: { + transformEvent?: (message: CdpEventMessage) => CdpEventMessage | null | Promise; + } = {}, + ) { + const listener = (event_name: unknown, payload: unknown, cdpSessionId: unknown) => { + if (typeof event_name !== "string" || !event_name.includes(".")) return; + const message: CdpEventMessage = { + method: event_name, + params: (payload ?? {}) as CdpEventMessage["params"], + }; + if (typeof cdpSessionId === "string") message.sessionId = cdpSessionId; + void (async () => { + const transformed_message = transformEvent ? await transformEvent(message) : message; + if (transformed_message != null) this.sendEvent(transformed_message); + })(); + }; + client.on("*", listener); + return { remove: () => client.off("*", listener) }; + } + + /** True when at least one downstream transport currently has a connected client. */ + hasConnectedClient() { + return [...this.transports.values()].some((transport) => transport.status().connected); + } + + toJSON() { + return modCDPToJSON(this, { + config: { ...this.config, closeBrowser: undefined }, + state: { + downstream_client_lease: this.downstream_client_lease != null, + transports: this.transports.size, + connected: this.hasConnectedClient(), + }, + children: Object.fromEntries(this.transports), + }); + } +} + +export { DownstreamTransportSet }; diff --git a/js/src/transport/NATSDownstreamTransport.ts b/js/src/transport/NATSDownstreamTransport.ts new file mode 100644 index 00000000..c689a658 --- /dev/null +++ b/js/src/transport/NATSDownstreamTransport.ts @@ -0,0 +1,286 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import { + CdpCommandMessageSchema, + type CdpCommandMessage, + type CdpEventMessage, + type CdpResponseMessage, +} from "../types/modcdp.js"; +import { DownstreamTransport } from "./DownstreamTransport.js"; +import { z } from "zod"; + +const DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; +const DEFAULT_NATS_BRIDGE_URL = "ws://127.0.0.1:4223"; +const DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX = "modcdp.default"; + +const NATSDownstreamTransportConfigSchema = z + .object({ + upstream_nats_url: z.string().default(DEFAULT_NATS_BRIDGE_URL), + upstream_nats_subject_prefix: z + .string() + .refine((value) => value.trim().length > 0 && !/[\s*>]/.test(value), "Invalid NATS subject prefix") + .default(DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX), + reconnect_interval_ms: z.number().positive().default(DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS), + }) + .strict(); + +/** + * Owns the NATS-over-WebSocket downstream connection from the extension service + * worker to a ModCDP client/proxy. + * + * This class owns only NATS-specific lifecycle: WebSocket connection, reconnect + * scheduling, NATS CONNECT/SUB/PUB framing, protocol buffering, hello messages, + * command-message decoding, CDP response publishing, and event-message + * publishing. It does not own ModCDP command registration, routing, middleware, + * upstream target/session state, native messaging, or reversews handling. + * + * Lifecycle: + * 1. `start()` records the endpoint/subject/reconnect interval and opens one + * WebSocket if none is already open or connecting. + * 2. `open` sends NATS CONNECT/PING/SUB frames and publishes a browser hello. + * 3. `message` appends decoded bytes to the protocol buffer and consumes + * complete NATS frames. + * 4. `MSG` payloads are decoded as ModCDP command envelopes and delegated + * through the injected `handleCommand` callback. + * 5. `error`/`close` drops the socket and schedules reconnect while an endpoint + * is still configured. + */ +class NATSDownstreamTransport extends DownstreamTransport { + readonly name = "nats" as const; + config: z.infer = NATSDownstreamTransportConfigSchema.parse({}); + + // True after start configures the NATS endpoint. Cleared by stop and read by + // reconnect scheduling/status. + private started = false; + + // Active NATS WebSocket. Set by connect, cleared by error/close, read by + // start/write/emit. + private socket: WebSocket | null = null; + + // Pending reconnect timer. Set by scheduleReconnect and cleared when it fires. + private reconnect_timer: ReturnType | null = null; + + // Unconsumed NATS protocol bytes decoded as text. Appended by readWebSocketData + // and replaced by consumeProtocol. + private buffer = ""; + + // Request object -> transport-native NATS reply subject. Written by + // handlePayload and read by sendResponse so responses route to the requesting + // SDK client instead of being broadcast on the event subject. + private readonly reply_subject_from_request = new WeakMap(); + + /** True when the NATS WebSocket is open and can publish CDP event messages. */ + get connected() { + return this.socket?.readyState === WebSocket.OPEN; + } + + /** Configure and start the NATS downstream connection. */ + start(endpoint?: string, config: z.input = {}) { + this.config = NATSDownstreamTransportConfigSchema.parse({ + ...config, + upstream_nats_url: endpoint, + }); + this.started = true; + void this.connect(this.config.upstream_nats_url).catch(() => { + this.scheduleReconnect(); + }); + return { + upstream_nats_url: this.config.upstream_nats_url, + upstream_nats_subject_prefix: this.config.upstream_nats_subject_prefix, + reconnect_interval_ms: this.config.reconnect_interval_ms, + connecting: true, + }; + } + + /** Start polling for NATS clients using the shipped extension default. */ + startPollingForClients() { + return this.start(); + } + + /** Stop reconnecting and close the active NATS socket. */ + stop(reason = "stopped") { + const upstream_nats_url = this.started ? this.config.upstream_nats_url : null; + const upstream_nats_subject_prefix = this.config.upstream_nats_subject_prefix; + this.started = false; + if (this.reconnect_timer) { + clearTimeout(this.reconnect_timer); + this.reconnect_timer = null; + } + const socket = this.socket; + this.socket = null; + if (socket?.readyState === WebSocket.OPEN || socket?.readyState === WebSocket.CONNECTING) { + socket.close(1000, reason); + } + return { upstream_nats_url, upstream_nats_subject_prefix, stopped: true, reason }; + } + + /** Publish one CDP response to the transport-native NATS reply subject. */ + sendResponse(request: CdpCommandMessage, response: CdpResponseMessage) { + if (this.socket?.readyState !== WebSocket.OPEN) return false; + const reply_subject = this.reply_subject_from_request.get(request); + if (!reply_subject) throw new Error(`NATS downstream request ${request.id} did not include a reply_subject.`); + this.publish(reply_subject, { + type: "modcdp.nats.message", + message: response, + }); + this.reply_subject_from_request.delete(request); + return true; + } + + /** Publish one CDP event message to the NATS browser-to-client subject. */ + sendEvent(message: CdpEventMessage) { + if (this.socket?.readyState !== WebSocket.OPEN) return 0; + this.publish(`${this.config.upstream_nats_subject_prefix}.browser_to_client`, { + type: "modcdp.nats.message", + message, + }); + return 1; + } + + /** Return generic status without exposing NATS lifecycle to ModCDPServer. */ + status() { + return { + connected: this.connected, + config: this.started ? this.config : {}, + }; + } + + private scheduleReconnect() { + if (!this.started) return; + if (this.reconnect_timer) return; + this.reconnect_timer = setTimeout(() => { + this.reconnect_timer = null; + if (!this.started) return; + void this.connect(this.config.upstream_nats_url).catch(() => {}); + }, this.config.reconnect_interval_ms); + } + + private async connect(endpoint: string) { + if (!/^wss?:\/\//i.test(endpoint)) { + throw new Error( + `NATS downstream endpoint must be a ws:// or wss:// URL for extension transport, got ${endpoint}.`, + ); + } + if (this.socket?.readyState === WebSocket.OPEN || this.socket?.readyState === WebSocket.CONNECTING) { + return { + upstream_nats_url: endpoint, + upstream_nats_subject_prefix: this.config.upstream_nats_subject_prefix, + connected: this.socket.readyState === WebSocket.OPEN, + }; + } + const ws = new WebSocket(endpoint); + this.socket = ws; + this.buffer = ""; + ws.addEventListener("open", () => { + this.write(`CONNECT ${JSON.stringify(this.connectConfig())}\r\nPING\r\n`); + this.write(`SUB ${this.config.upstream_nats_subject_prefix}.client_to_browser 1\r\n`); + this.publish(`${this.config.upstream_nats_subject_prefix}.browser_to_client`, { + type: "modcdp.nats.hello", + role: "extension-service-worker", + version: 1, + extension_id: globalThis.chrome?.runtime?.id ?? null, + }); + }); + ws.addEventListener("message", (event) => { + void this.readWebSocketData(event.data); + }); + ws.addEventListener("error", () => { + if (this.socket === ws) this.socket = null; + this.scheduleReconnect(); + }); + ws.addEventListener("close", () => { + if (this.socket === ws) this.socket = null; + this.scheduleReconnect(); + }); + return { + upstream_nats_url: endpoint, + upstream_nats_subject_prefix: this.config.upstream_nats_subject_prefix, + connected: false, + }; + } + + private write(data: string) { + if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(data); + } + + private publish(subject: string, message: unknown) { + const body = JSON.stringify(message); + this.write(`PUB ${subject} ${new TextEncoder().encode(body).byteLength}\r\n${body}\r\n`); + } + + private async readWebSocketData(data: unknown) { + if (typeof data === "string") this.buffer += data; + else if (data instanceof ArrayBuffer) this.buffer += new TextDecoder().decode(data); + else if (ArrayBuffer.isView(data)) this.buffer += new TextDecoder().decode(data); + else if (typeof Blob !== "undefined" && data instanceof Blob) this.buffer += await data.text(); + else return; + this.buffer = this.consumeProtocol(this.buffer); + } + + private consumeProtocol(buffer: string) { + for (;;) { + const line_end = buffer.indexOf("\r\n"); + if (line_end < 0) return buffer; + const line = buffer.slice(0, line_end); + const upper = line.toUpperCase(); + if (upper.startsWith("MSG ")) { + const parts = line.split(/\s+/); + const size = Number(parts[parts.length - 1]); + const payload_start = line_end + 2; + const payload_end = payload_start + size; + if (!Number.isInteger(size) || buffer.length < payload_end + 2) return buffer; + const payload = buffer.slice(payload_start, payload_end); + buffer = buffer.slice(payload_end + 2); + void this.handlePayload(payload); + continue; + } + buffer = buffer.slice(line_end + 2); + if (upper === "PING") this.write("PONG\r\n"); + } + } + + private async handlePayload(payload: string) { + let parsed: unknown; + try { + parsed = JSON.parse(payload); + } catch { + return; + } + const record = + parsed && typeof parsed === "object" + ? (parsed as { type?: unknown; message?: unknown; reply_subject?: unknown }) + : null; + if (record?.type === "modcdp.nats.hello") { + this.publish(`${this.config.upstream_nats_subject_prefix}.browser_to_client`, { + type: "modcdp.nats.hello", + role: "extension-service-worker", + version: 1, + extension_id: globalThis.chrome?.runtime?.id ?? null, + }); + return; + } + const message = CdpCommandMessageSchema.parse(record?.type === "modcdp.nats.message" ? record.message : parsed); + if (typeof record?.reply_subject !== "string" || record.reply_subject.length === 0) { + throw new Error(`NATS downstream command ${message.id} is missing reply_subject.`); + } + this.reply_subject_from_request.set(message, record.reply_subject); + await this.handleRequest(message); + } + + private connectConfig() { + return { + verbose: false, + pedantic: false, + lang: "modcdp-extension", + version: "1", + protocol: 1, + }; + } +} + +export { + DEFAULT_NATS_BRIDGE_RECONNECT_INTERVAL_MS, + DEFAULT_NATS_BRIDGE_URL, + DEFAULT_NATS_BRIDGE_SUBJECT_PREFIX, + NATSDownstreamTransport, +}; diff --git a/js/src/transport/NATSUpstreamTransport.ts b/js/src/transport/NATSUpstreamTransport.ts new file mode 100644 index 00000000..b30fe74d --- /dev/null +++ b/js/src/transport/NATSUpstreamTransport.ts @@ -0,0 +1,346 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import net from "node:net"; +import tls from "node:tls"; +import { z } from "zod"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import type { CdpCommandMessage, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS } from "../types/modcdp.js"; +import { UpstreamTransport, type TargetRoute } from "./UpstreamTransport.js"; + +const DEFAULT_UPSTREAM_NATS_URL = "ws://127.0.0.1:4223"; +const DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX = "modcdp.default"; +const DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS = 10_000; + +const NATSUpstreamTransportConfigSchema = z.object({ + upstream_mode: z.literal("nats").default("nats"), + upstream_nats_url: z.string().default(DEFAULT_UPSTREAM_NATS_URL), + upstream_nats_subject_prefix: z + .string() + .refine((value) => value.trim().length > 0 && !/[\s*>]/.test(value), "Invalid NATS subject prefix") + .default(DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX), + upstream_nats_role: z.enum(["client", "browser"]).default("client"), + upstream_nats_wait_timeout_ms: z.number().positive().default(DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), +}); +type NATSUpstreamTransportConfig = z.infer; + +type NatsTcpSocket = { + write(data: string): void; + destroy(): void; + on(event: "data", listener: (chunk: Buffer | string) => void): void; + on(event: "close" | "error", listener: () => void): void; + once(event: "connect", listener: () => void): void; + once(event: "error", listener: (error: Error) => void): void; +}; +type NatsSocket = WebSocket | NatsTcpSocket; + +class NATSUpstreamTransport extends UpstreamTransport { + declare config: NATSUpstreamTransportConfig; + override peer_kind = "modcdp_server" as const; + private socket: NatsSocket | null = null; + private tcp_buffer = Buffer.alloc(0); + private ws_buffer = ""; + private next_sid = 1; + private client_reply_subject: string; + private peer_seen = false; + private peer_waiters = new Set<{ + resolve: () => void; + reject: (error: Error) => void; + timeout: ReturnType; + }>(); + + constructor(config: z.input = {}) { + super(); + this.config = NATSUpstreamTransportConfigSchema.parse({ ...config, upstream_mode: "nats" }); + this.client_reply_subject = `${this.config.upstream_nats_subject_prefix}.client.${globalThis.crypto.randomUUID().replaceAll("-", "")}`; + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + if (!this.socket) throw new Error("NATS transport is not connected."); + this.publish(this.outgoingSubject(), { + type: "modcdp.nats.message", + ...(this.config.upstream_nats_role === "client" ? { reply_subject: this.client_reply_subject } : {}), + message: command, + }); + return; + } + if (typeof command === "string") { + return super.send( + command, + params as ProtocolPayload, + typeof route_or_sessionId === "string" ? route_or_sessionId : null, + config, + ); + } + return super.send(command, params as z.input, route_or_sessionId); + } + + override update(config: Record = {}) { + const previous_subject_prefix = this.config.upstream_nats_subject_prefix; + this.config = NATSUpstreamTransportConfigSchema.parse({ ...this.config, ...config, upstream_mode: "nats" }); + if (this.config.upstream_nats_subject_prefix !== previous_subject_prefix) { + this.client_reply_subject = `${this.config.upstream_nats_subject_prefix}.client.${globalThis.crypto.randomUUID().replaceAll("-", "")}`; + } + return this; + } + + async connect() { + if (this.socket) return; + try { + const parsed = new URL(this.config.upstream_nats_url); + if (parsed.protocol === "ws:" || parsed.protocol === "wss:") await this.connectWebSocket(parsed); + else if (parsed.protocol === "nats:" || parsed.protocol === "tls:") await this.connectTcp(parsed); + else + throw new Error( + `upstream_mode=nats requires ws://, wss://, nats://, or tls:// URL, got ${this.config.upstream_nats_url}.`, + ); + this.subscribe(); + this.publish(this.outgoingSubject(), { + type: "modcdp.nats.hello", + role: this.config.upstream_nats_role, + version: 1, + }); + } catch (error) { + this.socket = null; + throw error; + } + } + + async waitForPeer() { + if (this.peer_seen) return; + await new Promise((resolve, reject) => { + let waiter!: { + resolve: () => void; + reject: (error: Error) => void; + timeout: ReturnType; + }; + const wait_timeout_ms = this.config.upstream_nats_wait_timeout_ms; + const timeout = setTimeout(() => { + this.peer_waiters.delete(waiter); + reject(new Error(`Timed out waiting ${wait_timeout_ms}ms for NATS ModCDP peer.`)); + }, wait_timeout_ms); + waiter = { resolve, reject, timeout }; + this.peer_waiters.add(waiter); + }); + } + + async close() { + try { + if (this.socket instanceof WebSocket) this.socket.close(); + else this.socket?.destroy(); + } catch {} + this.socket = null; + this.peer_seen = false; + for (const waiter of this.peer_waiters) { + clearTimeout(waiter.timeout); + waiter.reject( + new Error(`NATS transport for ${this.config.upstream_nats_subject_prefix} closed before a peer connected.`), + ); + } + this.peer_waiters.clear(); + } + + private async connectWebSocket(url: URL) { + const ws = new WebSocket(url); + this.socket = ws; + ws.addEventListener("message", (event) => { + void this.readWebSocket(event.data); + }); + ws.addEventListener("close", () => { + if (this.socket === ws) this.socket = null; + this.emitClose(new Error("NATS websocket closed")); + }); + ws.addEventListener("error", () => { + if (this.socket === ws) this.socket = null; + this.emitClose(new Error("NATS websocket error")); + }); + await new Promise((resolve, reject) => { + const cleanup = () => { + ws.removeEventListener("open", onOpen); + ws.removeEventListener("error", onError); + }; + const onOpen = () => { + cleanup(); + this.writeProtocol(`CONNECT ${JSON.stringify(connectConfig())}\r\nPING\r\n`); + resolve(); + }; + const onError = () => { + cleanup(); + reject(new Error(`NATS websocket connection failed for ${url.toString()}`)); + }; + ws.addEventListener("open", onOpen); + ws.addEventListener("error", onError); + }); + } + + private async connectTcp(url: URL) { + const port = Number(url.port || (url.protocol === "tls:" ? 4222 : 4222)); + const host = url.hostname || "127.0.0.1"; + const socket = ( + url.protocol === "tls:" ? tls.connect({ host, port }) : net.connect({ host, port }) + ) as NatsTcpSocket; + this.socket = socket; + socket.on("data", (chunk) => this.readTcp(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); + socket.on("close", () => { + if (this.socket === socket) this.socket = null; + this.emitClose(new Error("NATS socket closed")); + }); + socket.on("error", () => { + if (this.socket === socket) this.socket = null; + this.emitClose(new Error("NATS socket error")); + }); + await new Promise((resolve, reject) => { + socket.once("connect", () => { + this.writeProtocol(`CONNECT ${JSON.stringify(connectConfig())}\r\nPING\r\n`); + resolve(); + }); + socket.once("error", reject); + }); + } + + private subscribe() { + this.writeProtocol(`SUB ${this.incomingSubject()} ${this.next_sid++}\r\n`); + if (this.config.upstream_nats_role === "client") { + this.writeProtocol(`SUB ${this.client_reply_subject} ${this.next_sid++}\r\n`); + } + } + + private publish(subject: string, message: unknown) { + const body = JSON.stringify(message); + this.writeProtocol(`PUB ${subject} ${Buffer.byteLength(body)}\r\n${body}\r\n`); + } + + private writeProtocol(data: string) { + const socket = this.socket; + if (!socket) throw new Error("NATS transport is not connected."); + if (socket instanceof WebSocket) socket.send(data); + else socket.write(data); + } + + private incomingSubject() { + return `${this.config.upstream_nats_subject_prefix}.${this.config.upstream_nats_role === "client" ? "browser_to_client" : "client_to_browser"}`; + } + + private outgoingSubject() { + return `${this.config.upstream_nats_subject_prefix}.${this.config.upstream_nats_role === "client" ? "client_to_browser" : "browser_to_client"}`; + } + + private async readWebSocket(data: unknown) { + if (data instanceof ArrayBuffer) this.ws_buffer += Buffer.from(data).toString("utf8"); + else if (ArrayBuffer.isView(data)) + this.ws_buffer += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8"); + else if (typeof Blob !== "undefined" && data instanceof Blob) this.ws_buffer += await data.text(); + else this.ws_buffer += String(data); + this.ws_buffer = this.consumeProtocol(this.ws_buffer); + } + + private readTcp(chunk: Buffer) { + this.tcp_buffer = Buffer.concat([this.tcp_buffer, chunk]); + const remaining = this.consumeProtocol(this.tcp_buffer.toString("utf8")); + this.tcp_buffer = Buffer.from(remaining, "utf8"); + } + + private consumeProtocol(buffer: string) { + for (;;) { + const lineEnd = buffer.indexOf("\r\n"); + if (lineEnd < 0) return buffer; + const line = buffer.slice(0, lineEnd); + const upper = line.toUpperCase(); + if (upper.startsWith("MSG ")) { + const parts = line.split(/\s+/); + const size = Number(parts[parts.length - 1]); + const payloadStart = lineEnd + 2; + const payloadEnd = payloadStart + size; + if (!Number.isInteger(size) || buffer.length < payloadEnd + 2) return buffer; + const payload = buffer.slice(payloadStart, payloadEnd); + buffer = buffer.slice(payloadEnd + 2); + this.handlePayload(payload); + continue; + } + buffer = buffer.slice(lineEnd + 2); + if (upper === "PING") this.writeProtocol("PONG\r\n"); + else if (upper.startsWith("-ERR")) this.emitClose(new Error(`NATS error: ${line}`)); + } + } + + private handlePayload(payload: string) { + let parsed: unknown; + try { + parsed = JSON.parse(payload); + } catch { + return; + } + const record = parsed && typeof parsed === "object" ? (parsed as Record) : null; + if (record?.type === "modcdp.nats.hello") { + this.peer_seen = true; + for (const waiter of this.peer_waiters) { + clearTimeout(waiter.timeout); + waiter.resolve(); + } + this.peer_waiters.clear(); + return; + } + const message = record?.type === "modcdp.nats.message" ? record.message : parsed; + this.parseAndEmitRecv(JSON.stringify(message)); + } + + override toJSON() { + const json = super.toJSON(); + return { + ...json, + state: { + ...json.state, + connected: this.socket != null, + peer_seen: this.peer_seen, + peer_waiters: this.peer_waiters.size, + buffered_bytes: this.tcp_buffer.length + this.ws_buffer.length, + }, + }; + } +} + +function connectConfig() { + return { + verbose: false, + pedantic: false, + lang: "modcdp", + version: "1", + protocol: 1, + }; +} + +export { + DEFAULT_UPSTREAM_NATS_URL, + DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX, + DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS, + NATSUpstreamTransport, + NATSUpstreamTransportConfigSchema, +}; +export type { NATSUpstreamTransportConfig }; diff --git a/js/src/transport/NativeMessagingDownstreamTransport.ts b/js/src/transport/NativeMessagingDownstreamTransport.ts new file mode 100644 index 00000000..5667ad36 --- /dev/null +++ b/js/src/transport/NativeMessagingDownstreamTransport.ts @@ -0,0 +1,195 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import { + CdpCommandMessageSchema, + type CdpCommandMessage, + type CdpEventMessage, + type CdpResponseMessage, +} from "../types/modcdp.js"; +import { DownstreamTransport } from "./DownstreamTransport.js"; +import { z } from "zod"; + +const DEFAULT_NATIVEMESSAGING_BRIDGE_HOST_NAME = "com.modcdp.bridge"; +const DEFAULT_NATIVEMESSAGING_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; + +const NativeMessagingDownstreamTransportConfigSchema = z + .object({ + upstream_nativemessaging_host_name: z.string().default(DEFAULT_NATIVEMESSAGING_BRIDGE_HOST_NAME), + reconnect_interval_ms: z.number().positive().default(DEFAULT_NATIVEMESSAGING_BRIDGE_RECONNECT_INTERVAL_MS), + }) + .strict(); + +/** + * Owns the native messaging downstream connection from the extension service + * worker to a native ModCDP host. + * + * This class owns only nativemessaging lifecycle: chrome.runtime.connectNative, + * reconnect scheduling, port status, hello messages, command-message decoding, + * CDP response posting, and event-message forwarding. It does not own ModCDP + * command registration, routing, middleware, upstream target/session state, + * reversews, or NATS protocol handling. + * + * Lifecycle: + * 1. `start()` records the desired native host/reconnect interval and opens one + * native port if no port is currently connected. + * 2. Successful connection posts the native hello and marks the transport + * connected. + * 3. `onMessage` parses client CDP commands and delegates execution through + * the injected `handleCommand` callback. + * 4. `onDisconnect` clears the active port, stores the browser-provided error, + * and schedules reconnect while a host is still configured. + */ +class NativeMessagingDownstreamTransport extends DownstreamTransport { + readonly name = "nativemessaging" as const; + config: z.infer = + NativeMessagingDownstreamTransportConfigSchema.parse({}); + + // True after start configures the native host. Cleared by stop and read by + // reconnect scheduling/status. + private started = false; + + // Active native messaging port. Set by connect, cleared by disconnect/error, + // read by start and emit. + private port: chrome.runtime.Port | null = null; + + // Pending reconnect timer. Set by scheduleReconnect and cleared when it fires. + private reconnect_timer: ReturnType | null = null; + + // Number of native connection attempts. Incremented by connect, read by the + // server status surface. + private attempts = 0; + + // Last native messaging error. Updated by connect/disconnect, read by the + // server status surface. + private last_error: string | null = null; + + // Request object -> native port that sent it. Written by handleMessage and + // read by sendResponse so responses go only to the originating downstream client. + private readonly port_from_request = new WeakMap(); + + /** True when the native messaging port is connected and can receive events. */ + get connected() { + return this.port != null; + } + + /** Configure and start the nativemessaging downstream connection. */ + start(hostName?: string, config: z.input = {}) { + this.config = NativeMessagingDownstreamTransportConfigSchema.parse({ + ...config, + upstream_nativemessaging_host_name: hostName, + }); + this.started = true; + return this.connect(this.config.upstream_nativemessaging_host_name); + } + + /** Start polling for native messaging clients using the shipped extension default. */ + startPollingForClients() { + return this.start(); + } + + /** Stop reconnecting and disconnect the active native messaging port. */ + stop(reason = "stopped") { + const upstream_nativemessaging_host_name = this.started ? this.config.upstream_nativemessaging_host_name : null; + this.started = false; + if (this.reconnect_timer) { + clearTimeout(this.reconnect_timer); + this.reconnect_timer = null; + } + const port = this.port; + this.port = null; + try { + port?.disconnect(); + } catch {} + return { upstream_nativemessaging_host_name, stopped: true, reason }; + } + + /** Send one CDP response to the native host that sent the request. */ + sendResponse(request: CdpCommandMessage, response: CdpResponseMessage) { + const port = this.port_from_request.get(request); + if (!port) return false; + port.postMessage(response); + this.port_from_request.delete(request); + return true; + } + + /** Send one CDP event message to the connected native host. */ + sendEvent(message: CdpEventMessage) { + if (!this.port) return 0; + this.port.postMessage(message); + return 1; + } + + /** Return generic status without exposing nativemessaging lifecycle to ModCDPServer. */ + status() { + return { + connected: this.connected, + attempts: this.attempts, + last_error: this.last_error, + config: this.started ? this.config : {}, + }; + } + + private scheduleReconnect() { + if (!this.started) return; + if (this.reconnect_timer) return; + this.reconnect_timer = setTimeout(() => { + this.reconnect_timer = null; + if (this.started) this.connect(this.config.upstream_nativemessaging_host_name); + }, this.config.reconnect_interval_ms); + } + + private connect(hostName: string) { + const chrome_api = globalThis.chrome; + if (!chrome_api?.runtime?.connectNative) { + this.scheduleReconnect(); + return { + upstream_nativemessaging_host_name: hostName, + connected: false, + reason: "nativemessaging_unavailable", + }; + } + if (this.port) return { upstream_nativemessaging_host_name: hostName, connected: true }; + try { + this.attempts += 1; + this.last_error = null; + const port = chrome_api.runtime.connectNative(hostName); + this.port = port; + port.postMessage({ + type: "modcdp.nativemessaging.hello", + role: "extension-service-worker", + version: 1, + extension_id: globalThis.chrome?.runtime?.id ?? null, + }); + port.onMessage.addListener((message) => { + void this.handleMessage(port, message); + }); + port.onDisconnect.addListener(() => { + if (this.port === port) this.port = null; + this.last_error = chrome_api.runtime.lastError?.message ?? "Native messaging port disconnected."; + this.scheduleReconnect(); + }); + return { upstream_nativemessaging_host_name: hostName, connected: true }; + } catch (error) { + this.port = null; + this.last_error = error instanceof Error ? error.message : String(error); + this.scheduleReconnect(); + return { + upstream_nativemessaging_host_name: hostName, + connected: false, + reason: this.last_error, + }; + } + } + + private async handleMessage(port: chrome.runtime.Port, data: unknown) { + const message = CdpCommandMessageSchema.parse(data); + this.port_from_request.set(message, port); + await this.handleRequest(message); + } +} + +export { + DEFAULT_NATIVEMESSAGING_BRIDGE_HOST_NAME, + DEFAULT_NATIVEMESSAGING_BRIDGE_RECONNECT_INTERVAL_MS, + NativeMessagingDownstreamTransport, +}; diff --git a/js/src/transport/NativeMessagingUpstreamTransport.ts b/js/src/transport/NativeMessagingUpstreamTransport.ts index b9a87e83..6efb32aa 100644 --- a/js/src/transport/NativeMessagingUpstreamTransport.ts +++ b/js/src/transport/NativeMessagingUpstreamTransport.ts @@ -1,321 +1,120 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { execFileSync } from "node:child_process"; -import type { CdpCommandMessage } from "../types/modcdp.js"; -import { DEFAULT_MODCDP_EXTENSION_ID } from "../injector/ExtensionInjector.js"; -import { UpstreamTransport, type UpstreamTransportConfig } from "./UpstreamTransport.js"; - -export const DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME = "com.modcdp.bridge"; -export const DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS = 10_000; - -type NativeMessagingOptions = { - upstream_nativemessaging_manifest?: string | null; - upstream_nativemessaging_manifests?: string[] | null; - upstream_nativemessaging_host_name?: string | null; - injector_extension_id?: string | null; - upstream_nativemessaging_wait_timeout_ms?: number; -}; +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import { z } from "zod"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import type { CdpCommandMessage, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS } from "../types/modcdp.js"; +import { UpstreamTransport, type TargetRoute } from "./UpstreamTransport.js"; + +const DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME = "com.modcdp.bridge"; + +const NativeMessagingUpstreamTransportConfigSchema = z.object({ + upstream_mode: z.literal("nativemessaging").default("nativemessaging"), + upstream_nativemessaging_host_name: z.string().default(DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), +}); +type NativeMessagingUpstreamTransportConfig = z.infer; -export class NativeMessagingUpstreamTransport extends UpstreamTransport { - readonly mode = "nativemessaging" as const; - readonly endpoint_kind = "modcdp_server" as const; - url = ""; - private server: any = null; - private socket: any = null; - private peer_waiters = new Set<{ - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }>(); - private wait_timeout_ms: number; - private upstream_nativemessaging_manifest: string | null; - private upstream_nativemessaging_manifests: string[]; - private include_default_manifest_paths: boolean; - private upstream_nativemessaging_host_name: string; - private extension_id: string; - private user_data_dir: string | null = null; - private bound_port: number | null = null; - private cdp_url: string | null = null; +class NativeMessagingUpstreamTransport extends UpstreamTransport { + declare config: NativeMessagingUpstreamTransportConfig; + override peer_kind = "modcdp_server" as const; + private buffer: Buffer = Buffer.alloc(0); + private read_native_message: ((chunk: Buffer) => void) | null = null; - constructor({ - upstream_nativemessaging_manifest = null, - upstream_nativemessaging_manifests = null, - upstream_nativemessaging_host_name = DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME, - injector_extension_id = DEFAULT_MODCDP_EXTENSION_ID, - upstream_nativemessaging_wait_timeout_ms = DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS, - }: NativeMessagingOptions = {}) { + constructor(config: z.input = {}) { super(); - this.upstream_nativemessaging_manifest = upstream_nativemessaging_manifest; - this.upstream_nativemessaging_manifests = upstream_nativemessaging_manifests ?? []; - this.include_default_manifest_paths = - !upstream_nativemessaging_manifest && !upstream_nativemessaging_manifests?.length; - this.upstream_nativemessaging_host_name = - upstream_nativemessaging_host_name || DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME; - this.extension_id = injector_extension_id || DEFAULT_MODCDP_EXTENSION_ID; - this.wait_timeout_ms = upstream_nativemessaging_wait_timeout_ms; - } - - update(config: UpstreamTransportConfig = {}) { - let should_install_native_host = false; - if (config.upstream_nativemessaging_manifest !== undefined) { - this.upstream_nativemessaging_manifest = config.upstream_nativemessaging_manifest; - should_install_native_host = true; - } - if (config.upstream_nativemessaging_manifests !== undefined) { - this.upstream_nativemessaging_manifests = config.upstream_nativemessaging_manifests ?? []; - should_install_native_host = true; - } - this.include_default_manifest_paths = - !this.upstream_nativemessaging_manifest && this.upstream_nativemessaging_manifests.length === 0; - if (config.upstream_nativemessaging_host_name) { - this.upstream_nativemessaging_host_name = config.upstream_nativemessaging_host_name; - should_install_native_host = true; - } - if (typeof config.upstream_nativemessaging_wait_timeout_ms === "number") - this.wait_timeout_ms = config.upstream_nativemessaging_wait_timeout_ms; - if (config.injector_extension_id) { - this.extension_id = config.injector_extension_id; - should_install_native_host = true; + this.config = NativeMessagingUpstreamTransportConfigSchema.parse({ ...config, upstream_mode: "nativemessaging" }); + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + if (!this.read_native_message) + throw new Error( + `Native messaging stdio is not connected for ${this.config.upstream_nativemessaging_host_name}.`, + ); + writeLengthPrefixedJSON(process.stdout, command); + return; } - if (config.user_data_dir && config.user_data_dir !== this.user_data_dir) { - this.setProfileManifestPaths(config.user_data_dir); - this.user_data_dir = config.user_data_dir; - should_install_native_host = true; + if (typeof command === "string") { + return super.send( + command, + params as ProtocolPayload, + typeof route_or_sessionId === "string" ? route_or_sessionId : null, + config, + ); } - if (should_install_native_host && this.bound_port != null) this.installNativeHost(this.bound_port); - this.cdp_url = config.cdp_url ?? this.cdp_url; - return this; - } - - getServerConfig() { - return this.cdp_url ? { server_loopback_cdp_url: this.cdp_url } : {}; + return super.send(command, params as z.input, route_or_sessionId); } - getInjectorConfig() { - return { - upstream_nativemessaging_host_name: this.upstream_nativemessaging_host_name, - }; + override update(config: Record = {}) { + this.config = NativeMessagingUpstreamTransportConfigSchema.parse({ + ...this.config, + ...config, + upstream_mode: "nativemessaging", + }); + return this; } async connect() { if (typeof process !== "object" || !process?.versions?.node) { - throw new Error("upstream.upstream_mode=nativemessaging requires Node."); + throw new Error("upstream_mode=nativemessaging requires Node."); } - const net = await import("node:net"); - const server = net.createServer((socket) => this.accept(socket)); - this.server = server; - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => resolve()); - }); - const address = server.address(); - if (!address || typeof address === "string") throw new Error("Native messaging bridge did not bind a TCP port."); - this.url = `native://${this.upstream_nativemessaging_host_name}@127.0.0.1:${address.port}`; - this.bound_port = address.port; - this.installNativeHost(address.port); - } - - send(message: CdpCommandMessage) { - if (!this.socket || this.socket.destroyed) - throw new Error(`No native messaging peer is connected for ${this.upstream_nativemessaging_host_name}.`); - writeLengthPrefixedJSON(this.socket, message); + if (this.read_native_message) return; + this.read_native_message = (chunk) => { + this.buffer = Buffer.concat([this.buffer, chunk]); + this.buffer = readLengthPrefixedJSON(this.buffer, (message) => { + this.parseAndEmitRecv(JSON.stringify(message)); + }); + }; + process.stdin.on("data", this.read_native_message); + process.stdin.on("end", () => this.emitClose(new Error("Native messaging stdin closed"))); + process.stdin.on("error", () => this.emitClose(new Error("Native messaging stdin error"))); } async waitForPeer() { - if (this.socket && !this.socket.destroyed) return; - await new Promise((resolve, reject) => { - let waiter!: { - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }; - const timeout = setTimeout(() => { - this.peer_waiters.delete(waiter); - reject( - new Error( - `Timed out waiting ${this.wait_timeout_ms}ms for native messaging host ${this.upstream_nativemessaging_host_name}.`, - ), - ); - }, this.wait_timeout_ms); - waiter = { resolve, reject, timeout }; - this.peer_waiters.add(waiter); - }); + if (!this.read_native_message) + throw new Error(`Native messaging stdio is not connected for ${this.config.upstream_nativemessaging_host_name}.`); } async close() { - try { - this.socket?.destroy(); - } catch {} - this.socket = null; - if (this.server) await new Promise((resolve) => this.server.close(() => resolve())); - this.server = null; - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.reject( - new Error( - `Native messaging transport for ${this.upstream_nativemessaging_host_name} closed before a peer connected.`, - ), - ); - } - this.peer_waiters.clear(); - } - - private accept(socket: any) { - if (this.socket && this.socket !== socket) { - try { - this.socket.destroy(); - } catch {} - } - this.socket = socket; - const readMessage = createLengthPrefixedJSONReader((message) => { - if (message?.type === "modcdp.native.hello") return; - this.parseAndEmitRecv(JSON.stringify(message)); - }); - socket.on("data", readMessage); - socket.on("close", () => { - if (this.socket !== socket) return; - this.socket = null; - this.emitClose(new Error("Native messaging host disconnected")); - }); - socket.on("error", () => { - if (this.socket !== socket) return; - this.socket = null; - this.emitClose(new Error("Native messaging host error")); - }); - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.resolve(); - } - this.peer_waiters.clear(); - } - - private installNativeHost(port: number) { - const hostDir = path.join(os.homedir(), ".modcdp", "native-messaging"); - fs.mkdirSync(hostDir, { recursive: true }); - - const configPath = path.join(hostDir, `${this.upstream_nativemessaging_host_name}.config.json`); - const hostScriptPath = path.join(hostDir, `${this.upstream_nativemessaging_host_name}.mjs`); - const hostExecutablePath = path.join( - hostDir, - `${this.upstream_nativemessaging_host_name}${process.platform === "win32" ? ".cmd" : ".sh"}`, - ); - fs.writeFileSync(configPath, JSON.stringify({ host: "127.0.0.1", port }, null, 2)); - fs.writeFileSync(hostScriptPath, nativeHostScript(configPath)); - fs.writeFileSync(hostExecutablePath, nativeHostWrapper(process.execPath, hostScriptPath)); - fs.chmodSync(hostExecutablePath, 0o755); - - const manifestPaths = - this.upstream_nativemessaging_manifest || this.upstream_nativemessaging_manifests.length > 0 - ? [ - ...(this.upstream_nativemessaging_manifest ? [this.upstream_nativemessaging_manifest] : []), - ...this.upstream_nativemessaging_manifests, - ...(this.include_default_manifest_paths - ? defaultNativeMessagingManifestPaths(this.upstream_nativemessaging_host_name, os.homedir()) - : []), - ] - : defaultNativeMessagingManifestPaths(this.upstream_nativemessaging_host_name, os.homedir()); - const manifest = JSON.stringify( - { - name: this.upstream_nativemessaging_host_name, - description: "ModCDP Native Messaging bridge", - path: hostExecutablePath, - type: "stdio", - allowed_origins: [`chrome-extension://${this.extension_id}/`], - }, - null, - 2, - ); - for (const manifestPath of manifestPaths) { - fs.mkdirSync(path.dirname(manifestPath), { recursive: true }); - fs.writeFileSync(manifestPath, manifest); - } - if (process.platform === "win32" && manifestPaths[0]) { - registerWindowsNativeMessagingHost(this.upstream_nativemessaging_host_name, manifestPaths[0]); + if (this.read_native_message) { + process.stdin.off("data", this.read_native_message); + this.read_native_message = null; } } - private setProfileManifestPaths(user_data_dir: string) { - const previous_profile_manifest_paths = this.user_data_dir - ? [ - path.join(this.user_data_dir, "NativeMessagingHosts", `${this.upstream_nativemessaging_host_name}.json`), - path.join( - this.user_data_dir, - "Default", - "NativeMessagingHosts", - `${this.upstream_nativemessaging_host_name}.json`, - ), - ] - : []; - const profile_manifest_paths = [ - path.join(user_data_dir, "NativeMessagingHosts", `${this.upstream_nativemessaging_host_name}.json`), - path.join(user_data_dir, "Default", "NativeMessagingHosts", `${this.upstream_nativemessaging_host_name}.json`), - ]; - this.upstream_nativemessaging_manifests = [ - ...profile_manifest_paths, - ...this.upstream_nativemessaging_manifests.filter( - (upstream_nativemessaging_manifest) => - !previous_profile_manifest_paths.includes(upstream_nativemessaging_manifest) && - !profile_manifest_paths.includes(upstream_nativemessaging_manifest), - ), - ]; - } -} - -function defaultNativeMessagingManifestPaths(upstream_nativemessaging_host_name: string, home: string) { - if (process.platform === "darwin") { - return [ - `${home}/Library/Application Support/Google/Chrome/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/Library/Application Support/Google/ChromeForTesting/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/Library/Application Support/Google/Chrome for Testing/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/Library/Application Support/Google/Chrome SxS/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/Library/Application Support/Chromium/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - ]; - } - if (process.platform === "linux") { - return [ - `${home}/.config/google-chrome/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/.config/google-chrome-for-testing/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/.config/chromium/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - `${home}/.config/chromium-browser/NativeMessagingHosts/${upstream_nativemessaging_host_name}.json`, - ]; - } - if (process.platform === "win32") { - return [path.join(home, ".modcdp", "native-messaging", `${upstream_nativemessaging_host_name}.json`)]; - } - throw new Error("upstream_nativemessaging_manifest is required on this platform."); -} - -function nativeHostWrapper(node_path: string, host_script_path: string) { - if (process.platform === "win32") { - return `@echo off\r\n${cmdQuote(node_path)} ${cmdQuote(host_script_path)}\r\n`; + override toJSON() { + const json = super.toJSON(); + return { + ...json, + state: { ...json.state, connected: this.read_native_message != null, buffered_bytes: this.buffer.length }, + }; } - return `#!/bin/sh\nexec ${JSON.stringify(node_path)} ${JSON.stringify(host_script_path)}\n`; -} - -function cmdQuote(value: string) { - return `"${value.replace(/"/g, '""')}"`; -} - -function registerWindowsNativeMessagingHost( - upstream_nativemessaging_host_name: string, - upstream_nativemessaging_manifest: string, -) { - execFileSync( - "reg", - [ - "add", - `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${upstream_nativemessaging_host_name}`, - "/ve", - "/t", - "REG_SZ", - "/d", - upstream_nativemessaging_manifest, - "/f", - ], - { stdio: "ignore" }, - ); } function writeLengthPrefixedJSON(stream: { write: (chunk: Buffer) => void }, message: unknown) { @@ -325,74 +124,20 @@ function writeLengthPrefixedJSON(stream: { write: (chunk: Buffer) => void }, mes stream.write(Buffer.concat([header, body])); } -function createLengthPrefixedJSONReader(onMessage: (message: any) => void) { - let buffer = Buffer.alloc(0); - return (chunk: Buffer) => { - buffer = Buffer.concat([buffer, chunk]); - while (buffer.length >= 4) { - const length = buffer.readUInt32LE(0); - if (buffer.length < length + 4) return; - const body = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); - try { - onMessage(JSON.parse(body.toString("utf8"))); - } catch {} - } - }; -} - -function nativeHostScript(configPath: string) { - return ` -import fs from "node:fs"; -import net from "node:net"; - -const config = JSON.parse(fs.readFileSync(${JSON.stringify(configPath)}, "utf8")); -const socket = net.createConnection({ host: config.host, port: config.port }, () => { - writeTCP({ type: "modcdp.native.hello", role: "native-host", version: 1 }); -}); -let stdinBuffer = Buffer.alloc(0); -let socketBuffer = Buffer.alloc(0); - -process.stdin.on("data", (chunk) => { - stdinBuffer = Buffer.concat([stdinBuffer, chunk]); - stdinBuffer = readMessages(stdinBuffer, (message) => { - writeTCP(message); - }); -}); -socket.on("data", (chunk) => { - socketBuffer = Buffer.concat([socketBuffer, chunk]); - socketBuffer = readMessages(socketBuffer, (message) => { - writeNative(message); - }); -}); -socket.on("close", () => process.exit(0)); -socket.on("error", () => process.exit(1)); - -function readMessages(buffer, onRecv) { +function readLengthPrefixedJSON(buffer: Buffer, onRecv: (message: unknown) => void) { while (buffer.length >= 4) { const length = buffer.readUInt32LE(0); if (buffer.length < length + 4) return buffer; const body = buffer.subarray(4, 4 + length); buffer = buffer.subarray(4 + length); - try { - onRecv(JSON.parse(body.toString("utf8"))); - } catch {} + onRecv(JSON.parse(body.toString("utf8"))); } return buffer; } -function writeNative(message) { - const body = Buffer.from(JSON.stringify(message), "utf8"); - const header = Buffer.alloc(4); - header.writeUInt32LE(body.length, 0); - process.stdout.write(Buffer.concat([header, body])); -} - -function writeTCP(message) { - const body = Buffer.from(JSON.stringify(message), "utf8"); - const header = Buffer.alloc(4); - header.writeUInt32LE(body.length, 0); - socket.write(Buffer.concat([header, body])); -} -`; -} +export { + DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME, + NativeMessagingUpstreamTransport, + NativeMessagingUpstreamTransportConfigSchema, +}; +export type { NativeMessagingUpstreamTransportConfig }; diff --git a/js/src/transport/NatsUpstreamTransport.ts b/js/src/transport/NatsUpstreamTransport.ts deleted file mode 100644 index add2732f..00000000 --- a/js/src/transport/NatsUpstreamTransport.ts +++ /dev/null @@ -1,284 +0,0 @@ -import net from "node:net"; -import tls from "node:tls"; -import type { CdpCommandMessage } from "../types/modcdp.js"; -import { UpstreamTransport, type UpstreamTransportConfig } from "./UpstreamTransport.js"; - -export const DEFAULT_UPSTREAM_NATS_URL = "ws://127.0.0.1:4223"; -export const DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX = "modcdp.default"; -export const DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS = 10_000; - -type NatsRole = "client" | "browser"; -type NatsOptions = { - upstream_nats_url?: string | null; - upstream_nats_subject_prefix?: string | null; - upstream_nats_role?: NatsRole; - upstream_nats_wait_timeout_ms?: number; -}; - -type NatsSocket = WebSocket | net.Socket | tls.TLSSocket; - -export class NatsUpstreamTransport extends UpstreamTransport { - readonly mode = "nats" as const; - readonly endpoint_kind = "modcdp_server" as const; - declare url: string; - upstream_nats_subject_prefix: string; - private upstream_nats_role: NatsRole; - private wait_timeout_ms: number; - private socket: NatsSocket | null = null; - private tcp_buffer = Buffer.alloc(0); - private ws_buffer = ""; - private sid = "1"; - private connected = false; - private peer_seen = false; - private peer_waiters = new Set<{ - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }>(); - - constructor(options: NatsOptions = {}) { - super(); - const { url, upstream_nats_subject_prefix } = normalizeNatsUrl( - options.upstream_nats_url ?? DEFAULT_UPSTREAM_NATS_URL, - options.upstream_nats_subject_prefix, - ); - this.url = url; - this.upstream_nats_subject_prefix = upstream_nats_subject_prefix; - this.upstream_nats_role = options.upstream_nats_role ?? "client"; - this.wait_timeout_ms = options.upstream_nats_wait_timeout_ms ?? DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS; - } - - update(config: UpstreamTransportConfig = {}) { - if (config.upstream_nats_url || config.upstream_nats_subject_prefix) { - const normalized = normalizeNatsUrl( - config.upstream_nats_url ?? this.url, - config.upstream_nats_subject_prefix ?? this.upstream_nats_subject_prefix, - ); - this.url = normalized.url; - this.upstream_nats_subject_prefix = normalized.upstream_nats_subject_prefix; - } - if (config.upstream_nats_role === "client" || config.upstream_nats_role === "browser") - this.upstream_nats_role = config.upstream_nats_role; - if (typeof config.upstream_nats_wait_timeout_ms === "number") - this.wait_timeout_ms = config.upstream_nats_wait_timeout_ms; - return this; - } - - getInjectorConfig() { - return { - upstream_nats_url: this.url, - upstream_nats_subject_prefix: this.upstream_nats_subject_prefix, - }; - } - - async connect() { - if (this.connected) return; - const parsed = new URL(this.url); - if (parsed.protocol === "ws:" || parsed.protocol === "wss:") await this.connectWebSocket(parsed); - else if (parsed.protocol === "nats:" || parsed.protocol === "tls:") await this.connectTcp(parsed); - else - throw new Error(`upstream.upstream_mode=nats requires ws://, wss://, nats://, or tls:// URL, got ${this.url}.`); - this.connected = true; - this.subscribe(); - this.publish(this.outgoingSubject(), { - type: "modcdp.nats.hello", - role: this.upstream_nats_role, - version: 1, - }); - } - - send(message: CdpCommandMessage) { - if (!this.connected || !this.socket) throw new Error("NATS transport is not connected."); - this.publish(this.outgoingSubject(), { - type: "modcdp.nats.message", - message, - }); - } - - async waitForPeer() { - if (this.peer_seen) return; - await new Promise((resolve, reject) => { - let waiter!: { - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }; - const timeout = setTimeout(() => { - this.peer_waiters.delete(waiter); - reject(new Error(`Timed out waiting ${this.wait_timeout_ms}ms for NATS ModCDP peer.`)); - }, this.wait_timeout_ms); - waiter = { resolve, reject, timeout }; - this.peer_waiters.add(waiter); - }); - } - - async close() { - try { - if (this.socket instanceof WebSocket) this.socket.close(); - else this.socket?.destroy(); - } catch {} - this.socket = null; - this.connected = false; - this.peer_seen = false; - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.reject( - new Error(`NATS transport for ${this.upstream_nats_subject_prefix} closed before a peer connected.`), - ); - } - this.peer_waiters.clear(); - } - - private async connectWebSocket(url: URL) { - const ws = new WebSocket(url); - this.socket = ws; - ws.addEventListener("message", (event) => { - void this.readWebSocket(event.data); - }); - ws.addEventListener("close", () => this.emitClose(new Error("NATS websocket closed"))); - ws.addEventListener("error", () => this.emitClose(new Error("NATS websocket error"))); - await new Promise((resolve, reject) => { - const cleanup = () => { - ws.removeEventListener("open", onOpen); - ws.removeEventListener("error", onError); - }; - const onOpen = () => { - cleanup(); - this.writeProtocol(`CONNECT ${JSON.stringify(connectOptions())}\r\nPING\r\n`); - resolve(); - }; - const onError = () => { - cleanup(); - reject(new Error(`NATS websocket connection failed for ${url.toString()}`)); - }; - ws.addEventListener("open", onOpen); - ws.addEventListener("error", onError); - }); - } - - private async connectTcp(url: URL) { - const port = Number(url.port || (url.protocol === "tls:" ? 4222 : 4222)); - const host = url.hostname || "127.0.0.1"; - const socket = url.protocol === "tls:" ? tls.connect({ host, port }) : net.connect({ host, port }); - this.socket = socket; - socket.on("data", (chunk) => this.readTcp(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); - socket.on("close", () => this.emitClose(new Error("NATS socket closed"))); - socket.on("error", () => this.emitClose(new Error("NATS socket error"))); - await new Promise((resolve, reject) => { - socket.once("connect", () => { - this.writeProtocol(`CONNECT ${JSON.stringify(connectOptions())}\r\nPING\r\n`); - resolve(); - }); - socket.once("error", reject); - }); - } - - private subscribe() { - this.writeProtocol(`SUB ${this.incomingSubject()} ${this.sid}\r\n`); - } - - private publish(subject: string, message: unknown) { - const body = JSON.stringify(message); - this.writeProtocol(`PUB ${subject} ${Buffer.byteLength(body)}\r\n${body}\r\n`); - } - - private writeProtocol(data: string) { - const socket = this.socket; - if (!socket) throw new Error("NATS transport is not connected."); - if (socket instanceof WebSocket) socket.send(data); - else socket.write(data); - } - - private incomingSubject() { - return `${this.upstream_nats_subject_prefix}.${this.upstream_nats_role === "client" ? "browser_to_client" : "client_to_browser"}`; - } - - private outgoingSubject() { - return `${this.upstream_nats_subject_prefix}.${this.upstream_nats_role === "client" ? "client_to_browser" : "browser_to_client"}`; - } - - private async readWebSocket(data: unknown) { - if (data instanceof ArrayBuffer) this.ws_buffer += Buffer.from(data).toString("utf8"); - else if (ArrayBuffer.isView(data)) - this.ws_buffer += Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8"); - else if (typeof Blob !== "undefined" && data instanceof Blob) this.ws_buffer += await data.text(); - else this.ws_buffer += String(data); - this.ws_buffer = this.consumeProtocol(this.ws_buffer); - } - - private readTcp(chunk: Buffer) { - this.tcp_buffer = Buffer.concat([this.tcp_buffer, chunk]); - const remaining = this.consumeProtocol(this.tcp_buffer.toString("utf8")); - this.tcp_buffer = Buffer.from(remaining, "utf8"); - } - - private consumeProtocol(buffer: string) { - for (;;) { - const lineEnd = buffer.indexOf("\r\n"); - if (lineEnd < 0) return buffer; - const line = buffer.slice(0, lineEnd); - const upper = line.toUpperCase(); - if (upper.startsWith("MSG ")) { - const parts = line.split(/\s+/); - const size = Number(parts[parts.length - 1]); - const payloadStart = lineEnd + 2; - const payloadEnd = payloadStart + size; - if (!Number.isInteger(size) || buffer.length < payloadEnd + 2) return buffer; - const payload = buffer.slice(payloadStart, payloadEnd); - buffer = buffer.slice(payloadEnd + 2); - this.handlePayload(payload); - continue; - } - buffer = buffer.slice(lineEnd + 2); - if (upper === "PING") this.writeProtocol("PONG\r\n"); - else if (upper.startsWith("-ERR")) this.emitClose(new Error(`NATS error: ${line}`)); - } - } - - private handlePayload(payload: string) { - let parsed: unknown; - try { - parsed = JSON.parse(payload); - } catch { - return; - } - const record = parsed && typeof parsed === "object" ? (parsed as Record) : null; - if (record?.type === "modcdp.nats.hello") { - this.peer_seen = true; - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.resolve(); - } - this.peer_waiters.clear(); - return; - } - const message = record?.type === "modcdp.nats.message" ? record.message : parsed; - this.parseAndEmitRecv(JSON.stringify(message)); - } -} - -function connectOptions() { - return { - verbose: false, - pedantic: false, - lang: "modcdp", - version: "1", - protocol: 1, - }; -} - -function normalizeNatsUrl(url: string, upstream_nats_subject_prefix?: string | null) { - const parsed = new URL(url); - const subject = upstream_nats_subject_prefix || parsed.searchParams.get("upstream_nats_subject_prefix"); - parsed.searchParams.delete("upstream_nats_subject_prefix"); - return { - url: parsed.toString(), - upstream_nats_subject_prefix: sanitizeSubjectPrefix(subject || DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX), - }; -} - -function sanitizeSubjectPrefix(value: string) { - const subject = value.trim(); - if (!subject || /[\s*>]/.test(subject)) throw new Error(`Invalid NATS subject prefix ${value}`); - return subject; -} diff --git a/js/src/transport/PipeUpstreamTransport.ts b/js/src/transport/PipeUpstreamTransport.ts index 71cf48d6..31d05bb7 100644 --- a/js/src/transport/PipeUpstreamTransport.ts +++ b/js/src/transport/PipeUpstreamTransport.ts @@ -1,67 +1,112 @@ -import type { CdpCommandMessage } from "../types/modcdp.js"; -import { UpstreamTransport, type UpstreamTransportConfig } from "./UpstreamTransport.js"; +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import { z } from "zod"; +import type { LauncherConfig } from "../launcher/BrowserLauncher.js"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import type { CdpCommandMessage, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS } from "../types/modcdp.js"; +import { UpstreamTransport, type TargetRoute } from "./UpstreamTransport.js"; -export class PipeUpstreamTransport extends UpstreamTransport { - readonly mode = "pipe" as const; - readonly endpoint_kind = "raw_cdp" as const; - declare url: string; - private buffer = ""; - private connected = false; +const PipeUpstreamTransportConfigSchema = z.object({ + upstream_mode: z.literal("pipe").default("pipe"), + upstream_pipe_read: z.custom().optional(), + upstream_pipe_write: z.custom().optional(), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), +}); +type PipeUpstreamTransportConfig = z.infer; - private pipe_read: NodeJS.ReadableStream | null; - private pipe_write: NodeJS.WritableStream | null; +class PipeUpstreamTransport extends UpstreamTransport { + declare config: PipeUpstreamTransportConfig; + private buffer = ""; + private pipe_cleanup: (() => void) | null = null; - constructor({ - pipe_read = null, - pipe_write = null, - cdp_url = "pipe://unknown", - }: { - pipe_read?: NodeJS.ReadableStream | null; - pipe_write?: NodeJS.WritableStream | null; - cdp_url?: string | null; - } = {}) { + constructor(config: z.input = {}) { super(); - this.pipe_read = pipe_read; - this.pipe_write = pipe_write; - this.url = cdp_url ?? "pipe://unknown"; + this.config = PipeUpstreamTransportConfigSchema.parse({ ...config, upstream_mode: "pipe" }); + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + if (!this.config.upstream_pipe_write || !this.pipe_cleanup) throw new Error("CDP pipe is not connected."); + this.config.upstream_pipe_write.write(`${JSON.stringify(command)}\0`); + return; + } + if (typeof command === "string") { + return super.send( + command, + params as ProtocolPayload, + typeof route_or_sessionId === "string" ? route_or_sessionId : null, + config, + ); + } + return super.send(command, params as z.input, route_or_sessionId); } - update(config: UpstreamTransportConfig = {}) { - this.pipe_read = config.pipe_read ?? this.pipe_read; - this.pipe_write = config.pipe_write ?? this.pipe_write; - this.url = config.cdp_url ?? this.url; + override update(config: Record = {}) { + this.config = PipeUpstreamTransportConfigSchema.parse({ ...this.config, ...config, upstream_mode: "pipe" }); return this; } - getLauncherConfig() { - return { remote_debugging: "pipe" as const }; + override configForLauncher(): LauncherConfig { + return { launcher_local_cdp_transport: "pipe" } as unknown as LauncherConfig; } async connect() { - if (!this.pipe_read || !this.pipe_write) { - throw new Error("upstream.upstream_mode=pipe requires launcher-provided remote-debugging pipe handles."); + if (!this.config.upstream_pipe_read || !this.config.upstream_pipe_write) { + throw new Error("upstream_mode=pipe requires launcher-provided CDP pipe handles."); } - if (this.connected) return; - this.connected = true; - this.pipe_read.on("data", (chunk) => this.read(chunk)); - this.pipe_read.on("end", () => this.handleClose(new Error("CDP pipe closed"))); - this.pipe_read.on("error", () => this.handleClose(new Error("CDP pipe error"))); - this.pipe_write.on("error", () => this.handleClose(new Error("CDP pipe write error"))); - } - - send(message: CdpCommandMessage) { - if (!this.pipe_write || !this.connected) throw new Error("CDP pipe is not connected."); - this.pipe_write.write(`${JSON.stringify(message)}\0`); + if (this.pipe_cleanup) return; + const on_data = (chunk: Buffer | string) => this.read(chunk); + const on_end = () => this.handleClose(new Error("CDP pipe closed")); + const on_read_error = () => this.handleClose(new Error("CDP pipe error")); + const on_write_error = () => this.handleClose(new Error("CDP pipe write error")); + this.config.upstream_pipe_read.on("data", on_data); + this.config.upstream_pipe_read.on("end", on_end); + this.config.upstream_pipe_read.on("error", on_read_error); + this.config.upstream_pipe_write.on("error", on_write_error); + this.pipe_cleanup = () => { + this.config.upstream_pipe_read?.off("data", on_data); + this.config.upstream_pipe_read?.off("end", on_end); + this.config.upstream_pipe_read?.off("error", on_read_error); + this.config.upstream_pipe_write?.off("error", on_write_error); + }; } async close() { + const pipe_cleanup = this.pipe_cleanup; + this.pipe_cleanup = null; + pipe_cleanup?.(); try { - this.pipe_write?.end(); + this.config.upstream_pipe_write?.end(); } catch {} try { - (this.pipe_read as { destroy?: () => void } | null)?.destroy?.(); + (this.config.upstream_pipe_read as { destroy?: () => void } | undefined)?.destroy?.(); } catch {} - this.connected = false; } private read(chunk: Buffer | string) { @@ -76,7 +121,20 @@ export class PipeUpstreamTransport extends UpstreamTransport { } private handleClose(error: Error) { - this.connected = false; + const pipe_cleanup = this.pipe_cleanup; + this.pipe_cleanup = null; + pipe_cleanup?.(); this.emitClose(error); } + + override toJSON() { + const json = super.toJSON(); + return { + ...json, + state: { ...json.state, connected: this.pipe_cleanup != null, buffered_bytes: this.buffer.length }, + }; + } } + +export { PipeUpstreamTransport, PipeUpstreamTransportConfigSchema }; +export type { PipeUpstreamTransportConfig }; diff --git a/js/src/transport/ReverseWSDownstreamTransport.ts b/js/src/transport/ReverseWSDownstreamTransport.ts new file mode 100644 index 00000000..db597e31 --- /dev/null +++ b/js/src/transport/ReverseWSDownstreamTransport.ts @@ -0,0 +1,186 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import { + CdpCommandMessageSchema, + type CdpCommandMessage, + type CdpEventMessage, + type CdpResponseMessage, +} from "../types/modcdp.js"; +import { DownstreamTransport } from "./DownstreamTransport.js"; +import { z } from "zod"; + +const DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS = 2_000; +const DEFAULT_REVERSE_BRIDGE_URL = "ws://127.0.0.1:29292"; + +const ReverseWSDownstreamTransportConfigSchema = z + .object({ + downstream_reversews_url: z.string().default(DEFAULT_REVERSE_BRIDGE_URL), + reconnect_interval_ms: z.number().positive().default(DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS), + }) + .strict(); + +/** + * Owns the reverse WebSocket downstream connection from the extension service + * worker back to a waiting ModCDP client/proxy. + * + * This class owns only reversews-specific lifecycle: socket creation, + * reconnect scheduling, hello messages, command-message decoding, CDP response + * writing, event-message forwarding, and stop semantics. It does not own + * ModCDP command registration, routing, middleware, upstream target/session + * state, native messaging, or NATS protocol handling. + * + * Lifecycle: + * 1. `start()` records the desired endpoint/reconnect interval and opens one + * WebSocket if none is already open or connecting. + * 2. `open` sends the reverse hello and asks the server to keep the extension + * service worker alive. + * 3. `message` parses client CDP commands and delegates execution through the + * injected `handleCommand` callback. + * 4. `error`/`close` drops the active socket reference and schedules reconnect + * while an endpoint is still configured. + * 5. `stop()` clears the endpoint and reconnect timer, then closes the socket. + */ +class ReverseWSDownstreamTransport extends DownstreamTransport { + readonly name = "reversews" as const; + config: z.infer = ReverseWSDownstreamTransportConfigSchema.parse({}); + + // True after start configures the reversews endpoint. Cleared by stop and read + // by reconnect scheduling/status. + private started = false; + + // Active reversews socket. Set by connect, cleared by error/close/stop, read + // by start and emit. + private socket: WebSocket | null = null; + + // Pending reconnect timer. Set by scheduleReconnect, cleared by stop or when + // the timer fires. + private reconnect_timer: ReturnType | null = null; + + // Request object -> WebSocket that sent it. Written by handleMessage and read + // by sendResponse so responses go only to the originating downstream client. + private readonly socket_from_request = new WeakMap(); + + /** True when the reversews socket is currently open and can receive events. */ + get connected() { + return this.socket?.readyState === WebSocket.OPEN; + } + + /** Configure and start the reversews downstream connection. */ + start(endpoint?: string, config: z.input = {}) { + this.config = ReverseWSDownstreamTransportConfigSchema.parse({ + ...config, + downstream_reversews_url: endpoint, + }); + if (!/^wss?:\/\//i.test(this.config.downstream_reversews_url)) { + throw new Error( + `reverse proxy endpoint must be a ws:// or wss:// URL, got ${this.config.downstream_reversews_url}.`, + ); + } + this.started = true; + void this.connect(this.config.downstream_reversews_url).catch(() => { + this.scheduleReconnect(); + }); + return { + downstream_reversews_url: this.config.downstream_reversews_url, + reconnect_interval_ms: this.config.reconnect_interval_ms, + connecting: true, + }; + } + + /** Start polling for reversews clients using the shipped extension default. */ + startPollingForClients() { + return this.start(); + } + + /** Stop reconnecting and close the active reversews socket. */ + stop(reason = "stopped") { + const downstream_reversews_url = this.started ? this.config.downstream_reversews_url : null; + this.started = false; + if (this.reconnect_timer) { + clearTimeout(this.reconnect_timer); + this.reconnect_timer = null; + } + const socket = this.socket; + this.socket = null; + if (socket?.readyState === WebSocket.OPEN || socket?.readyState === WebSocket.CONNECTING) { + socket.close(1000, reason); + } + return { downstream_reversews_url, stopped: true, reason }; + } + + /** Send one CDP response to the reversews client that sent the request. */ + sendResponse(request: CdpCommandMessage, response: CdpResponseMessage) { + const socket = this.socket_from_request.get(request); + if (socket?.readyState !== WebSocket.OPEN) return false; + socket.send(JSON.stringify(response)); + this.socket_from_request.delete(request); + return true; + } + + /** Send one CDP event message to the connected reversews client. */ + sendEvent(message: CdpEventMessage) { + if (this.socket?.readyState !== WebSocket.OPEN) return 0; + this.socket.send(JSON.stringify(message)); + return 1; + } + + /** Return generic status without exposing reversews-specific state to ModCDPServer. */ + status() { + return { + connected: this.connected, + config: this.started ? this.config : {}, + }; + } + + private scheduleReconnect() { + if (!this.started) return; + if (this.reconnect_timer) return; + this.reconnect_timer = setTimeout(() => { + this.reconnect_timer = null; + if (!this.started) return; + void this.connect(this.config.downstream_reversews_url).catch(() => {}); + }, this.config.reconnect_interval_ms); + } + + private async connect(endpoint: string) { + if (this.socket?.readyState === WebSocket.OPEN || this.socket?.readyState === WebSocket.CONNECTING) { + return { + downstream_reversews_url: endpoint, + connected: this.socket.readyState === WebSocket.OPEN, + }; + } + + const ws = new WebSocket(endpoint); + this.socket = ws; + ws.addEventListener("open", () => { + ws.send( + JSON.stringify({ + type: "modcdp.reverse.hello", + role: "extension-service-worker", + version: 1, + extension_id: globalThis.chrome?.runtime?.id ?? null, + }), + ); + }); + ws.addEventListener("message", (event) => { + void this.handleMessage(ws, event.data); + }); + ws.addEventListener("error", () => { + if (this.socket === ws) this.socket = null; + this.scheduleReconnect(); + }); + ws.addEventListener("close", () => { + if (this.socket === ws) this.socket = null; + this.scheduleReconnect(); + }); + return { downstream_reversews_url: endpoint, connected: false }; + } + + private async handleMessage(ws: WebSocket, data: unknown) { + const message = CdpCommandMessageSchema.parse(JSON.parse(typeof data === "string" ? data : String(data))); + this.socket_from_request.set(message, ws); + await this.handleRequest(message); + } +} + +export { DEFAULT_REVERSE_BRIDGE_RECONNECT_INTERVAL_MS, ReverseWSDownstreamTransport }; diff --git a/js/src/transport/ReverseWSUpstreamTransport.ts b/js/src/transport/ReverseWSUpstreamTransport.ts new file mode 100644 index 00000000..e32bbd8c --- /dev/null +++ b/js/src/transport/ReverseWSUpstreamTransport.ts @@ -0,0 +1,233 @@ +// MODCDP_TS_ONLY: DO NOT TRANSLATE THIS FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand (exotic transport). +import type { WebSocket as WsSocket, WebSocketServer as WsServer } from "ws"; +import { z } from "zod"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import type { CdpCommandMessage, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS } from "../types/modcdp.js"; +import { parseHostPort, UpstreamTransport, type UpstreamPeerWaitConfig } from "./UpstreamTransport.js"; +import type { TargetRoute } from "./UpstreamTransport.js"; + +const DEFAULT_UPSTREAM_REVERSEWS_BIND = "127.0.0.1:29292"; +const DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS = 10_000; + +const ReverseWSUpstreamTransportConfigSchema = z.object({ + upstream_mode: z.literal("reversews").default("reversews"), + upstream_reversews_bind: z.string().default(DEFAULT_UPSTREAM_REVERSEWS_BIND), + upstream_reversews_wait_timeout_ms: z.number().positive().default(DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), +}); +type ReverseWSUpstreamTransportConfig = z.infer; + +type ReverseHello = { + type: "modcdp.reverse.hello"; + role?: string; + version?: number; + extension_id?: string | null; +}; + +class ReverseWSUpstreamTransport extends UpstreamTransport { + declare config: ReverseWSUpstreamTransportConfig; + override peer_kind = "modcdp_server" as const; + endpoint_url: string; + private reversews_listener: WsServer | null = null; + private socket: WsSocket | null = null; + private peer_connected_at: number | null = null; + private peer_waiters = new Set<{ + resolve: () => void; + reject: (error: Error) => void; + timeout: ReturnType; + connected_after_ms: number | null; + }>(); + peer_info: ReverseHello | null = null; + + constructor(config: z.input = {}) { + super(); + this.config = ReverseWSUpstreamTransportConfigSchema.parse({ ...config, upstream_mode: "reversews" }); + this.endpoint_url = endpointFromBind(this.config.upstream_reversews_bind); + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + if (!this.socket || this.socket.readyState !== this.socket.OPEN) { + throw new Error(`No reverse ModCDP extension peer is connected at ${this.endpoint_url}.`); + } + this.socket.send(JSON.stringify(command)); + return; + } + if (typeof command === "string") { + return super.send( + command, + params as ProtocolPayload, + typeof route_or_sessionId === "string" ? route_or_sessionId : null, + config, + ); + } + return super.send(command, params as z.input, route_or_sessionId); + } + + override update(config: Record = {}) { + this.config = ReverseWSUpstreamTransportConfigSchema.parse({ + ...this.config, + ...config, + upstream_mode: "reversews", + }); + this.endpoint_url = endpointFromBind(this.config.upstream_reversews_bind); + return this; + } + + async connect() { + const { WebSocketServer } = await import("ws"); + const { host, port } = parseHostPort(this.endpoint_url, "127.0.0.1", 29292); + const reversews_listener = new WebSocketServer({ host, port }); + this.reversews_listener = reversews_listener; + reversews_listener.on("connection", (socket) => this.accept(socket)); + await new Promise((resolve, reject) => { + reversews_listener.once("listening", () => resolve()); + reversews_listener.once("error", reject); + }); + } + + async waitForPeer({ connected_after_ms = null }: UpstreamPeerWaitConfig = {}) { + if ( + this.socket && + this.socket.readyState === this.socket.OPEN && + (connected_after_ms == null || (this.peer_connected_at != null && this.peer_connected_at >= connected_after_ms)) + ) + return; + await new Promise((resolve, reject) => { + let waiter!: { + resolve: () => void; + reject: (error: Error) => void; + timeout: ReturnType; + connected_after_ms: number | null; + }; + const wait_timeout_ms = this.config.upstream_reversews_wait_timeout_ms; + const timeout = setTimeout(() => { + this.peer_waiters.delete(waiter); + reject(new Error(`Timed out waiting ${wait_timeout_ms}ms for reverse ModCDP extension connection.`)); + }, wait_timeout_ms); + waiter = { resolve, reject, timeout, connected_after_ms }; + this.peer_waiters.add(waiter); + }); + } + + async close() { + try { + this.socket?.close(); + } catch {} + this.socket = null; + this.peer_connected_at = null; + this.peer_info = null; + if (this.reversews_listener) await new Promise((resolve) => this.reversews_listener?.close(() => resolve())); + this.reversews_listener = null; + for (const waiter of this.peer_waiters) { + clearTimeout(waiter.timeout); + waiter.reject(new Error(`Reverse websocket transport at ${this.endpoint_url} closed before a peer connected.`)); + } + this.peer_waiters.clear(); + } + + private accept(socket: WsSocket) { + const fail = (message: string) => { + try { + socket.close(1008, message.slice(0, 120)); + } catch {} + }; + const timeout = setTimeout(() => fail("reverse hello timeout"), this.config.upstream_reversews_wait_timeout_ms); + socket.once("message", (buf: unknown) => { + clearTimeout(timeout); + let hello: ReverseHello; + try { + const parsed = JSON.parse(String(buf)); + if (parsed?.type !== "modcdp.reverse.hello") throw new Error("missing hello type"); + hello = parsed; + } catch (error) { + fail(`invalid reverse hello: ${error instanceof Error ? error.message : String(error)}`); + return; + } + if (this.socket && this.socket !== socket) { + try { + this.socket.close(1012, "reverse peer replaced"); + } catch {} + } + this.socket = socket; + this.peer_connected_at = Date.now(); + this.peer_info = hello; + socket.on("message", (data: unknown) => this.parseAndEmitRecv(data)); + socket.on("close", (code, reason) => { + if (this.socket !== socket) return; + this.socket = null; + this.peer_connected_at = null; + this.peer_info = null; + const suffix = code || reason.length ? ` (code=${code}, reason=${reason.toString()})` : ""; + this.emitClose(new Error(`Reverse ModCDP websocket closed${suffix}`)); + }); + socket.on("error", () => { + if (this.socket !== socket) return; + this.socket = null; + this.peer_connected_at = null; + this.peer_info = null; + this.emitClose(new Error("Reverse ModCDP websocket error")); + }); + for (const waiter of this.peer_waiters) { + if (waiter.connected_after_ms != null && this.peer_connected_at < waiter.connected_after_ms) continue; + clearTimeout(waiter.timeout); + waiter.resolve(); + this.peer_waiters.delete(waiter); + } + }); + } + + override toJSON() { + const json = super.toJSON(); + return { + ...json, + state: { + ...json.state, + connected: this.socket?.readyState === this.socket?.OPEN, + peer_connected_at: this.peer_connected_at, + peer_waiters: this.peer_waiters.size, + has_peer_info: this.peer_info != null, + }, + }; + } +} + +function endpointFromBind(bind: string) { + const { host, port } = parseHostPort(bind, "127.0.0.1", 29292); + return `ws://${host}:${port}`; +} + +export { + DEFAULT_UPSTREAM_REVERSEWS_BIND, + DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS, + ReverseWSUpstreamTransport, + ReverseWSUpstreamTransportConfigSchema, +}; +export type { ReverseWSUpstreamTransportConfig }; diff --git a/js/src/transport/ReverseWebSocketUpstreamTransport.ts b/js/src/transport/ReverseWebSocketUpstreamTransport.ts deleted file mode 100644 index 1425a52b..00000000 --- a/js/src/transport/ReverseWebSocketUpstreamTransport.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { CdpCommandMessage } from "../types/modcdp.js"; -import { parseHostPort, UpstreamTransport, type UpstreamTransportConfig } from "./UpstreamTransport.js"; - -export const DEFAULT_UPSTREAM_REVERSEWS_BIND = "127.0.0.1:29292"; -export const DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS = 10_000; - -type ReverseHello = { - type: "modcdp.reverse.hello"; - role?: string; - version?: number; - extension_id?: string | null; -}; - -export class ReverseWebSocketUpstreamTransport extends UpstreamTransport { - readonly mode = "reversews" as const; - readonly endpoint_kind = "modcdp_server" as const; - declare url: string; - private server: unknown = null; - private socket: { - readyState: number; - OPEN: number; - send: (data: string) => void; - close: (...args: unknown[]) => void; - } | null = null; - private peer_waiters = new Set<{ - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }>(); - peer_info: ReverseHello | null = null; - - private wait_timeout_ms: number; - - constructor({ - upstream_reversews_bind = DEFAULT_UPSTREAM_REVERSEWS_BIND, - upstream_reversews_wait_timeout_ms = DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS, - }: { - upstream_reversews_bind?: string | null; - upstream_reversews_wait_timeout_ms?: number | null; - } = {}) { - super(); - this.wait_timeout_ms = upstream_reversews_wait_timeout_ms ?? DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS; - this.setBind(upstream_reversews_bind ?? DEFAULT_UPSTREAM_REVERSEWS_BIND); - } - - update(config: UpstreamTransportConfig = {}) { - if (config.upstream_reversews_bind) this.setBind(config.upstream_reversews_bind); - if (typeof config.upstream_reversews_wait_timeout_ms === "number") - this.wait_timeout_ms = config.upstream_reversews_wait_timeout_ms; - return this; - } - - getInjectorConfig() { - return {}; - } - - private setBind(bind: string) { - const { host, port } = parseHostPort(bind, "127.0.0.1", 29292); - this.url = `ws://${host}:${port}`; - } - - async connect() { - const { WebSocketServer } = await import("ws"); - const { host, port } = parseHostPort(this.url, "127.0.0.1", 29292); - const server = new WebSocketServer({ host, port }); - this.server = server; - server.on("connection", (socket) => this.accept(socket)); - await new Promise((resolve, reject) => { - server.once("listening", () => resolve()); - server.once("error", reject); - }); - } - - send(message: CdpCommandMessage) { - if (!this.socket || this.socket.readyState !== this.socket.OPEN) { - throw new Error(`No reverse ModCDP extension peer is connected at ${this.url}.`); - } - this.socket.send(JSON.stringify(message)); - } - - async waitForPeer() { - if (this.socket && this.socket.readyState === this.socket.OPEN) return; - await new Promise((resolve, reject) => { - let waiter!: { - resolve: () => void; - reject: (error: Error) => void; - timeout: ReturnType; - }; - const timeout = setTimeout(() => { - this.peer_waiters.delete(waiter); - reject(new Error(`Timed out waiting ${this.wait_timeout_ms}ms for reverse ModCDP extension connection.`)); - }, this.wait_timeout_ms); - waiter = { resolve, reject, timeout }; - this.peer_waiters.add(waiter); - }); - } - - async close() { - try { - this.socket?.close(); - } catch {} - this.socket = null; - this.peer_info = null; - const server = this.server as { - close?: (callback: () => void) => void; - } | null; - if (server?.close) await new Promise((resolve) => server.close?.(() => resolve())); - this.server = null; - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.reject(new Error(`Reverse websocket transport at ${this.url} closed before a peer connected.`)); - } - this.peer_waiters.clear(); - } - - private accept(socket: any) { - const fail = (message: string) => { - try { - socket.close(1008, message.slice(0, 120)); - } catch {} - }; - const timeout = setTimeout(() => fail("reverse hello timeout"), this.wait_timeout_ms); - socket.once("message", (buf: unknown) => { - clearTimeout(timeout); - let hello: ReverseHello; - try { - const parsed = JSON.parse(String(buf)); - if (parsed?.type !== "modcdp.reverse.hello") throw new Error("missing hello type"); - hello = parsed; - } catch (error) { - fail(`invalid reverse hello: ${error instanceof Error ? error.message : String(error)}`); - return; - } - if (this.socket && this.socket !== socket) { - try { - this.socket.close(1012, "reverse peer replaced"); - } catch {} - } - this.socket = socket; - this.peer_info = hello; - socket.on("message", (data: unknown) => this.parseAndEmitRecv(data)); - socket.on("close", (code, reason) => { - if (this.socket !== socket) return; - this.socket = null; - this.peer_info = null; - const suffix = code || reason.length ? ` (code=${code}, reason=${reason.toString()})` : ""; - this.emitClose(new Error(`Reverse ModCDP websocket closed${suffix}`)); - }); - socket.on("error", () => { - if (this.socket !== socket) return; - this.socket = null; - this.peer_info = null; - this.emitClose(new Error("Reverse ModCDP websocket error")); - }); - for (const waiter of this.peer_waiters) { - clearTimeout(waiter.timeout); - waiter.resolve(); - } - this.peer_waiters.clear(); - }); - } -} diff --git a/js/src/transport/UpstreamTransport.ts b/js/src/transport/UpstreamTransport.ts index 788df9ed..84fd997c 100644 --- a/js/src/transport/UpstreamTransport.ts +++ b/js/src/transport/UpstreamTransport.ts @@ -1,60 +1,198 @@ -import type { CdpCommandMessage, CdpEventMessage, CdpResponseMessage } from "../types/modcdp.js"; -import type { ModCDPServerOptions } from "../types/modcdp.js"; -import type { BrowserLaunchOptions } from "../launcher/BrowserLauncher.js"; -import type { ExtensionInjectorConfig } from "../injector/ExtensionInjector.js"; -import { CdpEventMessageSchema, CdpResponseMessageSchema } from "../types/modcdp.js"; - -export type UpstreamMode = "ws" | "pipe" | "nativemessaging" | "reversews" | "nats"; -export type UpstreamEndpointKind = "raw_cdp" | "modcdp_server"; -export type UpstreamTransportConfig = { - cdp_url?: string | null; - user_data_dir?: string | null; - pipe_read?: NodeJS.ReadableStream | null; - pipe_write?: NodeJS.WritableStream | null; - upstream_nativemessaging_manifest?: string | null; - upstream_nativemessaging_manifests?: string[] | null; - upstream_nativemessaging_host_name?: string | null; - upstream_nativemessaging_wait_timeout_ms?: number | null; - injector_extension_id?: string | null; - upstream_nats_url?: string | null; - upstream_nats_subject_prefix?: string | null; - upstream_nats_role?: string | null; - upstream_nats_wait_timeout_ms?: number | null; - upstream_reversews_bind?: string | null; - upstream_reversews_wait_timeout_ms?: number | null; +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/transport/UpstreamTransport.py +// - ./go/modcdp/transport/UpstreamTransport.go +import type { z } from "zod"; +import type { LauncherConfig } from "../launcher/BrowserLauncher.js"; +import type { cdp } from "../types/generated/cdp.js"; +import type { CdpCommandSchema, CdpNamedSchema } from "../types/generated/zod/helpers.js"; +import * as Target from "../types/generated/zod/Target.js"; +import type { + CdpCommandMessage, + CdpDebuggeeCommandParams, + CdpEventMessage, + CdpResponseMessage, + ModCDPUpstreamConfig, + ProtocolPayload, + ProtocolResult, +} from "../types/modcdp.js"; +import { CdpEventMessageSchema, CdpResponseMessageSchema, ModCDPUpstreamConfigSchema } from "../types/modcdp.js"; +import { modCDPToJSON } from "../types/toJSON.js"; + +type UpstreamMode = "ws"; +type UpstreamTransportConfig = z.input; +type UpstreamTransportBaseConfig = { + upstream_mode: string; + upstream_cdp_send_timeout_ms: number; + upstream_ws_cdp_url?: string; + upstream_ws_connect_error_settle_timeout_ms?: number; +} & Record; + +type TargetRoute = { + targetId: cdp.types.ts.Target.TargetID; + sessionId?: cdp.types.ts.Target.SessionID | null; }; +type UpstreamPeerWaitConfig = { connected_after_ms?: number | null }; +type UpstreamPeerKind = "browser_cdp" | "modcdp_server"; + +type UpstreamEventListener = ( + payload: ProtocolPayload, + targetId: cdp.types.ts.Target.TargetID | null, + sessionId: cdp.types.ts.Target.SessionID | null, +) => void; -export class UpstreamTransport { - readonly mode: UpstreamMode = "ws"; - readonly endpoint_kind: UpstreamEndpointKind = "raw_cdp"; - url?: string; +class UpstreamTransport { + config: UpstreamTransportBaseConfig; + // The kind of remote peer this client-side transport talks to. Most + // transports talk to raw browser CDP. Reverse client transports talk to a + // ModCDPServer downstream connection and therefore do not use the local + // AutoSessionRouter bootstrap path. + peer_kind: UpstreamPeerKind = "browser_cdp"; + private next_id = 1; + private pending = new Map< + number, + { + method: string; + resolve: (value: ProtocolResult) => void; + reject: (error: Error) => void; + timeout: ReturnType | null; + } + >(); private recv_listeners = new Set<(message: CdpResponseMessage | CdpEventMessage) => void>(); private close_listeners = new Set<(error: Error) => void>(); + private event_listeners = new Map, Set>(); + + constructor(config: UpstreamTransportConfig = {}) { + this.config = ModCDPUpstreamConfigSchema.parse(config); + } async connect() { throw new Error(`${this.constructor.name}.connect is not implemented.`); } - update(_config: UpstreamTransportConfig = {}) { + update(config: UpstreamTransportConfig | Record = {}) { + this.config = ModCDPUpstreamConfigSchema.parse({ ...this.config, ...config }); return this; } - getLauncherConfig(): BrowserLaunchOptions { + configForLauncher(): LauncherConfig { return {}; } - getInjectorConfig(): ExtensionInjectorConfig { - return {}; + async close() {} + + send(message: CdpCommandMessage): void; + send( + method: string, + params?: ProtocolPayload, + sessionId?: cdp.types.ts.Target.SessionID | null, + config?: { timeout_ms?: number | null }, + ): Promise; + send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | cdp.types.ts.Target.SessionID | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + throw new Error(`${this.constructor.name}.send is not implemented.`); + } + if (typeof command === "string") { + const method = command; + const sessionId = typeof route_or_sessionId === "string" ? route_or_sessionId : null; + const timeout_ms = config.timeout_ms ?? this.config.upstream_cdp_send_timeout_ms; + const id = this.next_id++; + const message: CdpCommandMessage = { + id, + method, + params: params as ProtocolPayload, + }; + if (sessionId) message.sessionId = sessionId; + return new Promise((resolve, reject) => { + const timeout = + timeout_ms != null && timeout_ms > 0 + ? setTimeout(() => { + if (!this.pending.delete(id)) return; + reject(new Error(`${method} timed out after ${timeout_ms}ms`)); + }, timeout_ms) + : null; + this.pending.set(id, { method, resolve, reject, timeout }); + try { + this.send(message); + } catch (error) { + const pending = this.pending.get(id); + if (!pending) return; + this.pending.delete(id); + if (pending.timeout) clearTimeout(pending.timeout); + reject(error instanceof Error ? error : new Error(String(error))); + } + }); + } + if (typeof route_or_sessionId === "string") + return this.send(command.id, command.params.parse(params), route_or_sessionId).then((result) => + command.result.parse(result), + ); + const route = route_or_sessionId && typeof route_or_sessionId === "object" ? route_or_sessionId : undefined; + if (route && route.sessionId == null) throw new Error(`No CDP session is attached for targetId=${route.targetId}.`); + return this.send(command.id, command.params.parse(params), route?.sessionId ?? null).then((result) => + command.result.parse(result), + ); } - getServerConfig(): Partial { - return {}; + on>( + event: Event, + listener: ( + payload: z.output, + targetId: cdp.types.ts.Target.TargetID | null, + sessionId: cdp.types.ts.Target.SessionID | null, + ) => void, + ) { + const typed_listener: UpstreamEventListener = (payload, targetId, sessionId) => { + listener(event.parse(payload), targetId, sessionId); + }; + const listeners = this.event_listeners.get(event); + if (listeners) listeners.add(typed_listener); + else this.event_listeners.set(event, new Set([typed_listener])); + return { + remove: () => { + const current_listeners = this.event_listeners.get(event); + current_listeners?.delete(typed_listener); + if (current_listeners?.size === 0) this.event_listeners.delete(event); + }, + }; } - async close() {} + async getTargets() { + return (await this.send(Target.GetTargetsCommand, {})).targetInfos; + } - send(_message: CdpCommandMessage) { - throw new Error(`${this.constructor.name}.send is not implemented.`); + async resolveTargetId(params: CdpDebuggeeCommandParams) { + return typeof params.targetId === "string" && params.targetId.length > 0 ? params.targetId : null; + } + + async createTarget(url: string) { + return (await this.send(Target.CreateTargetCommand, { url })).targetId; + } + + async attachToTarget(targetId: cdp.types.ts.Target.TargetID) { + return (await this.send(Target.AttachToTargetCommand, { targetId, flatten: true })).sessionId; + } + + async detachFromTarget(sessionId: cdp.types.ts.Target.SessionID) { + await this.send(Target.DetachFromTargetCommand, { sessionId }); } onRecv(listener: (message: CdpResponseMessage | CdpEventMessage) => void) { @@ -72,20 +210,62 @@ export class UpstreamTransport { } protected emitClose(error: Error) { + for (const pending of this.pending.values()) { + if (pending.timeout) clearTimeout(pending.timeout); + pending.reject(error); + } + this.pending.clear(); for (const listener of this.close_listeners) listener(error); } protected parseAndEmitRecv(data: unknown) { - try { - const parsed = JSON.parse(typeof data === "string" ? data : String(data)); - this.emitRecv("id" in parsed ? CdpResponseMessageSchema.parse(parsed) : CdpEventMessageSchema.parse(parsed)); - } catch {} + const parsed = JSON.parse(typeof data === "string" ? data : String(data)); + if ("id" in parsed) { + const response = CdpResponseMessageSchema.parse(parsed); + const pending = this.pending.get(response.id); + if (pending) { + this.pending.delete(response.id); + if (pending.timeout) clearTimeout(pending.timeout); + if (response.error) pending.reject(new Error(response.error.message)); + else pending.resolve((response.result ?? {}) as ProtocolResult); + } + this.emitRecv(response); + return; + } + const event = CdpEventMessageSchema.parse(parsed); + const payload = (event.params ?? {}) as ProtocolPayload; + this.emitUpstreamEvent(event.method, payload, null, event.sessionId ?? null); + this.emitRecv(event); } - async waitForPeer() {} + protected emitUpstreamEvent( + method: string, + payload: ProtocolPayload, + targetId: cdp.types.ts.Target.TargetID | null, + sessionId: cdp.types.ts.Target.SessionID | null, + ) { + for (const [upstream_event, listeners] of this.event_listeners) { + if (upstream_event.id !== method) continue; + for (const listener of listeners) listener(payload, targetId, sessionId); + } + } + + async waitForPeer(_config: UpstreamPeerWaitConfig = {}) {} + + toJSON() { + return modCDPToJSON(this, { + config: this.config, + state: { + pending: this.pending.size, + recv_listeners: this.recv_listeners.size, + close_listeners: this.close_listeners.size, + event_listeners: this.event_listeners.size, + }, + }); + } } -export function parseHostPort(value: string, defaultHost: string, defaultPort: number) { +function parseHostPort(value: string, defaultHost: string, defaultPort: number) { const parsed = new URL(/^[a-z][a-z\d+\-.]*:\/\//i.test(value) ? value : `ws://${value}`); const host = parsed.hostname || defaultHost; const port = Number(parsed.port || defaultPort); @@ -93,6 +273,13 @@ export function parseHostPort(value: string, defaultHost: string, defaultPort: n return { host, port }; } -export function endpointKindForUpstream(mode: UpstreamMode): UpstreamEndpointKind { - return mode === "ws" || mode === "pipe" ? "raw_cdp" : "modcdp_server"; -} +export { UpstreamTransport, parseHostPort }; +export type { + UpstreamMode, + UpstreamTransportBaseConfig, + UpstreamTransportConfig, + TargetRoute, + UpstreamPeerWaitConfig, + UpstreamPeerKind, + UpstreamEventListener, +}; diff --git a/js/src/transport/WSUpstreamTransport.ts b/js/src/transport/WSUpstreamTransport.ts new file mode 100644 index 00000000..c95a092a --- /dev/null +++ b/js/src/transport/WSUpstreamTransport.ts @@ -0,0 +1,133 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/transport/WSUpstreamTransport.py +// - ./go/modcdp/transport/WSUpstreamTransport.go +import { resolveCdpWebSocketUrl } from "../launcher/BrowserLauncher.js"; +import type { z } from "zod"; +import type { CdpCommandSchema } from "../types/generated/zod/helpers.js"; +import type { CdpCommandMessage, ProtocolPayload, ProtocolResult } from "../types/modcdp.js"; +import { DEFAULT_UPSTREAM_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS } from "../types/modcdp.js"; +import { UpstreamTransport, type TargetRoute, type UpstreamTransportConfig } from "./UpstreamTransport.js"; + +class WSUpstreamTransport extends UpstreamTransport { + ws: WebSocket | null = null; + private connect_promise: Promise | null = null; + + constructor(config: UpstreamTransportConfig = {}) { + super({ ...config, upstream_mode: "ws" }); + } + + override send(message: CdpCommandMessage): void; + override send( + method: string, + params?: ProtocolPayload, + sessionId?: string | null, + config?: { timeout_ms?: number | null }, + ): Promise; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandSchema, + params?: z.input, + route?: TargetRoute | string | null, + ): Promise>; + override send< + Params extends z.ZodType>, + Result extends z.ZodType>, + Name extends string, + >( + command: CdpCommandMessage | string | CdpCommandSchema, + params: ProtocolPayload | z.input = {}, + route_or_sessionId: TargetRoute | string | null = null, + config: { timeout_ms?: number | null } = {}, + ): void | Promise | Promise> { + if (typeof command !== "string" && "method" in command) { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error("CDP websocket is not connected."); + this.ws.send(JSON.stringify(command)); + return; + } + if (typeof command === "string") { + return this.connect().then( + () => + super.send( + command, + params as ProtocolPayload, + typeof route_or_sessionId === "string" ? route_or_sessionId : null, + config, + ) as Promise, + ); + } + return this.connect().then( + () => super.send(command, params as z.input, route_or_sessionId) as Promise>, + ); + } + + update(config: UpstreamTransportConfig = {}) { + super.update(config); + return this; + } + + async connect() { + if (this.ws?.readyState === WebSocket.OPEN) return; + if (this.connect_promise) return await this.connect_promise; + if (!this.config.upstream_ws_cdp_url) + throw new Error("WSUpstreamTransport requires upstream_ws_cdp_url or launcher-provided cdp_url."); + this.connect_promise = (async () => { + // upstream_ws_cdp_url may start as an HTTP discovery endpoint; from here on it is the resolved WebSocket CDP endpoint. + const upstream_ws_cdp_url = await resolveCdpWebSocketUrl(this.config.upstream_ws_cdp_url, "upstream_ws_cdp_url"); + this.update({ upstream_ws_cdp_url }); + const ws = new WebSocket(upstream_ws_cdp_url); + this.ws = ws; + ws.addEventListener("message", (event) => this.parseAndEmitRecv(event.data)); + ws.addEventListener("close", () => { + if (this.ws === ws) this.ws = null; + this.connect_promise = null; + this.emitClose(new Error("CDP websocket closed")); + }); + ws.addEventListener("error", () => { + if (this.ws === ws) this.ws = null; + this.connect_promise = null; + this.emitClose(new Error("CDP websocket error")); + }); + await new Promise((resolve, reject) => { + const cleanup = () => { + ws.removeEventListener("open", onOpen); + ws.removeEventListener("error", onError); + }; + const onOpen = () => { + cleanup(); + resolve(); + }; + const onError = () => { + cleanup(); + reject(new Error("CDP websocket error")); + }; + ws.addEventListener("open", onOpen); + ws.addEventListener("error", onError); + }); + })(); + try { + await this.connect_promise; + } catch (error) { + this.connect_promise = null; + throw error; + } + } + + async close() { + try { + this.ws?.close(); + } catch {} + this.ws = null; + this.connect_promise = null; + } + + override toJSON() { + const json = super.toJSON(); + return { ...json, state: { ...json.state, connected: this.ws?.readyState === WebSocket.OPEN } }; + } +} + +export { DEFAULT_UPSTREAM_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS, WSUpstreamTransport }; diff --git a/js/src/transport/WebSocketUpstreamTransport.ts b/js/src/transport/WebSocketUpstreamTransport.ts deleted file mode 100644 index 81f37bdd..00000000 --- a/js/src/transport/WebSocketUpstreamTransport.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { CdpCommandMessage } from "../types/modcdp.js"; -import { resolveCdpWebSocketUrl } from "../launcher/BrowserLauncher.js"; -import { UpstreamTransport, type UpstreamTransportConfig } from "./UpstreamTransport.js"; - -export class WebSocketUpstreamTransport extends UpstreamTransport { - readonly mode = "ws" as const; - readonly endpoint_kind = "raw_cdp" as const; - ws: WebSocket | null = null; - - constructor({ cdp_url = null }: { cdp_url?: string | null } = {}) { - super(); - this.url = cdp_url ?? ""; - } - - update(config: UpstreamTransportConfig = {}) { - if (config.cdp_url) this.url = config.cdp_url; - return this; - } - - getServerConfig() { - return this.url ? { server_loopback_cdp_url: this.url } : {}; - } - - async connect() { - if (!this.url) throw new Error("upstream.upstream_mode=ws requires upstream_cdp_url or launcher-provided cdp_url."); - // cdp_url may start as an HTTP discovery endpoint; from here on it is the resolved WebSocket CDP endpoint. - this.url = await resolveCdpWebSocketUrl(this.url, "upstream_cdp_url"); - const ws = new WebSocket(this.url); - this.ws = ws; - ws.addEventListener("message", (event) => this.parseAndEmitRecv(event.data)); - ws.addEventListener("close", () => this.emitClose(new Error("CDP websocket closed"))); - ws.addEventListener("error", () => this.emitClose(new Error("CDP websocket error"))); - await new Promise((resolve, reject) => { - const cleanup = () => { - ws.removeEventListener("open", onOpen); - ws.removeEventListener("error", onError); - }; - const onOpen = () => { - cleanup(); - resolve(); - }; - const onError = () => { - cleanup(); - reject(new Error("CDP websocket error")); - }; - ws.addEventListener("open", onOpen); - ws.addEventListener("error", onError); - }); - } - - send(message: CdpCommandMessage) { - if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error("CDP websocket is not connected."); - this.ws.send(JSON.stringify(message)); - } - - async close() { - try { - this.ws?.close(); - } catch {} - this.ws = null; - } -} diff --git a/js/src/types/CDPTypes.ts b/js/src/types/CDPTypes.ts new file mode 100644 index 00000000..0ede5c7b --- /dev/null +++ b/js/src/types/CDPTypes.ts @@ -0,0 +1,763 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/client/ModCDPClient.py +// - ./go/modcdp/client/ModCDPClient.go +import { z } from "zod"; + +import { + createCdpAliases, + type CdpCommandAliases, + type CdpCommandMap, + type CdpCommandSpec, + type CdpEventMap, + type CdpEventPayloads, + type CdpEventSpec, +} from "./generated/aliases.js"; +import { + commands as nativeCommandSchemas, + events as nativeEventSchemas, + types as runtimeTypes, +} from "./generated/zod.js"; +import * as Runtime from "./generated/zod/Runtime.js"; +import type { CdpCommandSchema } from "./generated/zod/helpers.js"; +import { + type ModCDPAddCustomCommandParams, + type ModCDPAddCustomEventObjectParams, + type ModCDPAddMiddlewareParams, + type ModCDPNamedValue, + type ModCDPPayloadSchemaSpec, + type ProtocolEventParams, + type ProtocolParams, + type ProtocolResult, + type TranslatedStep, + Mod, + normalizeModCDPName, + validateZodSchema, +} from "./modcdp.js"; +import { modCDPToJSON } from "./toJSON.js"; + +type CDPCommandSpec< + TParamsSchema extends z.ZodType = z.ZodType, + TResultSchema extends z.ZodType = z.ZodType, +> = CdpCommandSpec; +type CDPEventSpec = CdpEventSpec; +type CDPCommandMap = CdpCommandMap; +type CDPEventMap = CdpEventMap; +type CDPTypesCustomCommands = + | ModCDPAddCustomCommandParams[] + | { + [TName in keyof TCommands]: TCommands[TName]; + }; +type CDPTypesCustomEvents = + | ModCDPAddCustomEventObjectParams[] + | { + [TName in keyof TEvents]: TEvents[TName]; + }; +type CDPTypesConfig = { + custom_commands?: CDPTypesCustomCommands; + custom_events?: CDPTypesCustomEvents; + custom_middlewares?: ModCDPAddMiddlewareParams[]; +}; +type CDPTypesCommandRegistration = ModCDPAddCustomCommandParams & { + params_schema?: z.ZodType | null; + result_schema?: z.ZodType | null; +}; +type CDPTypesEventRegistration = ModCDPAddCustomEventObjectParams & { + event_schema?: z.ZodType | null; +}; +type CDPAliasSend = (method: string, params?: unknown) => Promise; +type CDPEventNameInput = string | symbol | (z.ZodType & ModCDPNamedValue); +type CDPEventPayload = TEvent extends z.ZodType ? TPayload : never; +type ProtocolCommandSchema = { + params: z.ZodType; + result: z.ZodType; +}; +type ProtocolEventSchema = z.ZodType; +type CommandPreparation = { + params: ProtocolParams; + local_result: ProtocolResult | null; + custom_command_name: string | null; +}; +type CDPAliasBinding = { + target: object; + send: CDPAliasSend; +}; +type CDPCommandAliases = CdpCommandAliases; +type CDPEventMapPayloads = CdpEventPayloads; +type ServiceWorkerExpressionBuilder = (params: ProtocolParams, cdpSessionId: string | null) => string; + +const DEFAULT_BUILTIN_COMMANDS: ReadonlyArray = [ + { + name: "Mod.ping", + params_schema: Mod.PingParams, + result_schema: Mod.PingResponse, + expression: ` + async (params) => { + const received_at = Date.now(); + const message = { + method: "Mod.pong", + params: { + sent_at: + typeof params.sent_at === "number" + ? params.sent_at + : received_at, + received_at, + from: "extension-service-worker", + }, + }; + if (cdpSessionId) message.sessionId = cdpSessionId; + downstream.sendEvent(message); + return { ok: true }; + } + `, + }, + { + name: "Mod.configure", + params_schema: Mod.ConfigureParams, + result_schema: Mod.ConfigureResponse, + expression: `async (params) => { await ModCDP.configure(params); return params; }`, + }, + { + name: "Mod.evaluate", + params_schema: Mod.EvaluateParams, + result_schema: Mod.EvaluateResponse, + expression: ` + async ({ expression, params = {}, cdpSessionId = null }) => + ModCDP.evaluateInServiceWorker({ expression, params, cdpSessionId }) + `, + }, + { + name: "Mod.getTopology", + params_schema: Mod.GetTopologyParams, + result_schema: Mod.GetTopologyResponse, + expression: `async (params) => ModCDP.client.router.getTopology(params)`, + }, + { + name: "Mod.addCustomCommand", + params_schema: Mod.AddCustomCommandParams, + result_schema: Mod.AddCustomCommandResponse, + expression: `async (params) => ModCDP.addCustomCommand(params)`, + }, + { + name: "Mod.addCustomEvent", + params_schema: Mod.AddCustomEventObjectParams, + result_schema: Mod.AddCustomEventResponse, + expression: `async (params) => ModCDP.addCustomEvent(params)`, + }, + { + name: "Mod.addMiddleware", + params_schema: Mod.AddMiddlewareParams, + result_schema: Mod.AddMiddlewareResponse, + expression: `async (params) => ModCDP.addMiddleware(params)`, + }, +]; + +const DEFAULT_BUILTIN_EVENTS: ReadonlyArray = [ + { name: "Mod.pong", event_schema: Mod.PongEvent }, +]; + +function hasCommandExpression( + command: ModCDPAddCustomCommandParams, +): command is ModCDPAddCustomCommandParams & { expression: string } { + return typeof command.expression === "string" && command.expression.length > 0; +} + +const wire_omit_schema = Symbol("wire_omit_schema"); + +function sanitizeSerializableJsonSchema(value: unknown): unknown { + if (Array.isArray(value)) return value.map((item) => sanitizeSerializableJsonSchema(item)); + if (value == null || typeof value !== "object") return value; + const source = value as Record; + if (source.modcdp_wire === "omit") return wire_omit_schema; + const sanitized: Record = {}; + const omitted_properties = new Set(); + const properties = + source.properties != null && typeof source.properties === "object" && !Array.isArray(source.properties) + ? (source.properties as Record) + : null; + if (properties) { + const sanitized_properties: Record = {}; + for (const [property_name, property_schema] of Object.entries(properties)) { + const sanitized_property_schema = sanitizeSerializableJsonSchema(property_schema); + if (sanitized_property_schema === wire_omit_schema) { + omitted_properties.add(property_name); + } else { + sanitized_properties[property_name] = sanitized_property_schema; + } + } + sanitized.properties = sanitized_properties; + } + for (const [key, child_value] of Object.entries(source)) { + if (key === "modcdp_wire" || key === "properties") continue; + if (key === "required" && Array.isArray(child_value)) { + const required = child_value.filter( + (property_name): property_name is string => + typeof property_name === "string" && !omitted_properties.has(property_name), + ); + if (required.length > 0) sanitized.required = required; + continue; + } + const sanitized_child_value = sanitizeSerializableJsonSchema(child_value); + if (sanitized_child_value !== wire_omit_schema) sanitized[key] = sanitized_child_value; + } + return sanitized; +} + +function serializablePayloadSchema(schema: ModCDPPayloadSchemaSpec | null | undefined) { + if (!schema) return null; + const normalized_schema = validateZodSchema(schema); + if (!normalized_schema) return null; + const json_schema = z.toJSONSchema(normalized_schema, { unrepresentable: "any" }); + return sanitizeSerializableJsonSchema(json_schema) as ModCDPPayloadSchemaSpec; +} + +/** + * Protocol type registry for native CDP, ModCDP, and user-provided command/event + * schemas. CDPTypes owns shape metadata, Zod runtime validation, JSON-schema + * normalization, custom type registration, and optional alias installation over + * a caller-provided send function. It does not own transport, browser state, + * routing, command execution, middleware execution, or event delivery. + */ +class CDPTypes { + readonly types = runtimeTypes; + readonly commands = nativeCommandSchemas; + readonly events = nativeEventSchemas; + readonly custom_commands: Map; + readonly custom_events: Map; + readonly custom_middlewares: ModCDPAddMiddlewareParams[]; + readonly event_schemas = new Map(); + readonly command_params_schemas = new Map(); + readonly command_result_schemas = new Map(); + readonly service_worker_expression_builders = new Map(); + private readonly alias_bindings: CDPAliasBinding[] = []; + private readonly alias_targets = new WeakSet(); + + constructor(config: CDPTypesConfig = {}) { + this.custom_commands = new Map(); + this.custom_events = new Map(); + this.custom_middlewares = []; + this.hydrateBuiltinSchemas(); + for (const command of DEFAULT_BUILTIN_COMMANDS) this.addCustomCommand(command); + for (const event of DEFAULT_BUILTIN_EVENTS) this.addCustomEvent(event); + this.registerCustomCommands(config.custom_commands ?? []); + this.registerCustomEvents(config.custom_events ?? []); + for (const middleware of config.custom_middlewares ?? []) this.addCustomMiddleware(middleware); + this.service_worker_expression_builders.set("Mod.evaluate", (params) => { + const parsed = Mod.EvaluateParams.parse(params); + return ` + async ({ params = {}, cdpSessionId = null }) => { + const value = (${parsed.expression}); + return typeof value === "function" ? await value(params) : value; + } + `; + }); + } + + update( + config: CDPTypesConfig, + ): CDPTypes { + const updated = new CDPTypes({ + custom_commands: [...this.custom_commands.values(), ...this.customCommandEntries(config.custom_commands ?? [])], + custom_events: [...this.custom_events.values(), ...this.customEventEntries(config.custom_events ?? [])], + custom_middlewares: [...this.custom_middlewares, ...(config.custom_middlewares ?? [])], + }); + for (const binding of this.alias_bindings) updated.installAliases(binding.target, binding.send); + return updated; + } + + nativeCommandSchema(method: string) { + return (nativeCommandSchemas as Record)[method] ?? null; + } + + commandParamsSchema(method: string) { + return this.command_params_schemas.get(method) ?? null; + } + + commandResultSchema(method: string) { + return this.command_result_schemas.get(method) ?? null; + } + + eventPayloadSchema(event_name: string) { + return this.event_schemas.get(event_name) ?? null; + } + + normalizeEventName(event_name: CDPEventNameInput) { + if (typeof event_name !== "string" && typeof event_name !== "symbol") { + const name = normalizeModCDPName(event_name); + this.event_schemas.set(name, event_name); + return name; + } + return typeof event_name === "symbol" ? event_name : normalizeModCDPName(event_name); + } + + prepareCommand(method: string, params: unknown = {}, can_register_locally = false): CommandPreparation { + let command_params = this.parseCommandParams(method, params); + if (method === "Mod.addCustomCommand") { + const parsed = Mod.AddCustomCommandParams.parse(command_params); + const name = this.addCustomCommand(parsed); + if (!parsed.expression && can_register_locally) + return { + params: command_params, + local_result: { name, registered: true }, + custom_command_name: name, + }; + const params_schema = serializablePayloadSchema(parsed.params_schema); + const result_schema = serializablePayloadSchema(parsed.result_schema); + command_params = this.customCommandWireRegistration(name) ?? { + ...parsed, + name, + ...(params_schema == null ? {} : { params_schema }), + ...(result_schema == null ? {} : { result_schema }), + }; + } else if (method === "Mod.addCustomEvent") { + const parsed = Mod.AddCustomEventObjectParams.parse(params ?? {}); + const name = this.addCustomEvent(parsed); + if (can_register_locally) + return { + params: command_params, + local_result: { name, registered: true }, + custom_command_name: null, + }; + const event_schema = serializablePayloadSchema(parsed.event_schema); + command_params = this.customEventWireRegistration(name) ?? { + ...parsed, + name, + ...(event_schema == null ? {} : { event_schema }), + }; + } else if (method === "Mod.addMiddleware") { + const parsed = Mod.AddMiddlewareParams.parse(command_params); + this.addCustomMiddleware(parsed); + if (can_register_locally) + return { + params: command_params, + local_result: { + name: parsed.name == null ? "*" : normalizeModCDPName(parsed.name), + phase: parsed.phase, + registered: true, + }, + custom_command_name: null, + }; + } + return { + params: command_params, + local_result: null, + custom_command_name: + method === "Mod.addCustomCommand" + ? normalizeModCDPName(Mod.AddCustomCommandParams.parse(command_params).name) + : null, + }; + } + + parseCommandParams(method: string, params: unknown = {}) { + return (this.command_params_schemas.get(method)?.parse(params ?? {}) ?? params ?? {}) as ProtocolParams; + } + + parseCommandResult(method: string, result: unknown): ProtocolResult { + const result_schema = this.command_result_schemas.get(method); + return (result_schema ? result_schema.parse(result) : (result ?? {})) as ProtocolResult; + } + + parseEventPayload(event_name: string, payload: unknown = {}) { + return (this.event_schemas.get(event_name)?.parse(payload ?? {}) ?? payload ?? {}) as ProtocolEventParams; + } + + serviceWorkerCommandStep( + method: string, + params: ProtocolParams = {}, + cdpSessionId: string | null = null, + execution_context_id: number | null = null, + ): TranslatedStep { + const command = this.custom_commands.get(method); + if (command && hasCommandExpression(command)) { + const command_expression = + this.service_worker_expression_builders.get(method)?.(params, cdpSessionId) ?? command.expression; + return { + method: Runtime.EvaluateCommand.id, + params: { + expression: this.serviceWorkerRuntimeExpression(method, params, cdpSessionId, command_expression), + awaitPromise: true, + returnByValue: true, + ...(execution_context_id == null ? {} : { contextId: execution_context_id }), + }, + unwrap: "runtime", + }; + } + return { + method: Runtime.CallFunctionOnCommand.id, + params: { + functionDeclaration: + "async function(method, paramsJson, cdpSessionId) { return JSON.stringify(await globalThis.ModCDP.handleCommand(method, JSON.parse(paramsJson), cdpSessionId)); }", + arguments: [{ value: method }, { value: JSON.stringify(params) }, { value: cdpSessionId }], + awaitPromise: true, + returnByValue: true, + ...(execution_context_id == null ? {} : { executionContextId: execution_context_id }), + }, + unwrap: "runtime_json", + }; + } + + toJSON() { + return modCDPToJSON(this, { + config: { + custom_commands: this.customCommandWireRegistrations().map( + ({ expression: _expression, ...command }) => command, + ), + custom_events: this.customEventWireRegistrations(), + custom_middlewares: this.customMiddlewareWireRegistrations().map( + ({ expression: _expression, ...middleware }) => middleware, + ), + }, + state: { + custom_commands: this.custom_commands.size, + custom_events: this.custom_events.size, + custom_middlewares: this.custom_middlewares.length, + command_params_schemas: this.command_params_schemas.size, + command_result_schemas: this.command_result_schemas.size, + event_schemas: this.event_schemas.size, + }, + }); + } + + addCustomCommand(registration: ModCDPAddCustomCommandParams) { + const parsed = Mod.AddCustomCommandParams.parse(registration); + const name = normalizeModCDPName(parsed.name); + if (!/^[^.]+\.[^.]+$/.test(name)) throw new Error("name must be in Domain.method form."); + const params_schema = validateZodSchema(parsed.params_schema); + const result_schema = validateZodSchema(parsed.result_schema); + if (params_schema) this.command_params_schemas.set(name, params_schema); + if (result_schema) this.command_result_schemas.set(name, result_schema); + this.upsertCustomCommand({ + ...parsed, + name, + params_schema: params_schema ?? null, + result_schema: result_schema ?? null, + }); + return name; + } + + customCommandWireRegistrations({ expression_required = false }: { expression_required?: boolean } = {}) { + return [...this.custom_commands.values()] + .filter((command) => !expression_required || hasCommandExpression(command)) + .map((command) => { + const params_schema = serializablePayloadSchema(command.params_schema); + const result_schema = serializablePayloadSchema(command.result_schema); + return { + name: normalizeModCDPName(command.name), + expression: command.expression ?? null, + ...(params_schema == null ? {} : { params_schema }), + ...(result_schema == null ? {} : { result_schema }), + }; + }); + } + + customEventWireRegistrations() { + return [...this.custom_events.values()].map((event) => { + const event_schema = serializablePayloadSchema(event.event_schema); + return { + name: normalizeModCDPName(event.name), + ...(event_schema == null ? {} : { event_schema }), + }; + }); + } + + addCustomMiddleware(registration: ModCDPAddMiddlewareParams) { + const parsed = Mod.AddMiddlewareParams.parse(registration); + const name = parsed.name == null ? "*" : normalizeModCDPName(parsed.name); + if (name !== "*" && !name.includes(".")) throw new Error("name must be '*' or Domain.name form."); + this.custom_middlewares.push({ + ...parsed, + ...(name === "*" ? {} : { name }), + }); + return name; + } + + customMiddlewareWireRegistrations() { + return this.custom_middlewares.map(({ name, phase, expression }) => ({ + ...(name == null ? {} : { name: normalizeModCDPName(name) }), + phase, + expression, + })); + } + + customMiddlewareRegistrations(phase: "request" | "response" | "event", name: string) { + return this.custom_middlewares.filter((middleware) => { + const middleware_name = middleware.name == null ? "*" : normalizeModCDPName(middleware.name); + return middleware.phase === phase && (middleware_name === "*" || middleware_name === name); + }); + } + + addCustomEvent(registration: ModCDPAddCustomEventObjectParams) { + const parsed = Mod.AddCustomEventObjectParams.parse(registration); + const name = normalizeModCDPName(parsed.name); + if (!/^[^.]+\.[^.]+$/.test(name)) throw new Error("name must be in Domain.event form."); + const event_schema = validateZodSchema(parsed.event_schema); + if (event_schema) this.event_schemas.set(name, event_schema); + this.custom_events.set(name, { + ...parsed, + name, + event_schema: event_schema ?? null, + }); + return name; + } + + installAliases(target: object, send: CDPAliasSend) { + if (!this.alias_bindings.some((binding) => binding.target === target)) this.alias_bindings.push({ target, send }); + if (this.alias_targets.has(target)) return; + const { types: _runtime_types, ...aliases } = createCdpAliases(send, { + onCustomCommand: (name, params_schema, result_schema) => { + if (!this.custom_commands.has(name)) + this.addCustomCommand({ + name, + params_schema: params_schema ?? null, + result_schema: result_schema ?? null, + }); + this.installCustomCommandAlias(target, name, send); + }, + onCustomEvent: (name, event_schema) => { + if (!this.custom_events.has(name)) this.addCustomEvent({ name, event_schema: event_schema ?? null }); + }, + }); + Object.assign(target, aliases); + for (const command of this.custom_commands.values()) + this.installCustomCommandAlias(target, normalizeModCDPName(command.name), send); + this.alias_targets.add(target); + } + + installCustomCommandAlias(target: object, name: string, send: CDPAliasSend) { + const parts = name.split("."); + if (parts.length !== 2 || !parts[0] || !parts[1]) + throw new Error(`Custom command must use Domain.method format, got ${name}`); + const [domain, method] = parts; + if (method === "*") { + const existing_domain = Reflect.get(target, domain); + const domain_target = existing_domain != null && typeof existing_domain === "object" ? existing_domain : {}; + Reflect.set( + target, + domain, + new Proxy(domain_target, { + get(existing, property, receiver) { + if (typeof property !== "string") return Reflect.get(existing, property, receiver); + if (property in existing) return Reflect.get(existing, property, receiver); + const command_name = `${domain}.${property}`; + const alias = (params?: unknown) => send(command_name, params ?? {}); + Object.defineProperties(alias, { + cdp_command_name: { + value: command_name, + enumerable: true, + configurable: true, + }, + id: { value: command_name, enumerable: true, configurable: true }, + name: { value: command_name, configurable: true }, + kind: { value: "command", enumerable: true, configurable: true }, + meta: { + value: () => ({ + cdp_command_name: command_name, + id: command_name, + name: command_name, + kind: "command", + }), + configurable: true, + }, + }); + Reflect.set(existing, property, alias); + return alias; + }, + }), + ); + return; + } + const existing_domain = Reflect.get(target, domain); + const domain_target = existing_domain != null && typeof existing_domain === "object" ? existing_domain : {}; + if (existing_domain !== domain_target) Reflect.set(target, domain, domain_target); + if (Reflect.has(domain_target, method)) return; + const alias = (params?: unknown) => send(name, params ?? {}); + Object.defineProperties(alias, { + cdp_command_name: { value: name, enumerable: true, configurable: true }, + id: { value: name, enumerable: true, configurable: true }, + name: { value: name, configurable: true }, + kind: { value: "command", enumerable: true, configurable: true }, + meta: { + value: () => ({ + cdp_command_name: name, + id: name, + name, + kind: "command", + }), + configurable: true, + }, + }); + Reflect.set(domain_target, method, alias); + } + + private hydrateBuiltinSchemas() { + for (const [method, schema] of Object.entries(nativeCommandSchemas) as [string, ProtocolCommandSchema][]) { + this.command_params_schemas.set(method, schema.params); + this.command_result_schemas.set(method, schema.result); + } + this.command_params_schemas.set("Mod.evaluate", Mod.EvaluateParams); + this.command_result_schemas.set("Mod.evaluate", Mod.EvaluateResponse); + this.command_params_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandParams); + this.command_result_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandResponse); + this.command_params_schemas.set("Mod.addCustomEvent", Mod.AddCustomEventParams); + this.command_result_schemas.set("Mod.addCustomEvent", Mod.AddCustomEventResponse); + this.command_params_schemas.set("Mod.addMiddleware", Mod.AddMiddlewareParams); + this.command_result_schemas.set("Mod.addMiddleware", Mod.AddMiddlewareResponse); + this.command_params_schemas.set("Mod.configure", Mod.ConfigureParams); + this.command_result_schemas.set("Mod.configure", Mod.ConfigureResponse); + this.command_params_schemas.set("Mod.ping", Mod.PingParams); + this.command_result_schemas.set("Mod.ping", Mod.PingResponse); + this.command_params_schemas.set("Mod.getTopology", Mod.GetTopologyParams); + this.command_result_schemas.set("Mod.getTopology", Mod.GetTopologyResponse); + for (const [event, schema] of Object.entries(nativeEventSchemas) as [string, ProtocolEventSchema][]) { + this.event_schemas.set(event, schema); + } + } + + private serviceWorkerRuntimeExpression( + method: string, + params: ProtocolParams, + cdpSessionId: string | null, + command_expression: string, + ) { + return ` + (async () => { + const method = ${JSON.stringify(method)}; + let commandParams = ${JSON.stringify(params ?? {})}; + const cdpSessionId = ${JSON.stringify(cdpSessionId)}; + const upstream = globalThis.ModCDP.client; + const downstream = globalThis.ModCDP.downstream; + const ModCDP = globalThis.ModCDP; + const cdp = { + upstream, + client: upstream, + downstream, + send: (method, params = {}, targetCdpSessionId = cdpSessionId) => + ModCDP.handleCommand(method, params, targetCdpSessionId), + }; + const chrome = globalThis.chrome; + const runMiddlewares = async (middlewares, payload, context = {}) => { + const dispatch = async (index, value) => { + const middleware = middlewares[index]; + if (!middleware) return value; + let nextCalled = false; + const next = async (nextValue = value) => { + if (nextCalled) throw new Error("Middleware called next() more than once."); + nextCalled = true; + return await dispatch(index + 1, nextValue); + }; + const result = await middleware(value, next, context); + if (result && result.__ModCDP_middleware_next__ === true) { + const nextResult = await next(result.value); + const { __ModCDP_middleware_next__, value: _value, ...overrides } = result; + if (Object.keys(overrides).length === 0) return nextResult; + return nextResult && typeof nextResult === "object" && !Array.isArray(nextResult) + ? { ...nextResult, ...overrides } + : overrides; + } + return result; + }; + return await dispatch(0, payload); + }; + const requestMiddlewares = [${this.serviceWorkerMiddlewareExpressions("request", method).join(",")}]; + const responseMiddlewares = [${this.serviceWorkerMiddlewareExpressions("response", method).join(",")}]; + const request = { method, params: commandParams, cdpSessionId }; + commandParams = await runMiddlewares(requestMiddlewares, commandParams, { + cdpSessionId, + request, + name: method, + phase: "request", + }); + if (commandParams == null) throw new Error("Request middleware returned no params."); + commandParams = ModCDP.types.parseCommandParams(method, commandParams); + const handler = (${command_expression}); + let result = await handler(commandParams || {}, method); + result = await runMiddlewares(responseMiddlewares, result, { + cdpSessionId, + request: { ...request, params: commandParams }, + response: { result }, + name: method, + phase: "response", + }); + return ModCDP.types.parseCommandResult(method, result); + })() + `; + } + + private serviceWorkerMiddlewareExpressions(phase: "request" | "response", method: string) { + return this.customMiddlewareRegistrations(phase, method).map( + (middleware) => ` + async (payload, next, context = {}) => { + const middleware = (${middleware.expression}); + return await middleware(payload, next, context); + } + `, + ); + } + + private registerCustomCommands(custom_commands: CDPTypesCustomCommands) { + for (const command of this.customCommandEntries(custom_commands)) this.addCustomCommand(command); + } + + private registerCustomEvents(custom_events: CDPTypesCustomEvents) { + for (const event of this.customEventEntries(custom_events)) this.addCustomEvent(event); + } + + private customCommandWireRegistration(name: string) { + return this.customCommandWireRegistrations().find((command) => command.name === name) ?? null; + } + + private customEventWireRegistration(name: string) { + const event = this.custom_events.get(name); + if (!event) return null; + const event_schema = serializablePayloadSchema(event.event_schema); + return { + name, + ...(event_schema == null ? {} : { event_schema }), + }; + } + + private customCommandEntries( + custom_commands: CDPTypesCustomCommands | [], + ): ModCDPAddCustomCommandParams[] { + if (Array.isArray(custom_commands)) return custom_commands; + return Object.entries(custom_commands).map(([name, command]) => ({ + name, + expression: command.expression ?? null, + params_schema: command.params_schema ?? null, + result_schema: command.result_schema ?? null, + })); + } + + private customEventEntries( + custom_events: CDPTypesCustomEvents | [], + ): ModCDPAddCustomEventObjectParams[] { + if (Array.isArray(custom_events)) return custom_events; + return Object.entries(custom_events).map(([name, event]) => ({ + name, + event_schema: event.event_schema ?? null, + })); + } + + private upsertCustomCommand(command: ModCDPAddCustomCommandParams) { + const name = normalizeModCDPName(command.name); + this.custom_commands.set(name, { ...command, name }); + } +} + +export { DEFAULT_BUILTIN_COMMANDS, DEFAULT_BUILTIN_EVENTS, hasCommandExpression, serializablePayloadSchema, CDPTypes }; +export type { + CDPCommandSpec, + CDPEventSpec, + CDPCommandMap, + CDPEventMap, + CDPTypesCustomCommands, + CDPTypesCustomEvents, + CDPTypesConfig, + CDPTypesCommandRegistration, + CDPTypesEventRegistration, + CDPAliasSend, + CDPEventNameInput, + CDPEventPayload, + CDPCommandAliases, + CDPEventMapPayloads, +}; diff --git a/js/src/types/codegen.ts b/js/src/types/codegen.ts index b2150249..012e09ea 100755 --- a/js/src/types/codegen.ts +++ b/js/src/types/codegen.ts @@ -1,4 +1,8 @@ #!/usr/bin/env node +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/types/codegen.py +// - ./go/modcdp/types/codegen.go import { execFileSync } from "node:child_process"; import fs from "node:fs"; @@ -62,16 +66,8 @@ const collect_refs = (x, d, refs) => { for (const p of x.returns || []) collect_refs(p, d, refs); }; const modcdpTypes = [ - ...fs.readFileSync(path.join(here, "modcdp.ts"), "utf8").matchAll(/^export type ModCDP([A-Z]\w*)\s*=/gm), + ...fs.readFileSync(path.join(here, "modcdp.ts"), "utf8").matchAll(/^type ModCDP([A-Z]\w*)\s*=/gm), ].map((match) => ({ name: match[1], type: `ModCDP${match[1]}` })); -const modcdpTypeNames = new Set(modcdpTypes.map((x) => x.name)); -const modcdpCommands = modcdpTypes - .filter((x) => x.name.endsWith("Params")) - .map((x) => x.name.slice(0, -"Params".length)) - .filter((base) => modcdpTypeNames.has(`${base}Response`)); -const method = (x) => (x ? x[0].toLowerCase() + x.slice(1) : x); -const hasRequiredParams = (x) => (x.parameters || []).some((p) => !p.optional); -const hasCommandsOrEvents = (d) => (d.commands || []).length || (d.events || []).length; const emitCdpNamespace = (lines, indent = "") => { for (const d of domains) { @@ -129,7 +125,8 @@ const aliases = [ `import type { z } from "zod";`, `import type { cdp } from "./cdp.js";`, `import { commands, events, types as runtimeTypes } from "./zod.js";`, - `import { Mod, normalizeModCDPName, normalizeModCDPPayloadSchema } from "../modcdp.js";`, + `import { Mod, normalizeModCDPName, validateZodSchema } from "../modcdp.js";`, + `import type { ProtocolParams, ProtocolPayload, ProtocolResult } from "../modcdp.js";`, ``, `export type CdpNamedValue = { readonly id: Name; readonly name: Name; readonly kind: Kind; meta(): { id: Name; name: Name; kind: Kind } };`, `export type CdpCommandAlias = ((params: Params) => Promise) & CdpNamedValue;`, @@ -140,58 +137,84 @@ const aliases = [ ` onCustomCommand?: (name: string, params_schema?: z.ZodType | null, result_schema?: z.ZodType | null) => void;`, ` onCustomEvent?: (name: string, event_schema?: z.ZodType | null) => void;`, `};`, - `export type ModCustomCommandOptions = {`, + `export type ModCustomCommandConfig = {`, ` params_schema?: TParamsSchema | null;`, ` result_schema?: TResultSchema | null;`, ` expression?: string | null;`, `};`, + `export type CdpCommandSpec = {`, + ` params_schema?: TParamsSchema | null;`, + ` result_schema?: TResultSchema | null;`, + ` expression?: string | null;`, + `};`, + `export type CdpEventSpec = { event_schema?: TEventSchema | null };`, + `export type CdpCommandMap = Record;`, + `export type CdpEventMap = Record;`, ``, - `export type CdpAliases = {`, + `type DomainName = TName extends \`\${infer TDomain}.\${string}\` ? TDomain : never;`, + `type MemberName = TName extends \`\${string}.\${infer TMember}\` ? TMember : never;`, + `type RequiredKeys = TValue extends object ? { [TKey in keyof TValue]-?: {} extends Pick ? never : TKey }[keyof TValue] : keyof TValue;`, + `type HasRequiredParams = TParams extends object ? [RequiredKeys] extends [never] ? false : true : true;`, + `type NativeCommandAliasSpec = TCommand extends { params: infer TParams extends z.ZodType; result: infer TResult extends z.ZodType } ? { params: z.input; result: z.output } : never;`, + `type NativeEventAliasSpec = TEvent extends z.ZodType ? z.output : never;`, + `type CustomCommandAliasSpec = {`, + ` params: TCommand["params_schema"] extends z.ZodType ? z.input : ProtocolParams;`, + ` result: TCommand["result_schema"] extends z.ZodType ? z.output : ProtocolResult;`, + `};`, + `type CustomEventAliasSpec = TEvent["event_schema"] extends z.ZodType ? z.output : ProtocolPayload;`, + `type ModCommandBase = Extract extends infer TKey ? TKey extends \`\${infer TBase}Params\` ? \`\${TBase}Response\` extends keyof typeof Mod ? TBase : never : never : never;`, + `type ModEventBase = Extract extends infer TKey ? TKey extends \`\${infer TBase}Event\` ? TBase : never : never;`, + `type ModParamsSchema = Extract<(typeof Mod)[Extract<\`\${TBase}Params\`, keyof typeof Mod>], z.ZodType>;`, + `type ModResponseSchema = Extract<(typeof Mod)[Extract<\`\${TBase}Response\`, keyof typeof Mod>], z.ZodType>;`, + `type ModEventSchema = Extract<(typeof Mod)[Extract<\`\${TBase}Event\`, keyof typeof Mod>], z.ZodType>;`, + `type NativeCommandAliasSpecs = { [TName in keyof typeof commands & string]: NativeCommandAliasSpec<(typeof commands)[TName]> };`, + `type NativeEventAliasSpecs = { [TName in keyof typeof events & string]: NativeEventAliasSpec<(typeof events)[TName]> };`, + `type ModCommandAliasSpecs = { [TBase in ModCommandBase as \`Mod.\${Uncapitalize}\`]: { params: z.input>; result: z.output> } };`, + `type ModEventAliasSpecs = { [TBase in ModEventBase as \`Mod.\${Uncapitalize}\`]: z.output> };`, + `type CustomCommandAliasSpecs = { [TName in keyof TCommands & string]: CustomCommandAliasSpec };`, + `type CustomEventAliasSpecs = { [TName in keyof TEvents & string]: CustomEventAliasSpec };`, + `type CommandAliasSpecs = NativeCommandAliasSpecs & ModCommandAliasSpecs & CustomCommandAliasSpecs;`, + `type EventAliasSpecs = NativeEventAliasSpecs & ModEventAliasSpecs & CustomEventAliasSpecs;`, + `type CommandAlias = HasRequiredParams extends true ? CdpCommandAlias : CdpOptionalCommandAlias;`, + `type CommandAliases> = {`, + ` [TDomain in DomainName>]: {`, + ` [TName in Extract as MemberName>]: CommandAlias>;`, + ` };`, + `};`, + `type EventAliases> = {`, + ` [TDomain in DomainName>]: {`, + ` [TName in Extract as MemberName>]: CdpEventAlias>;`, + ` };`, + `};`, + `type MergeDomainAliases = {`, + ` [TDomain in keyof (CommandAliases> & EventAliases>)]:`, + ` TDomain extends keyof CommandAliases>`, + ` ? TDomain extends keyof EventAliases>`, + ` ? CommandAliases>[TDomain] & EventAliases>[TDomain]`, + ` : CommandAliases>[TDomain]`, + ` : TDomain extends keyof EventAliases>`, + ` ? EventAliases>[TDomain]`, + ` : never;`, + `};`, + `type ModAliasOverrides = {`, + ` Mod: {`, + ` addCustomCommand(name: TName, config?: ModCustomCommandConfig): Promise>;`, + ` addCustomCommand(params: z.input): Promise>;`, + ` addCustomEvent(name: TName, config?: { event_schema?: TEventSchema | null }): Promise>;`, + ` addCustomEvent(params: z.input): Promise>;`, + ` };`, + `};`, + `export type CdpEventPayloads = EventAliasSpecs;`, + `export type CdpCommandAliases = CommandAliases>;`, + `export type CdpAliases = {`, ` readonly types: typeof runtimeTypes;`, ` readonly commands: typeof commands;`, ` readonly events: typeof events;`, ` readonly REQUEST: "request";`, ` readonly RESPONSE: "response";`, ` readonly EVENT: "event";`, -]; -for (const d of domains) { - if (!hasCommandsOrEvents(d)) continue; - aliases.push(` ${word(d.domain)}: {`); - for (const x of d.commands || []) { - const optional = !hasRequiredParams(x); - const commandName = `${d.domain}.${x.name}`; - aliases.push( - ` ${word(x.name)}: ${optional ? "CdpOptionalCommandAlias" : "CdpCommandAlias"};`, - ); - } - for (const x of d.events || []) { - const eventName = `${d.domain}.${x.name}`; - aliases.push( - ` ${word(x.name)}: CdpEventAlias;`, - ); - } - aliases.push(` };`); -} -aliases.push(` Mod: {`); -for (const base of modcdpCommands) { - const optional = base === "Ping"; - if (base === "AddCustomCommand") { - aliases.push( - ` addCustomCommand(name: TName, options?: ModCustomCommandOptions): Promise;`, - ); - } - if (base === "AddCustomEvent") { - aliases.push( - ` addCustomEvent(name: TName, options?: { event_schema?: TEventSchema | null }): Promise;`, - ); - } - aliases.push( - ` ${method(base)}(${optional ? "params?" : "params"}: cdp.types.ts.Mod.${base}Params): Promise;`, - ); -} -aliases.push(` };`, `};`, ``); - -aliases.push( + `} & MergeDomainAliases & ModAliasOverrides;`, + ``, `function withCdpName(value: T, id: Name, kind: Kind): T & CdpNamedValue {`, ` Object.defineProperties(value, {`, ` id: { value: id, enumerable: true, configurable: true },`, @@ -210,68 +233,71 @@ aliases.push( ` REQUEST: "request",`, ` RESPONSE: "response",`, ` EVENT: "event",`, -); +]; for (const d of domains) { - if (!hasCommandsOrEvents(d)) continue; + if (!(d.commands || []).length && !(d.events || []).length) continue; aliases.push(` ${word(d.domain)}: {`); for (const x of d.commands || []) { const commandName = `${d.domain}.${x.name}`; aliases.push( - ` ${word(x.name)}: withCdpName(async (params?: unknown) => commands[${JSON.stringify(commandName)}].result.parse(await send(${JSON.stringify(commandName)}, commands[${JSON.stringify(commandName)}].params.parse(params ?? {}))), ${JSON.stringify(commandName)}, "command"),`, + ` ${word(x.name)}: withCdpName(async (params?: cdp.types.ts.${d.domain}.${params(x.name)}) => commands[${JSON.stringify(commandName)}].result.parse(await send(${JSON.stringify(commandName)}, commands[${JSON.stringify(commandName)}].params.parse(params ?? {}))), ${JSON.stringify(commandName)}, "command"),`, ); } for (const x of d.events || []) { const eventName = `${d.domain}.${x.name}`; aliases.push( - ` ${word(x.name)}: events[${JSON.stringify(eventName)}] as CdpEventAlias,`, + ` ${word(x.name)}: withCdpName(events[${JSON.stringify(eventName)}], ${JSON.stringify(eventName)}, "event"),`, ); } aliases.push(` },`); } aliases.push(` Mod: {`); -for (const base of modcdpCommands) { - const methodName = method(base); +for (const base of modcdpTypes + .filter((x) => x.name.endsWith("Params")) + .map((x) => x.name.slice(0, -"Params".length)) + .filter((base) => modcdpTypes.some((x) => x.name === `${base}Response`))) { + const methodName = base ? base[0].toLowerCase() + base.slice(1) : base; const commandName = `Mod.${methodName}`; const lines = base === "AddCustomCommand" ? [ - ` ${methodName}: async (params_or_name?: unknown, options: Record = {}) => {`, - ` const input = typeof params_or_name === "string" ? { name: params_or_name, expression: options.expression ?? null, params_schema: options.params_schema ?? null, result_schema: options.result_schema ?? null } : params_or_name;`, + ` ${methodName}: withCdpName(async (params_or_name?: cdp.types.ts.Mod.${base}Params | string, config: ModCustomCommandConfig = {}) => {`, + ` const input = typeof params_or_name === "string" ? { name: params_or_name, expression: config.expression ?? null, params_schema: config.params_schema ?? null, result_schema: config.result_schema ?? null } : params_or_name;`, ` const parsed = Mod.${base}Params.parse(input ?? {});`, ] : base === "AddCustomEvent" ? [ - ` ${methodName}: async (params_or_name?: unknown, options: Record = {}) => {`, - ` const input = typeof params_or_name === "string" ? { name: params_or_name, event_schema: options.event_schema ?? null } : params_or_name;`, + ` ${methodName}: withCdpName(async (params_or_name?: cdp.types.ts.Mod.${base}Params | string, config: { event_schema?: z.ZodType | null } = {}) => {`, + ` const input = typeof params_or_name === "string" ? { name: params_or_name, event_schema: config.event_schema ?? null } : params_or_name;`, ` const parsed = Mod.${base}Params.parse(input ?? {});`, ] : [ - ` ${methodName}: async (params?: unknown) => {`, + ` ${methodName}: withCdpName(async (params?: cdp.types.ts.Mod.${base}Params) => {`, ` const parsed = Mod.${base}Params.parse(params ?? {});`, ]; if (base === "AddCustomCommand") { lines.push( ` const name = normalizeModCDPName(parsed.name);`, - ` const params_schema = normalizeModCDPPayloadSchema(parsed.params_schema);`, - ` const result_schema = normalizeModCDPPayloadSchema(parsed.result_schema);`, - ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { ...parsed, name, params_schema: null, result_schema: null }));`, + ` const params_schema = validateZodSchema(parsed.params_schema);`, + ` const result_schema = validateZodSchema(parsed.result_schema);`, + ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { ...parsed, name }));`, ` hooks.onCustomCommand?.(name, params_schema, result_schema);`, ` return response;`, ); } else if (base === "AddCustomEvent") { lines.push( - ` const directSchema = Mod.ZodType.safeParse(parsed);`, - ` if (directSchema.success) {`, - ` const name = normalizeModCDPName(directSchema.data);`, - ` const event_schema = normalizeModCDPPayloadSchema(directSchema.data);`, - ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { name, event_schema: null }));`, + ` const direct_schema = Mod.ZodType.safeParse(parsed);`, + ` if (direct_schema.success) {`, + ` const name = normalizeModCDPName(direct_schema.data);`, + ` const event_schema = validateZodSchema(direct_schema.data);`, + ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { name, event_schema }));`, ` hooks.onCustomEvent?.(name, event_schema);`, ` return response;`, ` }`, - ` const objectParams = Mod.AddCustomEventObjectParams.parse(parsed);`, - ` const name = normalizeModCDPName(objectParams.name);`, - ` const event_schema = normalizeModCDPPayloadSchema(objectParams.event_schema);`, - ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { ...objectParams, name, event_schema: null }));`, + ` const object_params = Mod.AddCustomEventObjectParams.parse(parsed);`, + ` const name = normalizeModCDPName(object_params.name);`, + ` const event_schema = validateZodSchema(object_params.event_schema);`, + ` const response = Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, { ...object_params, name, event_schema }));`, ` hooks.onCustomEvent?.(name, event_schema);`, ` return response;`, ); @@ -283,9 +309,15 @@ for (const base of modcdpCommands) { } else { lines.push(` return Mod.${base}Response.parse(await send(${JSON.stringify(commandName)}, parsed));`); } - lines.push(` },`); + lines.push(` }, ${JSON.stringify(commandName)}, "command"),`); aliases.push(...lines); } +for (const base of modcdpTypes.filter((x) => x.name.endsWith("Event")).map((x) => x.name.slice(0, -"Event".length))) { + const eventName = `Mod.${base ? base[0].toLowerCase() + base.slice(1) : base}`; + aliases.push( + ` ${base ? base[0].toLowerCase() + base.slice(1) : base}: withCdpName(Mod.${base}Event, ${JSON.stringify(eventName)}, "event"),`, + ); +} aliases.push(` },`, ` };`, `}`, ``); const zod_dir = path.join(generated_dir, "zod"); @@ -297,6 +329,7 @@ const zod_helper = [ `import type { z } from "zod";`, ``, `export type CdpNamedSchema = T & { readonly id: string; readonly name: string; readonly kind: string; meta(): { id: string; name: string; kind: string } };`, + `export type CdpCommandSchema> = z.ZodType>, Result extends z.ZodType> = z.ZodType>, Name extends string = string> = { readonly id: Name; readonly name: Name; readonly kind: "command"; readonly params: Params; readonly result: Result; meta(): { id: Name; name: Name; kind: "command" } };`, `export const withCdpMeta = (schema: T, id: string, kind: string, extra = {}): CdpNamedSchema => {`, ` const meta = { id, name: id, kind, ...extra };`, ` const named = schema.meta(meta);`, @@ -307,6 +340,17 @@ const zod_helper = [ ` });`, ` return named as CdpNamedSchema;`, `};`, + `export const withCdpCommand = >, Result extends z.ZodType>>(id: Name, params: Params, result: Result): CdpCommandSchema => {`, + ` const meta = { id, name: id, kind: "command" as const };`, + ` return {`, + ` id,`, + ` name: id,`, + ` kind: "command",`, + ` params,`, + ` result,`, + ` meta: () => meta,`, + ` };`, + `};`, ``, ]; fs.writeFileSync(path.join(zod_dir, "helpers.ts"), zod_helper.join("\n")); @@ -321,7 +365,7 @@ for (const d of domains) { `// Generated by types/codegen.ts from devtools-protocol@${meta.version}. Do not edit by hand.`, `// @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references.`, `import { z } from "zod";`, - `import { withCdpMeta } from "./helpers.js";`, + `import { withCdpCommand, withCdpMeta } from "./helpers.js";`, ]; for (const ref of [...refs].sort()) domain_zod.push(`import * as ${domain_file(ref)} from "./${domain_file(ref)}.js";`); @@ -343,6 +387,9 @@ for (const d of domains) { domain_zod.push( `export const ${local_name(result(x.name))} = withCdpMeta(${zobj(x.returns || [], d.domain)}, ${JSON.stringify(`${commandName}.result`)}, "commandResult", { method: ${JSON.stringify(commandName)} });`, ); + domain_zod.push( + `export const ${local_name(`${title(x.name)}Command`)} = withCdpCommand(${JSON.stringify(commandName)}, ${local_name(params(x.name))}, ${local_name(result(x.name))});`, + ); } for (const x of d.events || []) domain_zod.push( @@ -359,9 +406,7 @@ for (const d of domains) { for (const x of d.events || []) domain_zod.push(` ${word(event(x.name))}: ${local_name(event(x.name))},`); domain_zod.push(`} as const;`, `export const commands = {`); for (const x of d.commands || []) - domain_zod.push( - ` ${JSON.stringify(`${d.domain}.${x.name}`)}: { params: ${local_name(params(x.name))}, result: ${local_name(result(x.name))} },`, - ); + domain_zod.push(` ${JSON.stringify(`${d.domain}.${x.name}`)}: ${local_name(`${title(x.name)}Command`)},`); domain_zod.push(`} as const;`, `export const events = {`); for (const x of d.events || []) domain_zod.push(` ${JSON.stringify(`${d.domain}.${x.name}`)}: ${local_name(event(x.name))},`); diff --git a/js/src/types/generated/aliases.ts b/js/src/types/generated/aliases.ts index fe5a7271..5211e430 100644 --- a/js/src/types/generated/aliases.ts +++ b/js/src/types/generated/aliases.ts @@ -2,1074 +2,184 @@ import type { z } from "zod"; import type { cdp } from "./cdp.js"; import { commands, events, types as runtimeTypes } from "./zod.js"; -import { Mod, normalizeModCDPName, normalizeModCDPPayloadSchema } from "../modcdp.js"; +import { Mod, normalizeModCDPName, validateZodSchema } from "../modcdp.js"; +import type { ProtocolParams, ProtocolPayload, ProtocolResult } from "../modcdp.js"; -export type CdpNamedValue = { readonly id: Name; readonly name: Name; readonly kind: Kind; meta(): { id: Name; name: Name; kind: Kind } }; -export type CdpCommandAlias = ((params: Params) => Promise) & CdpNamedValue; -export type CdpOptionalCommandAlias = ((params?: Params) => Promise) & CdpNamedValue; +export type CdpNamedValue = { + readonly id: Name; + readonly name: Name; + readonly kind: Kind; + meta(): { id: Name; name: Name; kind: Kind }; +}; +export type CdpCommandAlias = ((params: Params) => Promise) & + CdpNamedValue; +export type CdpOptionalCommandAlias = ((params?: Params) => Promise) & + CdpNamedValue; export type CdpEventAlias = z.ZodType & CdpNamedValue; export type CdpAliasSend = (method: string, params?: unknown) => Promise; export type CdpAliasHooks = { onCustomCommand?: (name: string, params_schema?: z.ZodType | null, result_schema?: z.ZodType | null) => void; onCustomEvent?: (name: string, event_schema?: z.ZodType | null) => void; }; -type ModSchemaInfer = T extends z.ZodType ? TValue : T extends Record ? z.infer> : unknown; -type IsUnion = T extends unknown ? ([U] extends [T] ? false : true) : never; -type UnwrapSingleObject = T extends Record ? IsUnion extends true ? T : T[keyof T] : T; -export type ModCustomCommandOptions = { +export type ModCustomCommandConfig = { + params_schema?: TParamsSchema | null; + result_schema?: TResultSchema | null; + expression?: string | null; +}; +export type CdpCommandSpec = { params_schema?: TParamsSchema | null; result_schema?: TResultSchema | null; expression?: string | null; }; +export type CdpEventSpec = { event_schema?: TEventSchema | null }; +export type CdpCommandMap = Record; +export type CdpEventMap = Record; -export type CdpAliases = { - readonly types: typeof runtimeTypes; - readonly commands: typeof commands; - readonly events: typeof events; - readonly REQUEST: "request"; - readonly RESPONSE: "response"; - readonly EVENT: "event"; - Accessibility: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getPartialAXTree: CdpOptionalCommandAlias; - getFullAXTree: CdpOptionalCommandAlias; - getRootAXNode: CdpOptionalCommandAlias; - getAXNodeAndAncestors: CdpOptionalCommandAlias; - getChildAXNodes: CdpCommandAlias; - queryAXTree: CdpOptionalCommandAlias; - loadComplete: CdpEventAlias; - nodesUpdated: CdpEventAlias; - }; - Animation: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getCurrentTime: CdpCommandAlias; - getPlaybackRate: CdpOptionalCommandAlias; - releaseAnimations: CdpCommandAlias; - resolveAnimation: CdpCommandAlias; - seekAnimations: CdpCommandAlias; - setPaused: CdpCommandAlias; - setPlaybackRate: CdpCommandAlias; - setTiming: CdpCommandAlias; - animationCanceled: CdpEventAlias; - animationCreated: CdpEventAlias; - animationStarted: CdpEventAlias; - animationUpdated: CdpEventAlias; - }; - Audits: { - getEncodedResponse: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - checkFormsIssues: CdpOptionalCommandAlias; - issueAdded: CdpEventAlias; - }; - Autofill: { - trigger: CdpCommandAlias; - setAddresses: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - addressFormFilled: CdpEventAlias; - }; - BackgroundService: { - startObserving: CdpCommandAlias; - stopObserving: CdpCommandAlias; - setRecording: CdpCommandAlias; - clearEvents: CdpCommandAlias; - recordingStateChanged: CdpEventAlias; - backgroundServiceEventReceived: CdpEventAlias; - }; - BluetoothEmulation: { - enable: CdpCommandAlias; - setSimulatedCentralState: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - simulatePreconnectedPeripheral: CdpCommandAlias; - simulateAdvertisement: CdpCommandAlias; - simulateGATTOperationResponse: CdpCommandAlias; - simulateCharacteristicOperationResponse: CdpCommandAlias; - simulateDescriptorOperationResponse: CdpCommandAlias; - addService: CdpCommandAlias; - removeService: CdpCommandAlias; - addCharacteristic: CdpCommandAlias; - removeCharacteristic: CdpCommandAlias; - addDescriptor: CdpCommandAlias; - removeDescriptor: CdpCommandAlias; - simulateGATTDisconnection: CdpCommandAlias; - gattOperationReceived: CdpEventAlias; - characteristicOperationReceived: CdpEventAlias; - descriptorOperationReceived: CdpEventAlias; - }; - Browser: { - setPermission: CdpCommandAlias; - grantPermissions: CdpCommandAlias; - resetPermissions: CdpOptionalCommandAlias; - setDownloadBehavior: CdpCommandAlias; - cancelDownload: CdpCommandAlias; - close: CdpOptionalCommandAlias; - crash: CdpOptionalCommandAlias; - crashGpuProcess: CdpOptionalCommandAlias; - getVersion: CdpOptionalCommandAlias; - getBrowserCommandLine: CdpOptionalCommandAlias; - getHistograms: CdpOptionalCommandAlias; - getHistogram: CdpCommandAlias; - getWindowBounds: CdpCommandAlias; - getWindowForTarget: CdpOptionalCommandAlias; - setWindowBounds: CdpCommandAlias; - setContentsSize: CdpCommandAlias; - setDockTile: CdpOptionalCommandAlias; - executeBrowserCommand: CdpCommandAlias; - addPrivacySandboxEnrollmentOverride: CdpCommandAlias; - addPrivacySandboxCoordinatorKeyConfig: CdpCommandAlias; - downloadWillBegin: CdpEventAlias; - downloadProgress: CdpEventAlias; - }; - CacheStorage: { - deleteCache: CdpCommandAlias; - deleteEntry: CdpCommandAlias; - requestCacheNames: CdpOptionalCommandAlias; - requestCachedResponse: CdpCommandAlias; - requestEntries: CdpCommandAlias; - }; - Cast: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - setSinkToUse: CdpCommandAlias; - startDesktopMirroring: CdpCommandAlias; - startTabMirroring: CdpCommandAlias; - stopCasting: CdpCommandAlias; - sinksUpdated: CdpEventAlias; - issueUpdated: CdpEventAlias; - }; - Console: { - clearMessages: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - messageAdded: CdpEventAlias; - }; - CrashReportContext: { - getEntries: CdpOptionalCommandAlias; - }; - CSS: { - addRule: CdpCommandAlias; - collectClassNames: CdpCommandAlias; - createStyleSheet: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - forcePseudoState: CdpCommandAlias; - forceStartingStyle: CdpCommandAlias; - getBackgroundColors: CdpCommandAlias; - getComputedStyleForNode: CdpCommandAlias; - resolveValues: CdpCommandAlias; - getLonghandProperties: CdpCommandAlias; - getInlineStylesForNode: CdpCommandAlias; - getAnimatedStylesForNode: CdpCommandAlias; - getMatchedStylesForNode: CdpCommandAlias; - getEnvironmentVariables: CdpOptionalCommandAlias; - getMediaQueries: CdpOptionalCommandAlias; - getPlatformFontsForNode: CdpCommandAlias; - getStyleSheetText: CdpCommandAlias; - getLayersForNode: CdpCommandAlias; - getLocationForSelector: CdpCommandAlias; - trackComputedStyleUpdatesForNode: CdpOptionalCommandAlias; - trackComputedStyleUpdates: CdpCommandAlias; - takeComputedStyleUpdates: CdpOptionalCommandAlias; - setEffectivePropertyValueForNode: CdpCommandAlias; - setPropertyRulePropertyName: CdpCommandAlias; - setKeyframeKey: CdpCommandAlias; - setMediaText: CdpCommandAlias; - setContainerQueryText: CdpCommandAlias; - setSupportsText: CdpCommandAlias; - setNavigationText: CdpCommandAlias; - setScopeText: CdpCommandAlias; - setRuleSelector: CdpCommandAlias; - setStyleSheetText: CdpCommandAlias; - setStyleTexts: CdpCommandAlias; - startRuleUsageTracking: CdpOptionalCommandAlias; - stopRuleUsageTracking: CdpOptionalCommandAlias; - takeCoverageDelta: CdpOptionalCommandAlias; - setLocalFontsEnabled: CdpCommandAlias; - fontsUpdated: CdpEventAlias; - mediaQueryResultChanged: CdpEventAlias; - styleSheetAdded: CdpEventAlias; - styleSheetChanged: CdpEventAlias; - styleSheetRemoved: CdpEventAlias; - computedStyleUpdated: CdpEventAlias; - }; - Debugger: { - continueToLocation: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - evaluateOnCallFrame: CdpCommandAlias; - getPossibleBreakpoints: CdpCommandAlias; - getScriptSource: CdpCommandAlias; - disassembleWasmModule: CdpCommandAlias; - nextWasmDisassemblyChunk: CdpCommandAlias; - getWasmBytecode: CdpCommandAlias; - getStackTrace: CdpCommandAlias; - pause: CdpOptionalCommandAlias; - pauseOnAsyncCall: CdpCommandAlias; - removeBreakpoint: CdpCommandAlias; - restartFrame: CdpCommandAlias; - resume: CdpOptionalCommandAlias; - searchInContent: CdpCommandAlias; - setAsyncCallStackDepth: CdpCommandAlias; - setBlackboxExecutionContexts: CdpCommandAlias; - setBlackboxPatterns: CdpCommandAlias; - setBlackboxedRanges: CdpCommandAlias; - setBreakpoint: CdpCommandAlias; - setInstrumentationBreakpoint: CdpCommandAlias; - setBreakpointByUrl: CdpCommandAlias; - setBreakpointOnFunctionCall: CdpCommandAlias; - setBreakpointsActive: CdpCommandAlias; - setPauseOnExceptions: CdpCommandAlias; - setReturnValue: CdpCommandAlias; - setScriptSource: CdpCommandAlias; - setSkipAllPauses: CdpCommandAlias; - setVariableValue: CdpCommandAlias; - stepInto: CdpOptionalCommandAlias; - stepOut: CdpOptionalCommandAlias; - stepOver: CdpOptionalCommandAlias; - breakpointResolved: CdpEventAlias; - paused: CdpEventAlias; - resumed: CdpEventAlias; - scriptFailedToParse: CdpEventAlias; - scriptParsed: CdpEventAlias; - }; - DeviceAccess: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - selectPrompt: CdpCommandAlias; - cancelPrompt: CdpCommandAlias; - deviceRequestPrompted: CdpEventAlias; - }; - DeviceOrientation: { - clearDeviceOrientationOverride: CdpOptionalCommandAlias; - setDeviceOrientationOverride: CdpCommandAlias; - }; - DOM: { - collectClassNamesFromSubtree: CdpCommandAlias; - copyTo: CdpCommandAlias; - describeNode: CdpOptionalCommandAlias; - scrollIntoViewIfNeeded: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - discardSearchResults: CdpCommandAlias; - enable: CdpOptionalCommandAlias; - focus: CdpOptionalCommandAlias; - getAttributes: CdpCommandAlias; - getBoxModel: CdpOptionalCommandAlias; - getContentQuads: CdpOptionalCommandAlias; - getDocument: CdpOptionalCommandAlias; - getFlattenedDocument: CdpOptionalCommandAlias; - getNodesForSubtreeByStyle: CdpCommandAlias; - getNodeForLocation: CdpCommandAlias; - getOuterHTML: CdpOptionalCommandAlias; - getRelayoutBoundary: CdpCommandAlias; - getSearchResults: CdpCommandAlias; - hideHighlight: CdpOptionalCommandAlias; - highlightNode: CdpOptionalCommandAlias; - highlightRect: CdpOptionalCommandAlias; - markUndoableState: CdpOptionalCommandAlias; - moveTo: CdpCommandAlias; - performSearch: CdpCommandAlias; - pushNodeByPathToFrontend: CdpCommandAlias; - pushNodesByBackendIdsToFrontend: CdpCommandAlias; - querySelector: CdpCommandAlias; - querySelectorAll: CdpCommandAlias; - getTopLayerElements: CdpOptionalCommandAlias; - getElementByRelation: CdpCommandAlias; - redo: CdpOptionalCommandAlias; - removeAttribute: CdpCommandAlias; - removeNode: CdpCommandAlias; - requestChildNodes: CdpCommandAlias; - requestNode: CdpCommandAlias; - resolveNode: CdpOptionalCommandAlias; - setAttributeValue: CdpCommandAlias; - setAttributesAsText: CdpCommandAlias; - setFileInputFiles: CdpCommandAlias; - setNodeStackTracesEnabled: CdpCommandAlias; - getNodeStackTraces: CdpCommandAlias; - getFileInfo: CdpCommandAlias; - getDetachedDomNodes: CdpOptionalCommandAlias; - setInspectedNode: CdpCommandAlias; - setNodeName: CdpCommandAlias; - setNodeValue: CdpCommandAlias; - setOuterHTML: CdpCommandAlias; - undo: CdpOptionalCommandAlias; - getFrameOwner: CdpCommandAlias; - getContainerForNode: CdpCommandAlias; - getQueryingDescendantsForContainer: CdpCommandAlias; - getAnchorElement: CdpCommandAlias; - forceShowPopover: CdpCommandAlias; - attributeModified: CdpEventAlias; - adoptedStyleSheetsModified: CdpEventAlias; - attributeRemoved: CdpEventAlias; - characterDataModified: CdpEventAlias; - childNodeCountUpdated: CdpEventAlias; - childNodeInserted: CdpEventAlias; - childNodeRemoved: CdpEventAlias; - distributedNodesUpdated: CdpEventAlias; - documentUpdated: CdpEventAlias; - inlineStyleInvalidated: CdpEventAlias; - pseudoElementAdded: CdpEventAlias; - topLayerElementsUpdated: CdpEventAlias; - scrollableFlagUpdated: CdpEventAlias; - adRelatedStateUpdated: CdpEventAlias; - affectedByStartingStylesFlagUpdated: CdpEventAlias; - pseudoElementRemoved: CdpEventAlias; - setChildNodes: CdpEventAlias; - shadowRootPopped: CdpEventAlias; - shadowRootPushed: CdpEventAlias; - }; - DOMDebugger: { - getEventListeners: CdpCommandAlias; - removeDOMBreakpoint: CdpCommandAlias; - removeEventListenerBreakpoint: CdpCommandAlias; - removeInstrumentationBreakpoint: CdpCommandAlias; - removeXHRBreakpoint: CdpCommandAlias; - setBreakOnCSPViolation: CdpCommandAlias; - setDOMBreakpoint: CdpCommandAlias; - setEventListenerBreakpoint: CdpCommandAlias; - setInstrumentationBreakpoint: CdpCommandAlias; - setXHRBreakpoint: CdpCommandAlias; - }; - DOMSnapshot: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getSnapshot: CdpCommandAlias; - captureSnapshot: CdpCommandAlias; - }; - DOMStorage: { - clear: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getDOMStorageItems: CdpCommandAlias; - removeDOMStorageItem: CdpCommandAlias; - setDOMStorageItem: CdpCommandAlias; - domStorageItemAdded: CdpEventAlias; - domStorageItemRemoved: CdpEventAlias; - domStorageItemUpdated: CdpEventAlias; - domStorageItemsCleared: CdpEventAlias; - }; - Emulation: { - canEmulate: CdpOptionalCommandAlias; - clearDeviceMetricsOverride: CdpOptionalCommandAlias; - clearGeolocationOverride: CdpOptionalCommandAlias; - resetPageScaleFactor: CdpOptionalCommandAlias; - setFocusEmulationEnabled: CdpCommandAlias; - setAutoDarkModeOverride: CdpOptionalCommandAlias; - setCPUThrottlingRate: CdpCommandAlias; - setDefaultBackgroundColorOverride: CdpOptionalCommandAlias; - setSafeAreaInsetsOverride: CdpCommandAlias; - setDeviceMetricsOverride: CdpCommandAlias; - setDevicePostureOverride: CdpCommandAlias; - clearDevicePostureOverride: CdpOptionalCommandAlias; - setDisplayFeaturesOverride: CdpCommandAlias; - clearDisplayFeaturesOverride: CdpOptionalCommandAlias; - setScrollbarsHidden: CdpCommandAlias; - setDocumentCookieDisabled: CdpCommandAlias; - setEmitTouchEventsForMouse: CdpCommandAlias; - setEmulatedMedia: CdpOptionalCommandAlias; - setEmulatedVisionDeficiency: CdpCommandAlias; - setEmulatedOSTextScale: CdpOptionalCommandAlias; - setGeolocationOverride: CdpOptionalCommandAlias; - getOverriddenSensorInformation: CdpCommandAlias; - setSensorOverrideEnabled: CdpCommandAlias; - setSensorOverrideReadings: CdpCommandAlias; - setPressureSourceOverrideEnabled: CdpCommandAlias; - setPressureStateOverride: CdpCommandAlias; - setPressureDataOverride: CdpCommandAlias; - setIdleOverride: CdpCommandAlias; - clearIdleOverride: CdpOptionalCommandAlias; - setNavigatorOverrides: CdpCommandAlias; - setPageScaleFactor: CdpCommandAlias; - setScriptExecutionDisabled: CdpCommandAlias; - setTouchEmulationEnabled: CdpCommandAlias; - setVirtualTimePolicy: CdpCommandAlias; - setLocaleOverride: CdpOptionalCommandAlias; - setTimezoneOverride: CdpCommandAlias; - setVisibleSize: CdpCommandAlias; - setDisabledImageTypes: CdpCommandAlias; - setDataSaverOverride: CdpOptionalCommandAlias; - setHardwareConcurrencyOverride: CdpCommandAlias; - setUserAgentOverride: CdpCommandAlias; - setAutomationOverride: CdpCommandAlias; - setSmallViewportHeightDifferenceOverride: CdpCommandAlias; - getScreenInfos: CdpOptionalCommandAlias; - addScreen: CdpCommandAlias; - updateScreen: CdpCommandAlias; - removeScreen: CdpCommandAlias; - setPrimaryScreen: CdpCommandAlias; - virtualTimeBudgetExpired: CdpEventAlias; - screenOrientationLockChanged: CdpEventAlias; - }; - EventBreakpoints: { - setInstrumentationBreakpoint: CdpCommandAlias; - removeInstrumentationBreakpoint: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - }; - Extensions: { - triggerAction: CdpCommandAlias; - loadUnpacked: CdpCommandAlias; - getExtensions: CdpOptionalCommandAlias; - uninstall: CdpCommandAlias; - getStorageItems: CdpCommandAlias; - removeStorageItems: CdpCommandAlias; - clearStorageItems: CdpCommandAlias; - setStorageItems: CdpCommandAlias; - }; - FedCm: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - selectAccount: CdpCommandAlias; - clickDialogButton: CdpCommandAlias; - openUrl: CdpCommandAlias; - dismissDialog: CdpCommandAlias; - resetCooldown: CdpOptionalCommandAlias; - dialogShown: CdpEventAlias; - dialogClosed: CdpEventAlias; - }; - Fetch: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - failRequest: CdpCommandAlias; - fulfillRequest: CdpCommandAlias; - continueRequest: CdpCommandAlias; - continueWithAuth: CdpCommandAlias; - continueResponse: CdpCommandAlias; - getResponseBody: CdpCommandAlias; - takeResponseBodyAsStream: CdpCommandAlias; - requestPaused: CdpEventAlias; - authRequired: CdpEventAlias; - }; - FileSystem: { - getDirectory: CdpCommandAlias; - }; - HeadlessExperimental: { - beginFrame: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - }; - HeapProfiler: { - addInspectedHeapObject: CdpCommandAlias; - collectGarbage: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getHeapObjectId: CdpCommandAlias; - getObjectByHeapObjectId: CdpCommandAlias; - getSamplingProfile: CdpOptionalCommandAlias; - startSampling: CdpOptionalCommandAlias; - startTrackingHeapObjects: CdpOptionalCommandAlias; - stopSampling: CdpOptionalCommandAlias; - stopTrackingHeapObjects: CdpOptionalCommandAlias; - takeHeapSnapshot: CdpOptionalCommandAlias; - addHeapSnapshotChunk: CdpEventAlias; - heapStatsUpdate: CdpEventAlias; - lastSeenObjectId: CdpEventAlias; - reportHeapSnapshotProgress: CdpEventAlias; - resetProfiles: CdpEventAlias; - }; - IndexedDB: { - clearObjectStore: CdpCommandAlias; - deleteDatabase: CdpCommandAlias; - deleteObjectStoreEntries: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - requestData: CdpCommandAlias; - getMetadata: CdpCommandAlias; - requestDatabase: CdpCommandAlias; - requestDatabaseNames: CdpOptionalCommandAlias; - }; - Input: { - dispatchDragEvent: CdpCommandAlias; - dispatchKeyEvent: CdpCommandAlias; - insertText: CdpCommandAlias; - imeSetComposition: CdpCommandAlias; - dispatchMouseEvent: CdpCommandAlias; - dispatchTouchEvent: CdpCommandAlias; - cancelDragging: CdpOptionalCommandAlias; - emulateTouchFromMouseEvent: CdpCommandAlias; - setIgnoreInputEvents: CdpCommandAlias; - setInterceptDrags: CdpCommandAlias; - synthesizePinchGesture: CdpCommandAlias; - synthesizeScrollGesture: CdpCommandAlias; - synthesizeTapGesture: CdpCommandAlias; - dragIntercepted: CdpEventAlias; - }; - Inspector: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - detached: CdpEventAlias; - targetCrashed: CdpEventAlias; - targetReloadedAfterCrash: CdpEventAlias; - workerScriptLoaded: CdpEventAlias; - }; - IO: { - close: CdpCommandAlias; - read: CdpCommandAlias; - resolveBlob: CdpCommandAlias; - }; - LayerTree: { - compositingReasons: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - loadSnapshot: CdpCommandAlias; - makeSnapshot: CdpCommandAlias; - profileSnapshot: CdpCommandAlias; - releaseSnapshot: CdpCommandAlias; - replaySnapshot: CdpCommandAlias; - snapshotCommandLog: CdpCommandAlias; - layerPainted: CdpEventAlias; - layerTreeDidChange: CdpEventAlias; - }; - Log: { - clear: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - startViolationsReport: CdpCommandAlias; - stopViolationsReport: CdpOptionalCommandAlias; - entryAdded: CdpEventAlias; - }; - Media: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - playerPropertiesChanged: CdpEventAlias; - playerEventsAdded: CdpEventAlias; - playerMessagesLogged: CdpEventAlias; - playerErrorsRaised: CdpEventAlias; - playerCreated: CdpEventAlias; - }; - Memory: { - getDOMCounters: CdpOptionalCommandAlias; - getDOMCountersForLeakDetection: CdpOptionalCommandAlias; - prepareForLeakDetection: CdpOptionalCommandAlias; - forciblyPurgeJavaScriptMemory: CdpOptionalCommandAlias; - setPressureNotificationsSuppressed: CdpCommandAlias; - simulatePressureNotification: CdpCommandAlias; - startSampling: CdpOptionalCommandAlias; - stopSampling: CdpOptionalCommandAlias; - getAllTimeSamplingProfile: CdpOptionalCommandAlias; - getBrowserSamplingProfile: CdpOptionalCommandAlias; - getSamplingProfile: CdpOptionalCommandAlias; - }; - Network: { - setAcceptedEncodings: CdpCommandAlias; - clearAcceptedEncodingsOverride: CdpOptionalCommandAlias; - canClearBrowserCache: CdpOptionalCommandAlias; - canClearBrowserCookies: CdpOptionalCommandAlias; - canEmulateNetworkConditions: CdpOptionalCommandAlias; - clearBrowserCache: CdpOptionalCommandAlias; - clearBrowserCookies: CdpOptionalCommandAlias; - continueInterceptedRequest: CdpCommandAlias; - deleteCookies: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - emulateNetworkConditions: CdpCommandAlias; - emulateNetworkConditionsByRule: CdpCommandAlias; - overrideNetworkState: CdpCommandAlias; - enable: CdpOptionalCommandAlias; - configureDurableMessages: CdpOptionalCommandAlias; - getAllCookies: CdpOptionalCommandAlias; - getCertificate: CdpCommandAlias; - getCookies: CdpOptionalCommandAlias; - getResponseBody: CdpCommandAlias; - getRequestPostData: CdpCommandAlias; - getResponseBodyForInterception: CdpCommandAlias; - takeResponseBodyForInterceptionAsStream: CdpCommandAlias; - replayXHR: CdpCommandAlias; - searchInResponseBody: CdpCommandAlias; - setBlockedURLs: CdpOptionalCommandAlias; - setBypassServiceWorker: CdpCommandAlias; - setCacheDisabled: CdpCommandAlias; - setCookie: CdpCommandAlias; - setCookies: CdpCommandAlias; - setExtraHTTPHeaders: CdpCommandAlias; - setAttachDebugStack: CdpCommandAlias; - setRequestInterception: CdpCommandAlias; - setUserAgentOverride: CdpCommandAlias; - streamResourceContent: CdpCommandAlias; - getSecurityIsolationStatus: CdpOptionalCommandAlias; - enableReportingApi: CdpCommandAlias; - enableDeviceBoundSessions: CdpCommandAlias; - deleteDeviceBoundSession: CdpCommandAlias; - fetchSchemefulSite: CdpCommandAlias; - loadNetworkResource: CdpCommandAlias; - setCookieControls: CdpCommandAlias; - dataReceived: CdpEventAlias; - eventSourceMessageReceived: CdpEventAlias; - loadingFailed: CdpEventAlias; - loadingFinished: CdpEventAlias; - requestIntercepted: CdpEventAlias; - requestServedFromCache: CdpEventAlias; - requestWillBeSent: CdpEventAlias; - resourceChangedPriority: CdpEventAlias; - signedExchangeReceived: CdpEventAlias; - responseReceived: CdpEventAlias; - webSocketClosed: CdpEventAlias; - webSocketCreated: CdpEventAlias; - webSocketFrameError: CdpEventAlias; - webSocketFrameReceived: CdpEventAlias; - webSocketFrameSent: CdpEventAlias; - webSocketHandshakeResponseReceived: CdpEventAlias; - webSocketWillSendHandshakeRequest: CdpEventAlias; - webTransportCreated: CdpEventAlias; - webTransportConnectionEstablished: CdpEventAlias; - webTransportClosed: CdpEventAlias; - directTCPSocketCreated: CdpEventAlias; - directTCPSocketOpened: CdpEventAlias; - directTCPSocketAborted: CdpEventAlias; - directTCPSocketClosed: CdpEventAlias; - directTCPSocketChunkSent: CdpEventAlias; - directTCPSocketChunkReceived: CdpEventAlias; - directUDPSocketJoinedMulticastGroup: CdpEventAlias; - directUDPSocketLeftMulticastGroup: CdpEventAlias; - directUDPSocketCreated: CdpEventAlias; - directUDPSocketOpened: CdpEventAlias; - directUDPSocketAborted: CdpEventAlias; - directUDPSocketClosed: CdpEventAlias; - directUDPSocketChunkSent: CdpEventAlias; - directUDPSocketChunkReceived: CdpEventAlias; - requestWillBeSentExtraInfo: CdpEventAlias; - responseReceivedExtraInfo: CdpEventAlias; - responseReceivedEarlyHints: CdpEventAlias; - trustTokenOperationDone: CdpEventAlias; - policyUpdated: CdpEventAlias; - reportingApiReportAdded: CdpEventAlias; - reportingApiReportUpdated: CdpEventAlias; - reportingApiEndpointsChangedForOrigin: CdpEventAlias; - deviceBoundSessionsAdded: CdpEventAlias; - deviceBoundSessionEventOccurred: CdpEventAlias; - }; - Overlay: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getHighlightObjectForTest: CdpCommandAlias; - getGridHighlightObjectsForTest: CdpCommandAlias; - getSourceOrderHighlightObjectForTest: CdpCommandAlias; - hideHighlight: CdpOptionalCommandAlias; - highlightFrame: CdpCommandAlias; - highlightNode: CdpCommandAlias; - highlightQuad: CdpCommandAlias; - highlightRect: CdpCommandAlias; - highlightSourceOrder: CdpCommandAlias; - setInspectMode: CdpCommandAlias; - setShowAdHighlights: CdpCommandAlias; - setPausedInDebuggerMessage: CdpOptionalCommandAlias; - setShowDebugBorders: CdpCommandAlias; - setShowFPSCounter: CdpCommandAlias; - setShowGridOverlays: CdpCommandAlias; - setShowFlexOverlays: CdpCommandAlias; - setShowScrollSnapOverlays: CdpCommandAlias; - setShowContainerQueryOverlays: CdpCommandAlias; - setShowInspectedElementAnchor: CdpCommandAlias; - setShowPaintRects: CdpCommandAlias; - setShowLayoutShiftRegions: CdpCommandAlias; - setShowScrollBottleneckRects: CdpCommandAlias; - setShowHitTestBorders: CdpCommandAlias; - setShowWebVitals: CdpCommandAlias; - setShowViewportSizeOnResize: CdpCommandAlias; - setShowHinge: CdpOptionalCommandAlias; - setShowIsolatedElements: CdpCommandAlias; - setShowWindowControlsOverlay: CdpOptionalCommandAlias; - inspectNodeRequested: CdpEventAlias; - nodeHighlightRequested: CdpEventAlias; - screenshotRequested: CdpEventAlias; - inspectPanelShowRequested: CdpEventAlias; - inspectedElementWindowRestored: CdpEventAlias; - inspectModeCanceled: CdpEventAlias; - }; - Page: { - addScriptToEvaluateOnLoad: CdpCommandAlias; - addScriptToEvaluateOnNewDocument: CdpCommandAlias; - bringToFront: CdpOptionalCommandAlias; - captureScreenshot: CdpOptionalCommandAlias; - captureSnapshot: CdpOptionalCommandAlias; - clearDeviceMetricsOverride: CdpOptionalCommandAlias; - clearDeviceOrientationOverride: CdpOptionalCommandAlias; - clearGeolocationOverride: CdpOptionalCommandAlias; - createIsolatedWorld: CdpCommandAlias; - deleteCookie: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getAppManifest: CdpOptionalCommandAlias; - getInstallabilityErrors: CdpOptionalCommandAlias; - getManifestIcons: CdpOptionalCommandAlias; - getAppId: CdpOptionalCommandAlias; - getAdScriptAncestry: CdpCommandAlias; - getFrameTree: CdpOptionalCommandAlias; - getLayoutMetrics: CdpOptionalCommandAlias; - getNavigationHistory: CdpOptionalCommandAlias; - resetNavigationHistory: CdpOptionalCommandAlias; - getResourceContent: CdpCommandAlias; - getResourceTree: CdpOptionalCommandAlias; - handleJavaScriptDialog: CdpCommandAlias; - navigate: CdpCommandAlias; - navigateToHistoryEntry: CdpCommandAlias; - printToPDF: CdpOptionalCommandAlias; - reload: CdpOptionalCommandAlias; - removeScriptToEvaluateOnLoad: CdpCommandAlias; - removeScriptToEvaluateOnNewDocument: CdpCommandAlias; - screencastFrameAck: CdpCommandAlias; - searchInResource: CdpCommandAlias; - setAdBlockingEnabled: CdpCommandAlias; - setBypassCSP: CdpCommandAlias; - getPermissionsPolicyState: CdpCommandAlias; - getOriginTrials: CdpCommandAlias; - setDeviceMetricsOverride: CdpCommandAlias; - setDeviceOrientationOverride: CdpCommandAlias; - setFontFamilies: CdpCommandAlias; - setFontSizes: CdpCommandAlias; - setDocumentContent: CdpCommandAlias; - setDownloadBehavior: CdpCommandAlias; - setGeolocationOverride: CdpOptionalCommandAlias; - setLifecycleEventsEnabled: CdpCommandAlias; - setTouchEmulationEnabled: CdpCommandAlias; - startScreencast: CdpOptionalCommandAlias; - stopLoading: CdpOptionalCommandAlias; - crash: CdpOptionalCommandAlias; - close: CdpOptionalCommandAlias; - setWebLifecycleState: CdpCommandAlias; - stopScreencast: CdpOptionalCommandAlias; - produceCompilationCache: CdpCommandAlias; - addCompilationCache: CdpCommandAlias; - clearCompilationCache: CdpOptionalCommandAlias; - setSPCTransactionMode: CdpCommandAlias; - setRPHRegistrationMode: CdpCommandAlias; - generateTestReport: CdpCommandAlias; - waitForDebugger: CdpOptionalCommandAlias; - setInterceptFileChooserDialog: CdpCommandAlias; - setPrerenderingAllowed: CdpCommandAlias; - getAnnotatedPageContent: CdpOptionalCommandAlias; - domContentEventFired: CdpEventAlias; - fileChooserOpened: CdpEventAlias; - frameAttached: CdpEventAlias; - frameClearedScheduledNavigation: CdpEventAlias; - frameDetached: CdpEventAlias; - frameSubtreeWillBeDetached: CdpEventAlias; - frameNavigated: CdpEventAlias; - documentOpened: CdpEventAlias; - frameResized: CdpEventAlias; - frameStartedNavigating: CdpEventAlias; - frameRequestedNavigation: CdpEventAlias; - frameScheduledNavigation: CdpEventAlias; - frameStartedLoading: CdpEventAlias; - frameStoppedLoading: CdpEventAlias; - downloadWillBegin: CdpEventAlias; - downloadProgress: CdpEventAlias; - interstitialHidden: CdpEventAlias; - interstitialShown: CdpEventAlias; - javascriptDialogClosed: CdpEventAlias; - javascriptDialogOpening: CdpEventAlias; - lifecycleEvent: CdpEventAlias; - backForwardCacheNotUsed: CdpEventAlias; - loadEventFired: CdpEventAlias; - navigatedWithinDocument: CdpEventAlias; - screencastFrame: CdpEventAlias; - screencastVisibilityChanged: CdpEventAlias; - windowOpen: CdpEventAlias; - compilationCacheProduced: CdpEventAlias; - }; - Performance: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - setTimeDomain: CdpCommandAlias; - getMetrics: CdpOptionalCommandAlias; - metrics: CdpEventAlias; - }; - PerformanceTimeline: { - enable: CdpCommandAlias; - timelineEventAdded: CdpEventAlias; - }; - Preload: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - ruleSetUpdated: CdpEventAlias; - ruleSetRemoved: CdpEventAlias; - preloadEnabledStateUpdated: CdpEventAlias; - prefetchStatusUpdated: CdpEventAlias; - prerenderStatusUpdated: CdpEventAlias; - preloadingAttemptSourcesUpdated: CdpEventAlias; - }; - Profiler: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - getBestEffortCoverage: CdpOptionalCommandAlias; - setSamplingInterval: CdpCommandAlias; - start: CdpOptionalCommandAlias; - startPreciseCoverage: CdpOptionalCommandAlias; - stop: CdpOptionalCommandAlias; - stopPreciseCoverage: CdpOptionalCommandAlias; - takePreciseCoverage: CdpOptionalCommandAlias; - consoleProfileFinished: CdpEventAlias; - consoleProfileStarted: CdpEventAlias; - preciseCoverageDeltaUpdate: CdpEventAlias; - }; - PWA: { - getOsAppState: CdpCommandAlias; - install: CdpCommandAlias; - uninstall: CdpCommandAlias; - launch: CdpCommandAlias; - launchFilesInApp: CdpCommandAlias; - openCurrentPageInApp: CdpCommandAlias; - changeAppUserSettings: CdpCommandAlias; - }; - Runtime: { - awaitPromise: CdpCommandAlias; - callFunctionOn: CdpCommandAlias; - compileScript: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - discardConsoleEntries: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - evaluate: CdpCommandAlias; - getIsolateId: CdpOptionalCommandAlias; - getHeapUsage: CdpOptionalCommandAlias; - getProperties: CdpCommandAlias; - globalLexicalScopeNames: CdpOptionalCommandAlias; - queryObjects: CdpCommandAlias; - releaseObject: CdpCommandAlias; - releaseObjectGroup: CdpCommandAlias; - runIfWaitingForDebugger: CdpOptionalCommandAlias; - runScript: CdpCommandAlias; - setAsyncCallStackDepth: CdpCommandAlias; - setCustomObjectFormatterEnabled: CdpCommandAlias; - setMaxCallStackSizeToCapture: CdpCommandAlias; - terminateExecution: CdpOptionalCommandAlias; - addBinding: CdpCommandAlias; - removeBinding: CdpCommandAlias; - getExceptionDetails: CdpCommandAlias; - bindingCalled: CdpEventAlias; - consoleAPICalled: CdpEventAlias; - exceptionRevoked: CdpEventAlias; - exceptionThrown: CdpEventAlias; - executionContextCreated: CdpEventAlias; - executionContextDestroyed: CdpEventAlias; - executionContextsCleared: CdpEventAlias; - inspectRequested: CdpEventAlias; - }; - Schema: { - getDomains: CdpOptionalCommandAlias; - }; - Security: { - disable: CdpOptionalCommandAlias; - enable: CdpOptionalCommandAlias; - setIgnoreCertificateErrors: CdpCommandAlias; - handleCertificateError: CdpCommandAlias; - setOverrideCertificateErrors: CdpCommandAlias; - certificateError: CdpEventAlias; - visibleSecurityStateChanged: CdpEventAlias; - securityStateChanged: CdpEventAlias; - }; - ServiceWorker: { - deliverPushMessage: CdpCommandAlias; - disable: CdpOptionalCommandAlias; - dispatchSyncEvent: CdpCommandAlias; - dispatchPeriodicSyncEvent: CdpCommandAlias; - enable: CdpOptionalCommandAlias; - setForceUpdateOnPageLoad: CdpCommandAlias; - skipWaiting: CdpCommandAlias; - startWorker: CdpCommandAlias; - stopAllWorkers: CdpOptionalCommandAlias; - stopWorker: CdpCommandAlias; - unregister: CdpCommandAlias; - updateRegistration: CdpCommandAlias; - workerErrorReported: CdpEventAlias; - workerRegistrationUpdated: CdpEventAlias; - workerVersionUpdated: CdpEventAlias; - }; - SmartCardEmulation: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - reportEstablishContextResult: CdpCommandAlias; - reportReleaseContextResult: CdpCommandAlias; - reportListReadersResult: CdpCommandAlias; - reportGetStatusChangeResult: CdpCommandAlias; - reportBeginTransactionResult: CdpCommandAlias; - reportPlainResult: CdpCommandAlias; - reportConnectResult: CdpCommandAlias; - reportDataResult: CdpCommandAlias; - reportStatusResult: CdpCommandAlias; - reportError: CdpCommandAlias; - establishContextRequested: CdpEventAlias; - releaseContextRequested: CdpEventAlias; - listReadersRequested: CdpEventAlias; - getStatusChangeRequested: CdpEventAlias; - cancelRequested: CdpEventAlias; - connectRequested: CdpEventAlias; - disconnectRequested: CdpEventAlias; - transmitRequested: CdpEventAlias; - controlRequested: CdpEventAlias; - getAttribRequested: CdpEventAlias; - setAttribRequested: CdpEventAlias; - statusRequested: CdpEventAlias; - beginTransactionRequested: CdpEventAlias; - endTransactionRequested: CdpEventAlias; - }; - Storage: { - getStorageKeyForFrame: CdpCommandAlias; - getStorageKey: CdpOptionalCommandAlias; - clearDataForOrigin: CdpCommandAlias; - clearDataForStorageKey: CdpCommandAlias; - getCookies: CdpOptionalCommandAlias; - setCookies: CdpCommandAlias; - clearCookies: CdpOptionalCommandAlias; - getUsageAndQuota: CdpCommandAlias; - overrideQuotaForOrigin: CdpCommandAlias; - trackCacheStorageForOrigin: CdpCommandAlias; - trackCacheStorageForStorageKey: CdpCommandAlias; - trackIndexedDBForOrigin: CdpCommandAlias; - trackIndexedDBForStorageKey: CdpCommandAlias; - untrackCacheStorageForOrigin: CdpCommandAlias; - untrackCacheStorageForStorageKey: CdpCommandAlias; - untrackIndexedDBForOrigin: CdpCommandAlias; - untrackIndexedDBForStorageKey: CdpCommandAlias; - getTrustTokens: CdpOptionalCommandAlias; - clearTrustTokens: CdpCommandAlias; - getInterestGroupDetails: CdpCommandAlias; - setInterestGroupTracking: CdpCommandAlias; - setInterestGroupAuctionTracking: CdpCommandAlias; - getSharedStorageMetadata: CdpCommandAlias; - getSharedStorageEntries: CdpCommandAlias; - setSharedStorageEntry: CdpCommandAlias; - deleteSharedStorageEntry: CdpCommandAlias; - clearSharedStorageEntries: CdpCommandAlias; - resetSharedStorageBudget: CdpCommandAlias; - setSharedStorageTracking: CdpCommandAlias; - setStorageBucketTracking: CdpCommandAlias; - deleteStorageBucket: CdpCommandAlias; - runBounceTrackingMitigations: CdpOptionalCommandAlias; - getRelatedWebsiteSets: CdpOptionalCommandAlias; - setProtectedAudienceKAnonymity: CdpCommandAlias; - cacheStorageContentUpdated: CdpEventAlias; - cacheStorageListUpdated: CdpEventAlias; - indexedDBContentUpdated: CdpEventAlias; - indexedDBListUpdated: CdpEventAlias; - interestGroupAccessed: CdpEventAlias; - interestGroupAuctionEventOccurred: CdpEventAlias; - interestGroupAuctionNetworkRequestCreated: CdpEventAlias; - sharedStorageAccessed: CdpEventAlias; - sharedStorageWorkletOperationExecutionFinished: CdpEventAlias; - storageBucketCreatedOrUpdated: CdpEventAlias; - storageBucketDeleted: CdpEventAlias; - }; - SystemInfo: { - getInfo: CdpOptionalCommandAlias; - getFeatureState: CdpCommandAlias; - getProcessInfo: CdpOptionalCommandAlias; - }; - Target: { - activateTarget: CdpCommandAlias; - attachToTarget: CdpCommandAlias; - attachToBrowserTarget: CdpOptionalCommandAlias; - closeTarget: CdpCommandAlias; - exposeDevToolsProtocol: CdpCommandAlias; - createBrowserContext: CdpOptionalCommandAlias; - getBrowserContexts: CdpOptionalCommandAlias; - createTarget: CdpCommandAlias; - detachFromTarget: CdpOptionalCommandAlias; - disposeBrowserContext: CdpCommandAlias; - getTargetInfo: CdpOptionalCommandAlias; - getTargets: CdpOptionalCommandAlias; - sendMessageToTarget: CdpCommandAlias; - setAutoAttach: CdpCommandAlias; - autoAttachRelated: CdpCommandAlias; - setDiscoverTargets: CdpCommandAlias; - setRemoteLocations: CdpCommandAlias; - getDevToolsTarget: CdpCommandAlias; - openDevTools: CdpCommandAlias; - attachedToTarget: CdpEventAlias; - detachedFromTarget: CdpEventAlias; - receivedMessageFromTarget: CdpEventAlias; - targetCreated: CdpEventAlias; - targetDestroyed: CdpEventAlias; - targetCrashed: CdpEventAlias; - targetInfoChanged: CdpEventAlias; - }; - Tethering: { - bind: CdpCommandAlias; - unbind: CdpCommandAlias; - accepted: CdpEventAlias; - }; - Tracing: { - end: CdpOptionalCommandAlias; - getCategories: CdpOptionalCommandAlias; - getTrackEventDescriptor: CdpOptionalCommandAlias; - recordClockSyncMarker: CdpCommandAlias; - requestMemoryDump: CdpOptionalCommandAlias; - start: CdpOptionalCommandAlias; - bufferUsage: CdpEventAlias; - dataCollected: CdpEventAlias; - tracingComplete: CdpEventAlias; - }; - WebAudio: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - getRealtimeData: CdpCommandAlias; - contextCreated: CdpEventAlias; - contextWillBeDestroyed: CdpEventAlias; - contextChanged: CdpEventAlias; - audioListenerCreated: CdpEventAlias; - audioListenerWillBeDestroyed: CdpEventAlias; - audioNodeCreated: CdpEventAlias; - audioNodeWillBeDestroyed: CdpEventAlias; - audioParamCreated: CdpEventAlias; - audioParamWillBeDestroyed: CdpEventAlias; - nodesConnected: CdpEventAlias; - nodesDisconnected: CdpEventAlias; - nodeParamConnected: CdpEventAlias; - nodeParamDisconnected: CdpEventAlias; +type DomainName = TName extends `${infer TDomain}.${string}` ? TDomain : never; +type MemberName = TName extends `${string}.${infer TMember}` ? TMember : never; +type RequiredKeys = TValue extends object + ? { [TKey in keyof TValue]-?: {} extends Pick ? never : TKey }[keyof TValue] + : keyof TValue; +type HasRequiredParams = TParams extends object + ? [RequiredKeys] extends [never] + ? false + : true + : true; +type NativeCommandAliasSpec = TCommand extends { + params: infer TParams extends z.ZodType; + result: infer TResult extends z.ZodType; +} + ? { params: z.input; result: z.output } + : never; +type NativeEventAliasSpec = TEvent extends z.ZodType ? z.output : never; +type CustomCommandAliasSpec = { + params: TCommand["params_schema"] extends z.ZodType ? z.input : ProtocolParams; + result: TCommand["result_schema"] extends z.ZodType ? z.output : ProtocolResult; +}; +type CustomEventAliasSpec = TEvent["event_schema"] extends z.ZodType + ? z.output + : ProtocolPayload; +type ModCommandBase = + Extract extends infer TKey + ? TKey extends `${infer TBase}Params` + ? `${TBase}Response` extends keyof typeof Mod + ? TBase + : never + : never + : never; +type ModEventBase = + Extract extends infer TKey + ? TKey extends `${infer TBase}Event` + ? TBase + : never + : never; +type ModParamsSchema = Extract< + (typeof Mod)[Extract<`${TBase}Params`, keyof typeof Mod>], + z.ZodType +>; +type ModResponseSchema = Extract< + (typeof Mod)[Extract<`${TBase}Response`, keyof typeof Mod>], + z.ZodType +>; +type ModEventSchema = Extract< + (typeof Mod)[Extract<`${TBase}Event`, keyof typeof Mod>], + z.ZodType +>; +type NativeCommandAliasSpecs = { + [TName in keyof typeof commands & string]: NativeCommandAliasSpec<(typeof commands)[TName]>; +}; +type NativeEventAliasSpecs = { [TName in keyof typeof events & string]: NativeEventAliasSpec<(typeof events)[TName]> }; +type ModCommandAliasSpecs = { + [TBase in ModCommandBase as `Mod.${Uncapitalize}`]: { + params: z.input>; + result: z.output>; }; - WebAuthn: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - addVirtualAuthenticator: CdpCommandAlias; - setResponseOverrideBits: CdpCommandAlias; - removeVirtualAuthenticator: CdpCommandAlias; - addCredential: CdpCommandAlias; - getCredential: CdpCommandAlias; - getCredentials: CdpCommandAlias; - removeCredential: CdpCommandAlias; - clearCredentials: CdpCommandAlias; - setUserVerified: CdpCommandAlias; - setAutomaticPresenceSimulation: CdpCommandAlias; - setCredentialProperties: CdpCommandAlias; - credentialAdded: CdpEventAlias; - credentialDeleted: CdpEventAlias; - credentialUpdated: CdpEventAlias; - credentialAsserted: CdpEventAlias; +}; +type ModEventAliasSpecs = { [TBase in ModEventBase as `Mod.${Uncapitalize}`]: z.output> }; +type CustomCommandAliasSpecs = { + [TName in keyof TCommands & string]: CustomCommandAliasSpec; +}; +type CustomEventAliasSpecs = { + [TName in keyof TEvents & string]: CustomEventAliasSpec; +}; +type CommandAliasSpecs = NativeCommandAliasSpecs & + ModCommandAliasSpecs & + CustomCommandAliasSpecs; +type EventAliasSpecs = NativeEventAliasSpecs & + ModEventAliasSpecs & + CustomEventAliasSpecs; +type CommandAlias = + HasRequiredParams extends true + ? CdpCommandAlias + : CdpOptionalCommandAlias; +type CommandAliases> = { + [TDomain in DomainName>]: { + [TName in Extract as MemberName>]: CommandAlias< + TSpecs[TName], + Extract + >; }; - WebMCP: { - enable: CdpOptionalCommandAlias; - disable: CdpOptionalCommandAlias; - invokeTool: CdpCommandAlias; - cancelInvocation: CdpCommandAlias; - toolsAdded: CdpEventAlias; - toolsRemoved: CdpEventAlias; - toolInvoked: CdpEventAlias; - toolResponded: CdpEventAlias; +}; +type EventAliases> = { + [TDomain in DomainName>]: { + [TName in Extract as MemberName>]: CdpEventAlias< + TSpecs[TName], + Extract + >; }; +}; +type MergeDomainAliases = { + [TDomain in keyof (CommandAliases> & + EventAliases>)]: TDomain extends keyof CommandAliases> + ? TDomain extends keyof EventAliases> + ? CommandAliases>[TDomain] & EventAliases>[TDomain] + : CommandAliases>[TDomain] + : TDomain extends keyof EventAliases> + ? EventAliases>[TDomain] + : never; +}; +type ModAliasOverrides = { Mod: { - evaluate(params: cdp.types.ts.Mod.EvaluateParams): Promise; addCustomCommand( name: TName, - options?: ModCustomCommandOptions, - ): Promise; - addCustomCommand(params: cdp.types.ts.Mod.AddCustomCommandParams): Promise; + config?: ModCustomCommandConfig, + ): Promise>; + addCustomCommand( + params: z.input, + ): Promise>; addCustomEvent( name: TName, - options?: { event_schema?: TEventSchema | null }, - ): Promise; - addCustomEvent(params: cdp.types.ts.Mod.AddCustomEventParams): Promise; - addMiddleware(params: cdp.types.ts.Mod.AddMiddlewareParams): Promise; - configure(params: cdp.types.ts.Mod.ConfigureParams): Promise; - ping(params?: cdp.types.ts.Mod.PingParams): Promise; + config?: { event_schema?: TEventSchema | null }, + ): Promise>; + addCustomEvent( + params: z.input, + ): Promise>; }; }; +export type CdpEventPayloads = EventAliasSpecs; +export type CdpCommandAliases = CommandAliases>; +export type CdpAliases = { + readonly types: typeof runtimeTypes; + readonly commands: typeof commands; + readonly events: typeof events; + readonly REQUEST: "request"; + readonly RESPONSE: "response"; + readonly EVENT: "event"; +} & MergeDomainAliases & + ModAliasOverrides; -function withCdpName(value: T, id: Name, kind: Kind): T & CdpNamedValue { +function withCdpName( + value: T, + id: Name, + kind: Kind, +): T & CdpNamedValue { Object.defineProperties(value, { id: { value: id, enumerable: true, configurable: true }, name: { value: id, configurable: true }, kind: { value: kind, enumerable: true, configurable: true }, }); - if (!("meta" in value)) Object.defineProperty(value, "meta", { value: () => ({ id, name: id, kind }), configurable: true }); + if (!("meta" in value)) + Object.defineProperty(value, "meta", { value: () => ({ id, name: id, kind }), configurable: true }); return value as T & CdpNamedValue; } @@ -1082,1076 +192,7073 @@ export function createCdpAliases(send: CdpAliasSend, hooks: CdpAliasHooks = {}): RESPONSE: "response", EVENT: "event", Accessibility: { - disable: withCdpName(async (params?: unknown) => commands["Accessibility.disable"].result.parse(await send("Accessibility.disable", commands["Accessibility.disable"].params.parse(params ?? {}))), "Accessibility.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Accessibility.enable"].result.parse(await send("Accessibility.enable", commands["Accessibility.enable"].params.parse(params ?? {}))), "Accessibility.enable", "command"), - getPartialAXTree: withCdpName(async (params?: unknown) => commands["Accessibility.getPartialAXTree"].result.parse(await send("Accessibility.getPartialAXTree", commands["Accessibility.getPartialAXTree"].params.parse(params ?? {}))), "Accessibility.getPartialAXTree", "command"), - getFullAXTree: withCdpName(async (params?: unknown) => commands["Accessibility.getFullAXTree"].result.parse(await send("Accessibility.getFullAXTree", commands["Accessibility.getFullAXTree"].params.parse(params ?? {}))), "Accessibility.getFullAXTree", "command"), - getRootAXNode: withCdpName(async (params?: unknown) => commands["Accessibility.getRootAXNode"].result.parse(await send("Accessibility.getRootAXNode", commands["Accessibility.getRootAXNode"].params.parse(params ?? {}))), "Accessibility.getRootAXNode", "command"), - getAXNodeAndAncestors: withCdpName(async (params?: unknown) => commands["Accessibility.getAXNodeAndAncestors"].result.parse(await send("Accessibility.getAXNodeAndAncestors", commands["Accessibility.getAXNodeAndAncestors"].params.parse(params ?? {}))), "Accessibility.getAXNodeAndAncestors", "command"), - getChildAXNodes: withCdpName(async (params?: unknown) => commands["Accessibility.getChildAXNodes"].result.parse(await send("Accessibility.getChildAXNodes", commands["Accessibility.getChildAXNodes"].params.parse(params ?? {}))), "Accessibility.getChildAXNodes", "command"), - queryAXTree: withCdpName(async (params?: unknown) => commands["Accessibility.queryAXTree"].result.parse(await send("Accessibility.queryAXTree", commands["Accessibility.queryAXTree"].params.parse(params ?? {}))), "Accessibility.queryAXTree", "command"), - loadComplete: events["Accessibility.loadComplete"] as CdpEventAlias, - nodesUpdated: events["Accessibility.nodesUpdated"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Accessibility.DisableParams) => + commands["Accessibility.disable"].result.parse( + await send("Accessibility.disable", commands["Accessibility.disable"].params.parse(params ?? {})), + ), + "Accessibility.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Accessibility.EnableParams) => + commands["Accessibility.enable"].result.parse( + await send("Accessibility.enable", commands["Accessibility.enable"].params.parse(params ?? {})), + ), + "Accessibility.enable", + "command", + ), + getPartialAXTree: withCdpName( + async (params?: cdp.types.ts.Accessibility.GetPartialAXTreeParams) => + commands["Accessibility.getPartialAXTree"].result.parse( + await send( + "Accessibility.getPartialAXTree", + commands["Accessibility.getPartialAXTree"].params.parse(params ?? {}), + ), + ), + "Accessibility.getPartialAXTree", + "command", + ), + getFullAXTree: withCdpName( + async (params?: cdp.types.ts.Accessibility.GetFullAXTreeParams) => + commands["Accessibility.getFullAXTree"].result.parse( + await send( + "Accessibility.getFullAXTree", + commands["Accessibility.getFullAXTree"].params.parse(params ?? {}), + ), + ), + "Accessibility.getFullAXTree", + "command", + ), + getRootAXNode: withCdpName( + async (params?: cdp.types.ts.Accessibility.GetRootAXNodeParams) => + commands["Accessibility.getRootAXNode"].result.parse( + await send( + "Accessibility.getRootAXNode", + commands["Accessibility.getRootAXNode"].params.parse(params ?? {}), + ), + ), + "Accessibility.getRootAXNode", + "command", + ), + getAXNodeAndAncestors: withCdpName( + async (params?: cdp.types.ts.Accessibility.GetAXNodeAndAncestorsParams) => + commands["Accessibility.getAXNodeAndAncestors"].result.parse( + await send( + "Accessibility.getAXNodeAndAncestors", + commands["Accessibility.getAXNodeAndAncestors"].params.parse(params ?? {}), + ), + ), + "Accessibility.getAXNodeAndAncestors", + "command", + ), + getChildAXNodes: withCdpName( + async (params?: cdp.types.ts.Accessibility.GetChildAXNodesParams) => + commands["Accessibility.getChildAXNodes"].result.parse( + await send( + "Accessibility.getChildAXNodes", + commands["Accessibility.getChildAXNodes"].params.parse(params ?? {}), + ), + ), + "Accessibility.getChildAXNodes", + "command", + ), + queryAXTree: withCdpName( + async (params?: cdp.types.ts.Accessibility.QueryAXTreeParams) => + commands["Accessibility.queryAXTree"].result.parse( + await send("Accessibility.queryAXTree", commands["Accessibility.queryAXTree"].params.parse(params ?? {})), + ), + "Accessibility.queryAXTree", + "command", + ), + loadComplete: withCdpName(events["Accessibility.loadComplete"], "Accessibility.loadComplete", "event"), + nodesUpdated: withCdpName(events["Accessibility.nodesUpdated"], "Accessibility.nodesUpdated", "event"), }, Animation: { - disable: withCdpName(async (params?: unknown) => commands["Animation.disable"].result.parse(await send("Animation.disable", commands["Animation.disable"].params.parse(params ?? {}))), "Animation.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Animation.enable"].result.parse(await send("Animation.enable", commands["Animation.enable"].params.parse(params ?? {}))), "Animation.enable", "command"), - getCurrentTime: withCdpName(async (params?: unknown) => commands["Animation.getCurrentTime"].result.parse(await send("Animation.getCurrentTime", commands["Animation.getCurrentTime"].params.parse(params ?? {}))), "Animation.getCurrentTime", "command"), - getPlaybackRate: withCdpName(async (params?: unknown) => commands["Animation.getPlaybackRate"].result.parse(await send("Animation.getPlaybackRate", commands["Animation.getPlaybackRate"].params.parse(params ?? {}))), "Animation.getPlaybackRate", "command"), - releaseAnimations: withCdpName(async (params?: unknown) => commands["Animation.releaseAnimations"].result.parse(await send("Animation.releaseAnimations", commands["Animation.releaseAnimations"].params.parse(params ?? {}))), "Animation.releaseAnimations", "command"), - resolveAnimation: withCdpName(async (params?: unknown) => commands["Animation.resolveAnimation"].result.parse(await send("Animation.resolveAnimation", commands["Animation.resolveAnimation"].params.parse(params ?? {}))), "Animation.resolveAnimation", "command"), - seekAnimations: withCdpName(async (params?: unknown) => commands["Animation.seekAnimations"].result.parse(await send("Animation.seekAnimations", commands["Animation.seekAnimations"].params.parse(params ?? {}))), "Animation.seekAnimations", "command"), - setPaused: withCdpName(async (params?: unknown) => commands["Animation.setPaused"].result.parse(await send("Animation.setPaused", commands["Animation.setPaused"].params.parse(params ?? {}))), "Animation.setPaused", "command"), - setPlaybackRate: withCdpName(async (params?: unknown) => commands["Animation.setPlaybackRate"].result.parse(await send("Animation.setPlaybackRate", commands["Animation.setPlaybackRate"].params.parse(params ?? {}))), "Animation.setPlaybackRate", "command"), - setTiming: withCdpName(async (params?: unknown) => commands["Animation.setTiming"].result.parse(await send("Animation.setTiming", commands["Animation.setTiming"].params.parse(params ?? {}))), "Animation.setTiming", "command"), - animationCanceled: events["Animation.animationCanceled"] as CdpEventAlias, - animationCreated: events["Animation.animationCreated"] as CdpEventAlias, - animationStarted: events["Animation.animationStarted"] as CdpEventAlias, - animationUpdated: events["Animation.animationUpdated"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Animation.DisableParams) => + commands["Animation.disable"].result.parse( + await send("Animation.disable", commands["Animation.disable"].params.parse(params ?? {})), + ), + "Animation.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Animation.EnableParams) => + commands["Animation.enable"].result.parse( + await send("Animation.enable", commands["Animation.enable"].params.parse(params ?? {})), + ), + "Animation.enable", + "command", + ), + getCurrentTime: withCdpName( + async (params?: cdp.types.ts.Animation.GetCurrentTimeParams) => + commands["Animation.getCurrentTime"].result.parse( + await send("Animation.getCurrentTime", commands["Animation.getCurrentTime"].params.parse(params ?? {})), + ), + "Animation.getCurrentTime", + "command", + ), + getPlaybackRate: withCdpName( + async (params?: cdp.types.ts.Animation.GetPlaybackRateParams) => + commands["Animation.getPlaybackRate"].result.parse( + await send("Animation.getPlaybackRate", commands["Animation.getPlaybackRate"].params.parse(params ?? {})), + ), + "Animation.getPlaybackRate", + "command", + ), + releaseAnimations: withCdpName( + async (params?: cdp.types.ts.Animation.ReleaseAnimationsParams) => + commands["Animation.releaseAnimations"].result.parse( + await send( + "Animation.releaseAnimations", + commands["Animation.releaseAnimations"].params.parse(params ?? {}), + ), + ), + "Animation.releaseAnimations", + "command", + ), + resolveAnimation: withCdpName( + async (params?: cdp.types.ts.Animation.ResolveAnimationParams) => + commands["Animation.resolveAnimation"].result.parse( + await send("Animation.resolveAnimation", commands["Animation.resolveAnimation"].params.parse(params ?? {})), + ), + "Animation.resolveAnimation", + "command", + ), + seekAnimations: withCdpName( + async (params?: cdp.types.ts.Animation.SeekAnimationsParams) => + commands["Animation.seekAnimations"].result.parse( + await send("Animation.seekAnimations", commands["Animation.seekAnimations"].params.parse(params ?? {})), + ), + "Animation.seekAnimations", + "command", + ), + setPaused: withCdpName( + async (params?: cdp.types.ts.Animation.SetPausedParams) => + commands["Animation.setPaused"].result.parse( + await send("Animation.setPaused", commands["Animation.setPaused"].params.parse(params ?? {})), + ), + "Animation.setPaused", + "command", + ), + setPlaybackRate: withCdpName( + async (params?: cdp.types.ts.Animation.SetPlaybackRateParams) => + commands["Animation.setPlaybackRate"].result.parse( + await send("Animation.setPlaybackRate", commands["Animation.setPlaybackRate"].params.parse(params ?? {})), + ), + "Animation.setPlaybackRate", + "command", + ), + setTiming: withCdpName( + async (params?: cdp.types.ts.Animation.SetTimingParams) => + commands["Animation.setTiming"].result.parse( + await send("Animation.setTiming", commands["Animation.setTiming"].params.parse(params ?? {})), + ), + "Animation.setTiming", + "command", + ), + animationCanceled: withCdpName(events["Animation.animationCanceled"], "Animation.animationCanceled", "event"), + animationCreated: withCdpName(events["Animation.animationCreated"], "Animation.animationCreated", "event"), + animationStarted: withCdpName(events["Animation.animationStarted"], "Animation.animationStarted", "event"), + animationUpdated: withCdpName(events["Animation.animationUpdated"], "Animation.animationUpdated", "event"), }, Audits: { - getEncodedResponse: withCdpName(async (params?: unknown) => commands["Audits.getEncodedResponse"].result.parse(await send("Audits.getEncodedResponse", commands["Audits.getEncodedResponse"].params.parse(params ?? {}))), "Audits.getEncodedResponse", "command"), - disable: withCdpName(async (params?: unknown) => commands["Audits.disable"].result.parse(await send("Audits.disable", commands["Audits.disable"].params.parse(params ?? {}))), "Audits.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Audits.enable"].result.parse(await send("Audits.enable", commands["Audits.enable"].params.parse(params ?? {}))), "Audits.enable", "command"), - checkFormsIssues: withCdpName(async (params?: unknown) => commands["Audits.checkFormsIssues"].result.parse(await send("Audits.checkFormsIssues", commands["Audits.checkFormsIssues"].params.parse(params ?? {}))), "Audits.checkFormsIssues", "command"), - issueAdded: events["Audits.issueAdded"] as CdpEventAlias, + getEncodedResponse: withCdpName( + async (params?: cdp.types.ts.Audits.GetEncodedResponseParams) => + commands["Audits.getEncodedResponse"].result.parse( + await send("Audits.getEncodedResponse", commands["Audits.getEncodedResponse"].params.parse(params ?? {})), + ), + "Audits.getEncodedResponse", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Audits.DisableParams) => + commands["Audits.disable"].result.parse( + await send("Audits.disable", commands["Audits.disable"].params.parse(params ?? {})), + ), + "Audits.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Audits.EnableParams) => + commands["Audits.enable"].result.parse( + await send("Audits.enable", commands["Audits.enable"].params.parse(params ?? {})), + ), + "Audits.enable", + "command", + ), + checkFormsIssues: withCdpName( + async (params?: cdp.types.ts.Audits.CheckFormsIssuesParams) => + commands["Audits.checkFormsIssues"].result.parse( + await send("Audits.checkFormsIssues", commands["Audits.checkFormsIssues"].params.parse(params ?? {})), + ), + "Audits.checkFormsIssues", + "command", + ), + issueAdded: withCdpName(events["Audits.issueAdded"], "Audits.issueAdded", "event"), }, Autofill: { - trigger: withCdpName(async (params?: unknown) => commands["Autofill.trigger"].result.parse(await send("Autofill.trigger", commands["Autofill.trigger"].params.parse(params ?? {}))), "Autofill.trigger", "command"), - setAddresses: withCdpName(async (params?: unknown) => commands["Autofill.setAddresses"].result.parse(await send("Autofill.setAddresses", commands["Autofill.setAddresses"].params.parse(params ?? {}))), "Autofill.setAddresses", "command"), - disable: withCdpName(async (params?: unknown) => commands["Autofill.disable"].result.parse(await send("Autofill.disable", commands["Autofill.disable"].params.parse(params ?? {}))), "Autofill.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Autofill.enable"].result.parse(await send("Autofill.enable", commands["Autofill.enable"].params.parse(params ?? {}))), "Autofill.enable", "command"), - addressFormFilled: events["Autofill.addressFormFilled"] as CdpEventAlias, + trigger: withCdpName( + async (params?: cdp.types.ts.Autofill.TriggerParams) => + commands["Autofill.trigger"].result.parse( + await send("Autofill.trigger", commands["Autofill.trigger"].params.parse(params ?? {})), + ), + "Autofill.trigger", + "command", + ), + setAddresses: withCdpName( + async (params?: cdp.types.ts.Autofill.SetAddressesParams) => + commands["Autofill.setAddresses"].result.parse( + await send("Autofill.setAddresses", commands["Autofill.setAddresses"].params.parse(params ?? {})), + ), + "Autofill.setAddresses", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Autofill.DisableParams) => + commands["Autofill.disable"].result.parse( + await send("Autofill.disable", commands["Autofill.disable"].params.parse(params ?? {})), + ), + "Autofill.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Autofill.EnableParams) => + commands["Autofill.enable"].result.parse( + await send("Autofill.enable", commands["Autofill.enable"].params.parse(params ?? {})), + ), + "Autofill.enable", + "command", + ), + addressFormFilled: withCdpName(events["Autofill.addressFormFilled"], "Autofill.addressFormFilled", "event"), }, BackgroundService: { - startObserving: withCdpName(async (params?: unknown) => commands["BackgroundService.startObserving"].result.parse(await send("BackgroundService.startObserving", commands["BackgroundService.startObserving"].params.parse(params ?? {}))), "BackgroundService.startObserving", "command"), - stopObserving: withCdpName(async (params?: unknown) => commands["BackgroundService.stopObserving"].result.parse(await send("BackgroundService.stopObserving", commands["BackgroundService.stopObserving"].params.parse(params ?? {}))), "BackgroundService.stopObserving", "command"), - setRecording: withCdpName(async (params?: unknown) => commands["BackgroundService.setRecording"].result.parse(await send("BackgroundService.setRecording", commands["BackgroundService.setRecording"].params.parse(params ?? {}))), "BackgroundService.setRecording", "command"), - clearEvents: withCdpName(async (params?: unknown) => commands["BackgroundService.clearEvents"].result.parse(await send("BackgroundService.clearEvents", commands["BackgroundService.clearEvents"].params.parse(params ?? {}))), "BackgroundService.clearEvents", "command"), - recordingStateChanged: events["BackgroundService.recordingStateChanged"] as CdpEventAlias, - backgroundServiceEventReceived: events["BackgroundService.backgroundServiceEventReceived"] as CdpEventAlias, + startObserving: withCdpName( + async (params?: cdp.types.ts.BackgroundService.StartObservingParams) => + commands["BackgroundService.startObserving"].result.parse( + await send( + "BackgroundService.startObserving", + commands["BackgroundService.startObserving"].params.parse(params ?? {}), + ), + ), + "BackgroundService.startObserving", + "command", + ), + stopObserving: withCdpName( + async (params?: cdp.types.ts.BackgroundService.StopObservingParams) => + commands["BackgroundService.stopObserving"].result.parse( + await send( + "BackgroundService.stopObserving", + commands["BackgroundService.stopObserving"].params.parse(params ?? {}), + ), + ), + "BackgroundService.stopObserving", + "command", + ), + setRecording: withCdpName( + async (params?: cdp.types.ts.BackgroundService.SetRecordingParams) => + commands["BackgroundService.setRecording"].result.parse( + await send( + "BackgroundService.setRecording", + commands["BackgroundService.setRecording"].params.parse(params ?? {}), + ), + ), + "BackgroundService.setRecording", + "command", + ), + clearEvents: withCdpName( + async (params?: cdp.types.ts.BackgroundService.ClearEventsParams) => + commands["BackgroundService.clearEvents"].result.parse( + await send( + "BackgroundService.clearEvents", + commands["BackgroundService.clearEvents"].params.parse(params ?? {}), + ), + ), + "BackgroundService.clearEvents", + "command", + ), + recordingStateChanged: withCdpName( + events["BackgroundService.recordingStateChanged"], + "BackgroundService.recordingStateChanged", + "event", + ), + backgroundServiceEventReceived: withCdpName( + events["BackgroundService.backgroundServiceEventReceived"], + "BackgroundService.backgroundServiceEventReceived", + "event", + ), }, BluetoothEmulation: { - enable: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.enable"].result.parse(await send("BluetoothEmulation.enable", commands["BluetoothEmulation.enable"].params.parse(params ?? {}))), "BluetoothEmulation.enable", "command"), - setSimulatedCentralState: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.setSimulatedCentralState"].result.parse(await send("BluetoothEmulation.setSimulatedCentralState", commands["BluetoothEmulation.setSimulatedCentralState"].params.parse(params ?? {}))), "BluetoothEmulation.setSimulatedCentralState", "command"), - disable: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.disable"].result.parse(await send("BluetoothEmulation.disable", commands["BluetoothEmulation.disable"].params.parse(params ?? {}))), "BluetoothEmulation.disable", "command"), - simulatePreconnectedPeripheral: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulatePreconnectedPeripheral"].result.parse(await send("BluetoothEmulation.simulatePreconnectedPeripheral", commands["BluetoothEmulation.simulatePreconnectedPeripheral"].params.parse(params ?? {}))), "BluetoothEmulation.simulatePreconnectedPeripheral", "command"), - simulateAdvertisement: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulateAdvertisement"].result.parse(await send("BluetoothEmulation.simulateAdvertisement", commands["BluetoothEmulation.simulateAdvertisement"].params.parse(params ?? {}))), "BluetoothEmulation.simulateAdvertisement", "command"), - simulateGATTOperationResponse: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulateGATTOperationResponse"].result.parse(await send("BluetoothEmulation.simulateGATTOperationResponse", commands["BluetoothEmulation.simulateGATTOperationResponse"].params.parse(params ?? {}))), "BluetoothEmulation.simulateGATTOperationResponse", "command"), - simulateCharacteristicOperationResponse: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulateCharacteristicOperationResponse"].result.parse(await send("BluetoothEmulation.simulateCharacteristicOperationResponse", commands["BluetoothEmulation.simulateCharacteristicOperationResponse"].params.parse(params ?? {}))), "BluetoothEmulation.simulateCharacteristicOperationResponse", "command"), - simulateDescriptorOperationResponse: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulateDescriptorOperationResponse"].result.parse(await send("BluetoothEmulation.simulateDescriptorOperationResponse", commands["BluetoothEmulation.simulateDescriptorOperationResponse"].params.parse(params ?? {}))), "BluetoothEmulation.simulateDescriptorOperationResponse", "command"), - addService: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.addService"].result.parse(await send("BluetoothEmulation.addService", commands["BluetoothEmulation.addService"].params.parse(params ?? {}))), "BluetoothEmulation.addService", "command"), - removeService: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.removeService"].result.parse(await send("BluetoothEmulation.removeService", commands["BluetoothEmulation.removeService"].params.parse(params ?? {}))), "BluetoothEmulation.removeService", "command"), - addCharacteristic: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.addCharacteristic"].result.parse(await send("BluetoothEmulation.addCharacteristic", commands["BluetoothEmulation.addCharacteristic"].params.parse(params ?? {}))), "BluetoothEmulation.addCharacteristic", "command"), - removeCharacteristic: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.removeCharacteristic"].result.parse(await send("BluetoothEmulation.removeCharacteristic", commands["BluetoothEmulation.removeCharacteristic"].params.parse(params ?? {}))), "BluetoothEmulation.removeCharacteristic", "command"), - addDescriptor: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.addDescriptor"].result.parse(await send("BluetoothEmulation.addDescriptor", commands["BluetoothEmulation.addDescriptor"].params.parse(params ?? {}))), "BluetoothEmulation.addDescriptor", "command"), - removeDescriptor: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.removeDescriptor"].result.parse(await send("BluetoothEmulation.removeDescriptor", commands["BluetoothEmulation.removeDescriptor"].params.parse(params ?? {}))), "BluetoothEmulation.removeDescriptor", "command"), - simulateGATTDisconnection: withCdpName(async (params?: unknown) => commands["BluetoothEmulation.simulateGATTDisconnection"].result.parse(await send("BluetoothEmulation.simulateGATTDisconnection", commands["BluetoothEmulation.simulateGATTDisconnection"].params.parse(params ?? {}))), "BluetoothEmulation.simulateGATTDisconnection", "command"), - gattOperationReceived: events["BluetoothEmulation.gattOperationReceived"] as CdpEventAlias, - characteristicOperationReceived: events["BluetoothEmulation.characteristicOperationReceived"] as CdpEventAlias, - descriptorOperationReceived: events["BluetoothEmulation.descriptorOperationReceived"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.EnableParams) => + commands["BluetoothEmulation.enable"].result.parse( + await send("BluetoothEmulation.enable", commands["BluetoothEmulation.enable"].params.parse(params ?? {})), + ), + "BluetoothEmulation.enable", + "command", + ), + setSimulatedCentralState: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SetSimulatedCentralStateParams) => + commands["BluetoothEmulation.setSimulatedCentralState"].result.parse( + await send( + "BluetoothEmulation.setSimulatedCentralState", + commands["BluetoothEmulation.setSimulatedCentralState"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.setSimulatedCentralState", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.DisableParams) => + commands["BluetoothEmulation.disable"].result.parse( + await send("BluetoothEmulation.disable", commands["BluetoothEmulation.disable"].params.parse(params ?? {})), + ), + "BluetoothEmulation.disable", + "command", + ), + simulatePreconnectedPeripheral: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulatePreconnectedPeripheralParams) => + commands["BluetoothEmulation.simulatePreconnectedPeripheral"].result.parse( + await send( + "BluetoothEmulation.simulatePreconnectedPeripheral", + commands["BluetoothEmulation.simulatePreconnectedPeripheral"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulatePreconnectedPeripheral", + "command", + ), + simulateAdvertisement: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulateAdvertisementParams) => + commands["BluetoothEmulation.simulateAdvertisement"].result.parse( + await send( + "BluetoothEmulation.simulateAdvertisement", + commands["BluetoothEmulation.simulateAdvertisement"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulateAdvertisement", + "command", + ), + simulateGATTOperationResponse: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulateGATTOperationResponseParams) => + commands["BluetoothEmulation.simulateGATTOperationResponse"].result.parse( + await send( + "BluetoothEmulation.simulateGATTOperationResponse", + commands["BluetoothEmulation.simulateGATTOperationResponse"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulateGATTOperationResponse", + "command", + ), + simulateCharacteristicOperationResponse: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulateCharacteristicOperationResponseParams) => + commands["BluetoothEmulation.simulateCharacteristicOperationResponse"].result.parse( + await send( + "BluetoothEmulation.simulateCharacteristicOperationResponse", + commands["BluetoothEmulation.simulateCharacteristicOperationResponse"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulateCharacteristicOperationResponse", + "command", + ), + simulateDescriptorOperationResponse: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulateDescriptorOperationResponseParams) => + commands["BluetoothEmulation.simulateDescriptorOperationResponse"].result.parse( + await send( + "BluetoothEmulation.simulateDescriptorOperationResponse", + commands["BluetoothEmulation.simulateDescriptorOperationResponse"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulateDescriptorOperationResponse", + "command", + ), + addService: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.AddServiceParams) => + commands["BluetoothEmulation.addService"].result.parse( + await send( + "BluetoothEmulation.addService", + commands["BluetoothEmulation.addService"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.addService", + "command", + ), + removeService: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.RemoveServiceParams) => + commands["BluetoothEmulation.removeService"].result.parse( + await send( + "BluetoothEmulation.removeService", + commands["BluetoothEmulation.removeService"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.removeService", + "command", + ), + addCharacteristic: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.AddCharacteristicParams) => + commands["BluetoothEmulation.addCharacteristic"].result.parse( + await send( + "BluetoothEmulation.addCharacteristic", + commands["BluetoothEmulation.addCharacteristic"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.addCharacteristic", + "command", + ), + removeCharacteristic: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.RemoveCharacteristicParams) => + commands["BluetoothEmulation.removeCharacteristic"].result.parse( + await send( + "BluetoothEmulation.removeCharacteristic", + commands["BluetoothEmulation.removeCharacteristic"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.removeCharacteristic", + "command", + ), + addDescriptor: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.AddDescriptorParams) => + commands["BluetoothEmulation.addDescriptor"].result.parse( + await send( + "BluetoothEmulation.addDescriptor", + commands["BluetoothEmulation.addDescriptor"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.addDescriptor", + "command", + ), + removeDescriptor: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.RemoveDescriptorParams) => + commands["BluetoothEmulation.removeDescriptor"].result.parse( + await send( + "BluetoothEmulation.removeDescriptor", + commands["BluetoothEmulation.removeDescriptor"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.removeDescriptor", + "command", + ), + simulateGATTDisconnection: withCdpName( + async (params?: cdp.types.ts.BluetoothEmulation.SimulateGATTDisconnectionParams) => + commands["BluetoothEmulation.simulateGATTDisconnection"].result.parse( + await send( + "BluetoothEmulation.simulateGATTDisconnection", + commands["BluetoothEmulation.simulateGATTDisconnection"].params.parse(params ?? {}), + ), + ), + "BluetoothEmulation.simulateGATTDisconnection", + "command", + ), + gattOperationReceived: withCdpName( + events["BluetoothEmulation.gattOperationReceived"], + "BluetoothEmulation.gattOperationReceived", + "event", + ), + characteristicOperationReceived: withCdpName( + events["BluetoothEmulation.characteristicOperationReceived"], + "BluetoothEmulation.characteristicOperationReceived", + "event", + ), + descriptorOperationReceived: withCdpName( + events["BluetoothEmulation.descriptorOperationReceived"], + "BluetoothEmulation.descriptorOperationReceived", + "event", + ), }, Browser: { - setPermission: withCdpName(async (params?: unknown) => commands["Browser.setPermission"].result.parse(await send("Browser.setPermission", commands["Browser.setPermission"].params.parse(params ?? {}))), "Browser.setPermission", "command"), - grantPermissions: withCdpName(async (params?: unknown) => commands["Browser.grantPermissions"].result.parse(await send("Browser.grantPermissions", commands["Browser.grantPermissions"].params.parse(params ?? {}))), "Browser.grantPermissions", "command"), - resetPermissions: withCdpName(async (params?: unknown) => commands["Browser.resetPermissions"].result.parse(await send("Browser.resetPermissions", commands["Browser.resetPermissions"].params.parse(params ?? {}))), "Browser.resetPermissions", "command"), - setDownloadBehavior: withCdpName(async (params?: unknown) => commands["Browser.setDownloadBehavior"].result.parse(await send("Browser.setDownloadBehavior", commands["Browser.setDownloadBehavior"].params.parse(params ?? {}))), "Browser.setDownloadBehavior", "command"), - cancelDownload: withCdpName(async (params?: unknown) => commands["Browser.cancelDownload"].result.parse(await send("Browser.cancelDownload", commands["Browser.cancelDownload"].params.parse(params ?? {}))), "Browser.cancelDownload", "command"), - close: withCdpName(async (params?: unknown) => commands["Browser.close"].result.parse(await send("Browser.close", commands["Browser.close"].params.parse(params ?? {}))), "Browser.close", "command"), - crash: withCdpName(async (params?: unknown) => commands["Browser.crash"].result.parse(await send("Browser.crash", commands["Browser.crash"].params.parse(params ?? {}))), "Browser.crash", "command"), - crashGpuProcess: withCdpName(async (params?: unknown) => commands["Browser.crashGpuProcess"].result.parse(await send("Browser.crashGpuProcess", commands["Browser.crashGpuProcess"].params.parse(params ?? {}))), "Browser.crashGpuProcess", "command"), - getVersion: withCdpName(async (params?: unknown) => commands["Browser.getVersion"].result.parse(await send("Browser.getVersion", commands["Browser.getVersion"].params.parse(params ?? {}))), "Browser.getVersion", "command"), - getBrowserCommandLine: withCdpName(async (params?: unknown) => commands["Browser.getBrowserCommandLine"].result.parse(await send("Browser.getBrowserCommandLine", commands["Browser.getBrowserCommandLine"].params.parse(params ?? {}))), "Browser.getBrowserCommandLine", "command"), - getHistograms: withCdpName(async (params?: unknown) => commands["Browser.getHistograms"].result.parse(await send("Browser.getHistograms", commands["Browser.getHistograms"].params.parse(params ?? {}))), "Browser.getHistograms", "command"), - getHistogram: withCdpName(async (params?: unknown) => commands["Browser.getHistogram"].result.parse(await send("Browser.getHistogram", commands["Browser.getHistogram"].params.parse(params ?? {}))), "Browser.getHistogram", "command"), - getWindowBounds: withCdpName(async (params?: unknown) => commands["Browser.getWindowBounds"].result.parse(await send("Browser.getWindowBounds", commands["Browser.getWindowBounds"].params.parse(params ?? {}))), "Browser.getWindowBounds", "command"), - getWindowForTarget: withCdpName(async (params?: unknown) => commands["Browser.getWindowForTarget"].result.parse(await send("Browser.getWindowForTarget", commands["Browser.getWindowForTarget"].params.parse(params ?? {}))), "Browser.getWindowForTarget", "command"), - setWindowBounds: withCdpName(async (params?: unknown) => commands["Browser.setWindowBounds"].result.parse(await send("Browser.setWindowBounds", commands["Browser.setWindowBounds"].params.parse(params ?? {}))), "Browser.setWindowBounds", "command"), - setContentsSize: withCdpName(async (params?: unknown) => commands["Browser.setContentsSize"].result.parse(await send("Browser.setContentsSize", commands["Browser.setContentsSize"].params.parse(params ?? {}))), "Browser.setContentsSize", "command"), - setDockTile: withCdpName(async (params?: unknown) => commands["Browser.setDockTile"].result.parse(await send("Browser.setDockTile", commands["Browser.setDockTile"].params.parse(params ?? {}))), "Browser.setDockTile", "command"), - executeBrowserCommand: withCdpName(async (params?: unknown) => commands["Browser.executeBrowserCommand"].result.parse(await send("Browser.executeBrowserCommand", commands["Browser.executeBrowserCommand"].params.parse(params ?? {}))), "Browser.executeBrowserCommand", "command"), - addPrivacySandboxEnrollmentOverride: withCdpName(async (params?: unknown) => commands["Browser.addPrivacySandboxEnrollmentOverride"].result.parse(await send("Browser.addPrivacySandboxEnrollmentOverride", commands["Browser.addPrivacySandboxEnrollmentOverride"].params.parse(params ?? {}))), "Browser.addPrivacySandboxEnrollmentOverride", "command"), - addPrivacySandboxCoordinatorKeyConfig: withCdpName(async (params?: unknown) => commands["Browser.addPrivacySandboxCoordinatorKeyConfig"].result.parse(await send("Browser.addPrivacySandboxCoordinatorKeyConfig", commands["Browser.addPrivacySandboxCoordinatorKeyConfig"].params.parse(params ?? {}))), "Browser.addPrivacySandboxCoordinatorKeyConfig", "command"), - downloadWillBegin: events["Browser.downloadWillBegin"] as CdpEventAlias, - downloadProgress: events["Browser.downloadProgress"] as CdpEventAlias, + setPermission: withCdpName( + async (params?: cdp.types.ts.Browser.SetPermissionParams) => + commands["Browser.setPermission"].result.parse( + await send("Browser.setPermission", commands["Browser.setPermission"].params.parse(params ?? {})), + ), + "Browser.setPermission", + "command", + ), + grantPermissions: withCdpName( + async (params?: cdp.types.ts.Browser.GrantPermissionsParams) => + commands["Browser.grantPermissions"].result.parse( + await send("Browser.grantPermissions", commands["Browser.grantPermissions"].params.parse(params ?? {})), + ), + "Browser.grantPermissions", + "command", + ), + resetPermissions: withCdpName( + async (params?: cdp.types.ts.Browser.ResetPermissionsParams) => + commands["Browser.resetPermissions"].result.parse( + await send("Browser.resetPermissions", commands["Browser.resetPermissions"].params.parse(params ?? {})), + ), + "Browser.resetPermissions", + "command", + ), + setDownloadBehavior: withCdpName( + async (params?: cdp.types.ts.Browser.SetDownloadBehaviorParams) => + commands["Browser.setDownloadBehavior"].result.parse( + await send( + "Browser.setDownloadBehavior", + commands["Browser.setDownloadBehavior"].params.parse(params ?? {}), + ), + ), + "Browser.setDownloadBehavior", + "command", + ), + cancelDownload: withCdpName( + async (params?: cdp.types.ts.Browser.CancelDownloadParams) => + commands["Browser.cancelDownload"].result.parse( + await send("Browser.cancelDownload", commands["Browser.cancelDownload"].params.parse(params ?? {})), + ), + "Browser.cancelDownload", + "command", + ), + close: withCdpName( + async (params?: cdp.types.ts.Browser.CloseParams) => + commands["Browser.close"].result.parse( + await send("Browser.close", commands["Browser.close"].params.parse(params ?? {})), + ), + "Browser.close", + "command", + ), + crash: withCdpName( + async (params?: cdp.types.ts.Browser.CrashParams) => + commands["Browser.crash"].result.parse( + await send("Browser.crash", commands["Browser.crash"].params.parse(params ?? {})), + ), + "Browser.crash", + "command", + ), + crashGpuProcess: withCdpName( + async (params?: cdp.types.ts.Browser.CrashGpuProcessParams) => + commands["Browser.crashGpuProcess"].result.parse( + await send("Browser.crashGpuProcess", commands["Browser.crashGpuProcess"].params.parse(params ?? {})), + ), + "Browser.crashGpuProcess", + "command", + ), + getVersion: withCdpName( + async (params?: cdp.types.ts.Browser.GetVersionParams) => + commands["Browser.getVersion"].result.parse( + await send("Browser.getVersion", commands["Browser.getVersion"].params.parse(params ?? {})), + ), + "Browser.getVersion", + "command", + ), + getBrowserCommandLine: withCdpName( + async (params?: cdp.types.ts.Browser.GetBrowserCommandLineParams) => + commands["Browser.getBrowserCommandLine"].result.parse( + await send( + "Browser.getBrowserCommandLine", + commands["Browser.getBrowserCommandLine"].params.parse(params ?? {}), + ), + ), + "Browser.getBrowserCommandLine", + "command", + ), + getHistograms: withCdpName( + async (params?: cdp.types.ts.Browser.GetHistogramsParams) => + commands["Browser.getHistograms"].result.parse( + await send("Browser.getHistograms", commands["Browser.getHistograms"].params.parse(params ?? {})), + ), + "Browser.getHistograms", + "command", + ), + getHistogram: withCdpName( + async (params?: cdp.types.ts.Browser.GetHistogramParams) => + commands["Browser.getHistogram"].result.parse( + await send("Browser.getHistogram", commands["Browser.getHistogram"].params.parse(params ?? {})), + ), + "Browser.getHistogram", + "command", + ), + getWindowBounds: withCdpName( + async (params?: cdp.types.ts.Browser.GetWindowBoundsParams) => + commands["Browser.getWindowBounds"].result.parse( + await send("Browser.getWindowBounds", commands["Browser.getWindowBounds"].params.parse(params ?? {})), + ), + "Browser.getWindowBounds", + "command", + ), + getWindowForTarget: withCdpName( + async (params?: cdp.types.ts.Browser.GetWindowForTargetParams) => + commands["Browser.getWindowForTarget"].result.parse( + await send("Browser.getWindowForTarget", commands["Browser.getWindowForTarget"].params.parse(params ?? {})), + ), + "Browser.getWindowForTarget", + "command", + ), + setWindowBounds: withCdpName( + async (params?: cdp.types.ts.Browser.SetWindowBoundsParams) => + commands["Browser.setWindowBounds"].result.parse( + await send("Browser.setWindowBounds", commands["Browser.setWindowBounds"].params.parse(params ?? {})), + ), + "Browser.setWindowBounds", + "command", + ), + setContentsSize: withCdpName( + async (params?: cdp.types.ts.Browser.SetContentsSizeParams) => + commands["Browser.setContentsSize"].result.parse( + await send("Browser.setContentsSize", commands["Browser.setContentsSize"].params.parse(params ?? {})), + ), + "Browser.setContentsSize", + "command", + ), + setDockTile: withCdpName( + async (params?: cdp.types.ts.Browser.SetDockTileParams) => + commands["Browser.setDockTile"].result.parse( + await send("Browser.setDockTile", commands["Browser.setDockTile"].params.parse(params ?? {})), + ), + "Browser.setDockTile", + "command", + ), + executeBrowserCommand: withCdpName( + async (params?: cdp.types.ts.Browser.ExecuteBrowserCommandParams) => + commands["Browser.executeBrowserCommand"].result.parse( + await send( + "Browser.executeBrowserCommand", + commands["Browser.executeBrowserCommand"].params.parse(params ?? {}), + ), + ), + "Browser.executeBrowserCommand", + "command", + ), + addPrivacySandboxEnrollmentOverride: withCdpName( + async (params?: cdp.types.ts.Browser.AddPrivacySandboxEnrollmentOverrideParams) => + commands["Browser.addPrivacySandboxEnrollmentOverride"].result.parse( + await send( + "Browser.addPrivacySandboxEnrollmentOverride", + commands["Browser.addPrivacySandboxEnrollmentOverride"].params.parse(params ?? {}), + ), + ), + "Browser.addPrivacySandboxEnrollmentOverride", + "command", + ), + addPrivacySandboxCoordinatorKeyConfig: withCdpName( + async (params?: cdp.types.ts.Browser.AddPrivacySandboxCoordinatorKeyConfigParams) => + commands["Browser.addPrivacySandboxCoordinatorKeyConfig"].result.parse( + await send( + "Browser.addPrivacySandboxCoordinatorKeyConfig", + commands["Browser.addPrivacySandboxCoordinatorKeyConfig"].params.parse(params ?? {}), + ), + ), + "Browser.addPrivacySandboxCoordinatorKeyConfig", + "command", + ), + downloadWillBegin: withCdpName(events["Browser.downloadWillBegin"], "Browser.downloadWillBegin", "event"), + downloadProgress: withCdpName(events["Browser.downloadProgress"], "Browser.downloadProgress", "event"), }, CacheStorage: { - deleteCache: withCdpName(async (params?: unknown) => commands["CacheStorage.deleteCache"].result.parse(await send("CacheStorage.deleteCache", commands["CacheStorage.deleteCache"].params.parse(params ?? {}))), "CacheStorage.deleteCache", "command"), - deleteEntry: withCdpName(async (params?: unknown) => commands["CacheStorage.deleteEntry"].result.parse(await send("CacheStorage.deleteEntry", commands["CacheStorage.deleteEntry"].params.parse(params ?? {}))), "CacheStorage.deleteEntry", "command"), - requestCacheNames: withCdpName(async (params?: unknown) => commands["CacheStorage.requestCacheNames"].result.parse(await send("CacheStorage.requestCacheNames", commands["CacheStorage.requestCacheNames"].params.parse(params ?? {}))), "CacheStorage.requestCacheNames", "command"), - requestCachedResponse: withCdpName(async (params?: unknown) => commands["CacheStorage.requestCachedResponse"].result.parse(await send("CacheStorage.requestCachedResponse", commands["CacheStorage.requestCachedResponse"].params.parse(params ?? {}))), "CacheStorage.requestCachedResponse", "command"), - requestEntries: withCdpName(async (params?: unknown) => commands["CacheStorage.requestEntries"].result.parse(await send("CacheStorage.requestEntries", commands["CacheStorage.requestEntries"].params.parse(params ?? {}))), "CacheStorage.requestEntries", "command"), + deleteCache: withCdpName( + async (params?: cdp.types.ts.CacheStorage.DeleteCacheParams) => + commands["CacheStorage.deleteCache"].result.parse( + await send("CacheStorage.deleteCache", commands["CacheStorage.deleteCache"].params.parse(params ?? {})), + ), + "CacheStorage.deleteCache", + "command", + ), + deleteEntry: withCdpName( + async (params?: cdp.types.ts.CacheStorage.DeleteEntryParams) => + commands["CacheStorage.deleteEntry"].result.parse( + await send("CacheStorage.deleteEntry", commands["CacheStorage.deleteEntry"].params.parse(params ?? {})), + ), + "CacheStorage.deleteEntry", + "command", + ), + requestCacheNames: withCdpName( + async (params?: cdp.types.ts.CacheStorage.RequestCacheNamesParams) => + commands["CacheStorage.requestCacheNames"].result.parse( + await send( + "CacheStorage.requestCacheNames", + commands["CacheStorage.requestCacheNames"].params.parse(params ?? {}), + ), + ), + "CacheStorage.requestCacheNames", + "command", + ), + requestCachedResponse: withCdpName( + async (params?: cdp.types.ts.CacheStorage.RequestCachedResponseParams) => + commands["CacheStorage.requestCachedResponse"].result.parse( + await send( + "CacheStorage.requestCachedResponse", + commands["CacheStorage.requestCachedResponse"].params.parse(params ?? {}), + ), + ), + "CacheStorage.requestCachedResponse", + "command", + ), + requestEntries: withCdpName( + async (params?: cdp.types.ts.CacheStorage.RequestEntriesParams) => + commands["CacheStorage.requestEntries"].result.parse( + await send( + "CacheStorage.requestEntries", + commands["CacheStorage.requestEntries"].params.parse(params ?? {}), + ), + ), + "CacheStorage.requestEntries", + "command", + ), }, Cast: { - enable: withCdpName(async (params?: unknown) => commands["Cast.enable"].result.parse(await send("Cast.enable", commands["Cast.enable"].params.parse(params ?? {}))), "Cast.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["Cast.disable"].result.parse(await send("Cast.disable", commands["Cast.disable"].params.parse(params ?? {}))), "Cast.disable", "command"), - setSinkToUse: withCdpName(async (params?: unknown) => commands["Cast.setSinkToUse"].result.parse(await send("Cast.setSinkToUse", commands["Cast.setSinkToUse"].params.parse(params ?? {}))), "Cast.setSinkToUse", "command"), - startDesktopMirroring: withCdpName(async (params?: unknown) => commands["Cast.startDesktopMirroring"].result.parse(await send("Cast.startDesktopMirroring", commands["Cast.startDesktopMirroring"].params.parse(params ?? {}))), "Cast.startDesktopMirroring", "command"), - startTabMirroring: withCdpName(async (params?: unknown) => commands["Cast.startTabMirroring"].result.parse(await send("Cast.startTabMirroring", commands["Cast.startTabMirroring"].params.parse(params ?? {}))), "Cast.startTabMirroring", "command"), - stopCasting: withCdpName(async (params?: unknown) => commands["Cast.stopCasting"].result.parse(await send("Cast.stopCasting", commands["Cast.stopCasting"].params.parse(params ?? {}))), "Cast.stopCasting", "command"), - sinksUpdated: events["Cast.sinksUpdated"] as CdpEventAlias, - issueUpdated: events["Cast.issueUpdated"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.Cast.EnableParams) => + commands["Cast.enable"].result.parse( + await send("Cast.enable", commands["Cast.enable"].params.parse(params ?? {})), + ), + "Cast.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Cast.DisableParams) => + commands["Cast.disable"].result.parse( + await send("Cast.disable", commands["Cast.disable"].params.parse(params ?? {})), + ), + "Cast.disable", + "command", + ), + setSinkToUse: withCdpName( + async (params?: cdp.types.ts.Cast.SetSinkToUseParams) => + commands["Cast.setSinkToUse"].result.parse( + await send("Cast.setSinkToUse", commands["Cast.setSinkToUse"].params.parse(params ?? {})), + ), + "Cast.setSinkToUse", + "command", + ), + startDesktopMirroring: withCdpName( + async (params?: cdp.types.ts.Cast.StartDesktopMirroringParams) => + commands["Cast.startDesktopMirroring"].result.parse( + await send("Cast.startDesktopMirroring", commands["Cast.startDesktopMirroring"].params.parse(params ?? {})), + ), + "Cast.startDesktopMirroring", + "command", + ), + startTabMirroring: withCdpName( + async (params?: cdp.types.ts.Cast.StartTabMirroringParams) => + commands["Cast.startTabMirroring"].result.parse( + await send("Cast.startTabMirroring", commands["Cast.startTabMirroring"].params.parse(params ?? {})), + ), + "Cast.startTabMirroring", + "command", + ), + stopCasting: withCdpName( + async (params?: cdp.types.ts.Cast.StopCastingParams) => + commands["Cast.stopCasting"].result.parse( + await send("Cast.stopCasting", commands["Cast.stopCasting"].params.parse(params ?? {})), + ), + "Cast.stopCasting", + "command", + ), + sinksUpdated: withCdpName(events["Cast.sinksUpdated"], "Cast.sinksUpdated", "event"), + issueUpdated: withCdpName(events["Cast.issueUpdated"], "Cast.issueUpdated", "event"), }, Console: { - clearMessages: withCdpName(async (params?: unknown) => commands["Console.clearMessages"].result.parse(await send("Console.clearMessages", commands["Console.clearMessages"].params.parse(params ?? {}))), "Console.clearMessages", "command"), - disable: withCdpName(async (params?: unknown) => commands["Console.disable"].result.parse(await send("Console.disable", commands["Console.disable"].params.parse(params ?? {}))), "Console.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Console.enable"].result.parse(await send("Console.enable", commands["Console.enable"].params.parse(params ?? {}))), "Console.enable", "command"), - messageAdded: events["Console.messageAdded"] as CdpEventAlias, + clearMessages: withCdpName( + async (params?: cdp.types.ts.Console.ClearMessagesParams) => + commands["Console.clearMessages"].result.parse( + await send("Console.clearMessages", commands["Console.clearMessages"].params.parse(params ?? {})), + ), + "Console.clearMessages", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Console.DisableParams) => + commands["Console.disable"].result.parse( + await send("Console.disable", commands["Console.disable"].params.parse(params ?? {})), + ), + "Console.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Console.EnableParams) => + commands["Console.enable"].result.parse( + await send("Console.enable", commands["Console.enable"].params.parse(params ?? {})), + ), + "Console.enable", + "command", + ), + messageAdded: withCdpName(events["Console.messageAdded"], "Console.messageAdded", "event"), }, CrashReportContext: { - getEntries: withCdpName(async (params?: unknown) => commands["CrashReportContext.getEntries"].result.parse(await send("CrashReportContext.getEntries", commands["CrashReportContext.getEntries"].params.parse(params ?? {}))), "CrashReportContext.getEntries", "command"), + getEntries: withCdpName( + async (params?: cdp.types.ts.CrashReportContext.GetEntriesParams) => + commands["CrashReportContext.getEntries"].result.parse( + await send( + "CrashReportContext.getEntries", + commands["CrashReportContext.getEntries"].params.parse(params ?? {}), + ), + ), + "CrashReportContext.getEntries", + "command", + ), }, CSS: { - addRule: withCdpName(async (params?: unknown) => commands["CSS.addRule"].result.parse(await send("CSS.addRule", commands["CSS.addRule"].params.parse(params ?? {}))), "CSS.addRule", "command"), - collectClassNames: withCdpName(async (params?: unknown) => commands["CSS.collectClassNames"].result.parse(await send("CSS.collectClassNames", commands["CSS.collectClassNames"].params.parse(params ?? {}))), "CSS.collectClassNames", "command"), - createStyleSheet: withCdpName(async (params?: unknown) => commands["CSS.createStyleSheet"].result.parse(await send("CSS.createStyleSheet", commands["CSS.createStyleSheet"].params.parse(params ?? {}))), "CSS.createStyleSheet", "command"), - disable: withCdpName(async (params?: unknown) => commands["CSS.disable"].result.parse(await send("CSS.disable", commands["CSS.disable"].params.parse(params ?? {}))), "CSS.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["CSS.enable"].result.parse(await send("CSS.enable", commands["CSS.enable"].params.parse(params ?? {}))), "CSS.enable", "command"), - forcePseudoState: withCdpName(async (params?: unknown) => commands["CSS.forcePseudoState"].result.parse(await send("CSS.forcePseudoState", commands["CSS.forcePseudoState"].params.parse(params ?? {}))), "CSS.forcePseudoState", "command"), - forceStartingStyle: withCdpName(async (params?: unknown) => commands["CSS.forceStartingStyle"].result.parse(await send("CSS.forceStartingStyle", commands["CSS.forceStartingStyle"].params.parse(params ?? {}))), "CSS.forceStartingStyle", "command"), - getBackgroundColors: withCdpName(async (params?: unknown) => commands["CSS.getBackgroundColors"].result.parse(await send("CSS.getBackgroundColors", commands["CSS.getBackgroundColors"].params.parse(params ?? {}))), "CSS.getBackgroundColors", "command"), - getComputedStyleForNode: withCdpName(async (params?: unknown) => commands["CSS.getComputedStyleForNode"].result.parse(await send("CSS.getComputedStyleForNode", commands["CSS.getComputedStyleForNode"].params.parse(params ?? {}))), "CSS.getComputedStyleForNode", "command"), - resolveValues: withCdpName(async (params?: unknown) => commands["CSS.resolveValues"].result.parse(await send("CSS.resolveValues", commands["CSS.resolveValues"].params.parse(params ?? {}))), "CSS.resolveValues", "command"), - getLonghandProperties: withCdpName(async (params?: unknown) => commands["CSS.getLonghandProperties"].result.parse(await send("CSS.getLonghandProperties", commands["CSS.getLonghandProperties"].params.parse(params ?? {}))), "CSS.getLonghandProperties", "command"), - getInlineStylesForNode: withCdpName(async (params?: unknown) => commands["CSS.getInlineStylesForNode"].result.parse(await send("CSS.getInlineStylesForNode", commands["CSS.getInlineStylesForNode"].params.parse(params ?? {}))), "CSS.getInlineStylesForNode", "command"), - getAnimatedStylesForNode: withCdpName(async (params?: unknown) => commands["CSS.getAnimatedStylesForNode"].result.parse(await send("CSS.getAnimatedStylesForNode", commands["CSS.getAnimatedStylesForNode"].params.parse(params ?? {}))), "CSS.getAnimatedStylesForNode", "command"), - getMatchedStylesForNode: withCdpName(async (params?: unknown) => commands["CSS.getMatchedStylesForNode"].result.parse(await send("CSS.getMatchedStylesForNode", commands["CSS.getMatchedStylesForNode"].params.parse(params ?? {}))), "CSS.getMatchedStylesForNode", "command"), - getEnvironmentVariables: withCdpName(async (params?: unknown) => commands["CSS.getEnvironmentVariables"].result.parse(await send("CSS.getEnvironmentVariables", commands["CSS.getEnvironmentVariables"].params.parse(params ?? {}))), "CSS.getEnvironmentVariables", "command"), - getMediaQueries: withCdpName(async (params?: unknown) => commands["CSS.getMediaQueries"].result.parse(await send("CSS.getMediaQueries", commands["CSS.getMediaQueries"].params.parse(params ?? {}))), "CSS.getMediaQueries", "command"), - getPlatformFontsForNode: withCdpName(async (params?: unknown) => commands["CSS.getPlatformFontsForNode"].result.parse(await send("CSS.getPlatformFontsForNode", commands["CSS.getPlatformFontsForNode"].params.parse(params ?? {}))), "CSS.getPlatformFontsForNode", "command"), - getStyleSheetText: withCdpName(async (params?: unknown) => commands["CSS.getStyleSheetText"].result.parse(await send("CSS.getStyleSheetText", commands["CSS.getStyleSheetText"].params.parse(params ?? {}))), "CSS.getStyleSheetText", "command"), - getLayersForNode: withCdpName(async (params?: unknown) => commands["CSS.getLayersForNode"].result.parse(await send("CSS.getLayersForNode", commands["CSS.getLayersForNode"].params.parse(params ?? {}))), "CSS.getLayersForNode", "command"), - getLocationForSelector: withCdpName(async (params?: unknown) => commands["CSS.getLocationForSelector"].result.parse(await send("CSS.getLocationForSelector", commands["CSS.getLocationForSelector"].params.parse(params ?? {}))), "CSS.getLocationForSelector", "command"), - trackComputedStyleUpdatesForNode: withCdpName(async (params?: unknown) => commands["CSS.trackComputedStyleUpdatesForNode"].result.parse(await send("CSS.trackComputedStyleUpdatesForNode", commands["CSS.trackComputedStyleUpdatesForNode"].params.parse(params ?? {}))), "CSS.trackComputedStyleUpdatesForNode", "command"), - trackComputedStyleUpdates: withCdpName(async (params?: unknown) => commands["CSS.trackComputedStyleUpdates"].result.parse(await send("CSS.trackComputedStyleUpdates", commands["CSS.trackComputedStyleUpdates"].params.parse(params ?? {}))), "CSS.trackComputedStyleUpdates", "command"), - takeComputedStyleUpdates: withCdpName(async (params?: unknown) => commands["CSS.takeComputedStyleUpdates"].result.parse(await send("CSS.takeComputedStyleUpdates", commands["CSS.takeComputedStyleUpdates"].params.parse(params ?? {}))), "CSS.takeComputedStyleUpdates", "command"), - setEffectivePropertyValueForNode: withCdpName(async (params?: unknown) => commands["CSS.setEffectivePropertyValueForNode"].result.parse(await send("CSS.setEffectivePropertyValueForNode", commands["CSS.setEffectivePropertyValueForNode"].params.parse(params ?? {}))), "CSS.setEffectivePropertyValueForNode", "command"), - setPropertyRulePropertyName: withCdpName(async (params?: unknown) => commands["CSS.setPropertyRulePropertyName"].result.parse(await send("CSS.setPropertyRulePropertyName", commands["CSS.setPropertyRulePropertyName"].params.parse(params ?? {}))), "CSS.setPropertyRulePropertyName", "command"), - setKeyframeKey: withCdpName(async (params?: unknown) => commands["CSS.setKeyframeKey"].result.parse(await send("CSS.setKeyframeKey", commands["CSS.setKeyframeKey"].params.parse(params ?? {}))), "CSS.setKeyframeKey", "command"), - setMediaText: withCdpName(async (params?: unknown) => commands["CSS.setMediaText"].result.parse(await send("CSS.setMediaText", commands["CSS.setMediaText"].params.parse(params ?? {}))), "CSS.setMediaText", "command"), - setContainerQueryText: withCdpName(async (params?: unknown) => commands["CSS.setContainerQueryText"].result.parse(await send("CSS.setContainerQueryText", commands["CSS.setContainerQueryText"].params.parse(params ?? {}))), "CSS.setContainerQueryText", "command"), - setSupportsText: withCdpName(async (params?: unknown) => commands["CSS.setSupportsText"].result.parse(await send("CSS.setSupportsText", commands["CSS.setSupportsText"].params.parse(params ?? {}))), "CSS.setSupportsText", "command"), - setNavigationText: withCdpName(async (params?: unknown) => commands["CSS.setNavigationText"].result.parse(await send("CSS.setNavigationText", commands["CSS.setNavigationText"].params.parse(params ?? {}))), "CSS.setNavigationText", "command"), - setScopeText: withCdpName(async (params?: unknown) => commands["CSS.setScopeText"].result.parse(await send("CSS.setScopeText", commands["CSS.setScopeText"].params.parse(params ?? {}))), "CSS.setScopeText", "command"), - setRuleSelector: withCdpName(async (params?: unknown) => commands["CSS.setRuleSelector"].result.parse(await send("CSS.setRuleSelector", commands["CSS.setRuleSelector"].params.parse(params ?? {}))), "CSS.setRuleSelector", "command"), - setStyleSheetText: withCdpName(async (params?: unknown) => commands["CSS.setStyleSheetText"].result.parse(await send("CSS.setStyleSheetText", commands["CSS.setStyleSheetText"].params.parse(params ?? {}))), "CSS.setStyleSheetText", "command"), - setStyleTexts: withCdpName(async (params?: unknown) => commands["CSS.setStyleTexts"].result.parse(await send("CSS.setStyleTexts", commands["CSS.setStyleTexts"].params.parse(params ?? {}))), "CSS.setStyleTexts", "command"), - startRuleUsageTracking: withCdpName(async (params?: unknown) => commands["CSS.startRuleUsageTracking"].result.parse(await send("CSS.startRuleUsageTracking", commands["CSS.startRuleUsageTracking"].params.parse(params ?? {}))), "CSS.startRuleUsageTracking", "command"), - stopRuleUsageTracking: withCdpName(async (params?: unknown) => commands["CSS.stopRuleUsageTracking"].result.parse(await send("CSS.stopRuleUsageTracking", commands["CSS.stopRuleUsageTracking"].params.parse(params ?? {}))), "CSS.stopRuleUsageTracking", "command"), - takeCoverageDelta: withCdpName(async (params?: unknown) => commands["CSS.takeCoverageDelta"].result.parse(await send("CSS.takeCoverageDelta", commands["CSS.takeCoverageDelta"].params.parse(params ?? {}))), "CSS.takeCoverageDelta", "command"), - setLocalFontsEnabled: withCdpName(async (params?: unknown) => commands["CSS.setLocalFontsEnabled"].result.parse(await send("CSS.setLocalFontsEnabled", commands["CSS.setLocalFontsEnabled"].params.parse(params ?? {}))), "CSS.setLocalFontsEnabled", "command"), - fontsUpdated: events["CSS.fontsUpdated"] as CdpEventAlias, - mediaQueryResultChanged: events["CSS.mediaQueryResultChanged"] as CdpEventAlias, - styleSheetAdded: events["CSS.styleSheetAdded"] as CdpEventAlias, - styleSheetChanged: events["CSS.styleSheetChanged"] as CdpEventAlias, - styleSheetRemoved: events["CSS.styleSheetRemoved"] as CdpEventAlias, - computedStyleUpdated: events["CSS.computedStyleUpdated"] as CdpEventAlias, + addRule: withCdpName( + async (params?: cdp.types.ts.CSS.AddRuleParams) => + commands["CSS.addRule"].result.parse( + await send("CSS.addRule", commands["CSS.addRule"].params.parse(params ?? {})), + ), + "CSS.addRule", + "command", + ), + collectClassNames: withCdpName( + async (params?: cdp.types.ts.CSS.CollectClassNamesParams) => + commands["CSS.collectClassNames"].result.parse( + await send("CSS.collectClassNames", commands["CSS.collectClassNames"].params.parse(params ?? {})), + ), + "CSS.collectClassNames", + "command", + ), + createStyleSheet: withCdpName( + async (params?: cdp.types.ts.CSS.CreateStyleSheetParams) => + commands["CSS.createStyleSheet"].result.parse( + await send("CSS.createStyleSheet", commands["CSS.createStyleSheet"].params.parse(params ?? {})), + ), + "CSS.createStyleSheet", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.CSS.DisableParams) => + commands["CSS.disable"].result.parse( + await send("CSS.disable", commands["CSS.disable"].params.parse(params ?? {})), + ), + "CSS.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.CSS.EnableParams) => + commands["CSS.enable"].result.parse( + await send("CSS.enable", commands["CSS.enable"].params.parse(params ?? {})), + ), + "CSS.enable", + "command", + ), + forcePseudoState: withCdpName( + async (params?: cdp.types.ts.CSS.ForcePseudoStateParams) => + commands["CSS.forcePseudoState"].result.parse( + await send("CSS.forcePseudoState", commands["CSS.forcePseudoState"].params.parse(params ?? {})), + ), + "CSS.forcePseudoState", + "command", + ), + forceStartingStyle: withCdpName( + async (params?: cdp.types.ts.CSS.ForceStartingStyleParams) => + commands["CSS.forceStartingStyle"].result.parse( + await send("CSS.forceStartingStyle", commands["CSS.forceStartingStyle"].params.parse(params ?? {})), + ), + "CSS.forceStartingStyle", + "command", + ), + getBackgroundColors: withCdpName( + async (params?: cdp.types.ts.CSS.GetBackgroundColorsParams) => + commands["CSS.getBackgroundColors"].result.parse( + await send("CSS.getBackgroundColors", commands["CSS.getBackgroundColors"].params.parse(params ?? {})), + ), + "CSS.getBackgroundColors", + "command", + ), + getComputedStyleForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetComputedStyleForNodeParams) => + commands["CSS.getComputedStyleForNode"].result.parse( + await send( + "CSS.getComputedStyleForNode", + commands["CSS.getComputedStyleForNode"].params.parse(params ?? {}), + ), + ), + "CSS.getComputedStyleForNode", + "command", + ), + resolveValues: withCdpName( + async (params?: cdp.types.ts.CSS.ResolveValuesParams) => + commands["CSS.resolveValues"].result.parse( + await send("CSS.resolveValues", commands["CSS.resolveValues"].params.parse(params ?? {})), + ), + "CSS.resolveValues", + "command", + ), + getLonghandProperties: withCdpName( + async (params?: cdp.types.ts.CSS.GetLonghandPropertiesParams) => + commands["CSS.getLonghandProperties"].result.parse( + await send("CSS.getLonghandProperties", commands["CSS.getLonghandProperties"].params.parse(params ?? {})), + ), + "CSS.getLonghandProperties", + "command", + ), + getInlineStylesForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetInlineStylesForNodeParams) => + commands["CSS.getInlineStylesForNode"].result.parse( + await send("CSS.getInlineStylesForNode", commands["CSS.getInlineStylesForNode"].params.parse(params ?? {})), + ), + "CSS.getInlineStylesForNode", + "command", + ), + getAnimatedStylesForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetAnimatedStylesForNodeParams) => + commands["CSS.getAnimatedStylesForNode"].result.parse( + await send( + "CSS.getAnimatedStylesForNode", + commands["CSS.getAnimatedStylesForNode"].params.parse(params ?? {}), + ), + ), + "CSS.getAnimatedStylesForNode", + "command", + ), + getMatchedStylesForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetMatchedStylesForNodeParams) => + commands["CSS.getMatchedStylesForNode"].result.parse( + await send( + "CSS.getMatchedStylesForNode", + commands["CSS.getMatchedStylesForNode"].params.parse(params ?? {}), + ), + ), + "CSS.getMatchedStylesForNode", + "command", + ), + getEnvironmentVariables: withCdpName( + async (params?: cdp.types.ts.CSS.GetEnvironmentVariablesParams) => + commands["CSS.getEnvironmentVariables"].result.parse( + await send( + "CSS.getEnvironmentVariables", + commands["CSS.getEnvironmentVariables"].params.parse(params ?? {}), + ), + ), + "CSS.getEnvironmentVariables", + "command", + ), + getMediaQueries: withCdpName( + async (params?: cdp.types.ts.CSS.GetMediaQueriesParams) => + commands["CSS.getMediaQueries"].result.parse( + await send("CSS.getMediaQueries", commands["CSS.getMediaQueries"].params.parse(params ?? {})), + ), + "CSS.getMediaQueries", + "command", + ), + getPlatformFontsForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetPlatformFontsForNodeParams) => + commands["CSS.getPlatformFontsForNode"].result.parse( + await send( + "CSS.getPlatformFontsForNode", + commands["CSS.getPlatformFontsForNode"].params.parse(params ?? {}), + ), + ), + "CSS.getPlatformFontsForNode", + "command", + ), + getStyleSheetText: withCdpName( + async (params?: cdp.types.ts.CSS.GetStyleSheetTextParams) => + commands["CSS.getStyleSheetText"].result.parse( + await send("CSS.getStyleSheetText", commands["CSS.getStyleSheetText"].params.parse(params ?? {})), + ), + "CSS.getStyleSheetText", + "command", + ), + getLayersForNode: withCdpName( + async (params?: cdp.types.ts.CSS.GetLayersForNodeParams) => + commands["CSS.getLayersForNode"].result.parse( + await send("CSS.getLayersForNode", commands["CSS.getLayersForNode"].params.parse(params ?? {})), + ), + "CSS.getLayersForNode", + "command", + ), + getLocationForSelector: withCdpName( + async (params?: cdp.types.ts.CSS.GetLocationForSelectorParams) => + commands["CSS.getLocationForSelector"].result.parse( + await send("CSS.getLocationForSelector", commands["CSS.getLocationForSelector"].params.parse(params ?? {})), + ), + "CSS.getLocationForSelector", + "command", + ), + trackComputedStyleUpdatesForNode: withCdpName( + async (params?: cdp.types.ts.CSS.TrackComputedStyleUpdatesForNodeParams) => + commands["CSS.trackComputedStyleUpdatesForNode"].result.parse( + await send( + "CSS.trackComputedStyleUpdatesForNode", + commands["CSS.trackComputedStyleUpdatesForNode"].params.parse(params ?? {}), + ), + ), + "CSS.trackComputedStyleUpdatesForNode", + "command", + ), + trackComputedStyleUpdates: withCdpName( + async (params?: cdp.types.ts.CSS.TrackComputedStyleUpdatesParams) => + commands["CSS.trackComputedStyleUpdates"].result.parse( + await send( + "CSS.trackComputedStyleUpdates", + commands["CSS.trackComputedStyleUpdates"].params.parse(params ?? {}), + ), + ), + "CSS.trackComputedStyleUpdates", + "command", + ), + takeComputedStyleUpdates: withCdpName( + async (params?: cdp.types.ts.CSS.TakeComputedStyleUpdatesParams) => + commands["CSS.takeComputedStyleUpdates"].result.parse( + await send( + "CSS.takeComputedStyleUpdates", + commands["CSS.takeComputedStyleUpdates"].params.parse(params ?? {}), + ), + ), + "CSS.takeComputedStyleUpdates", + "command", + ), + setEffectivePropertyValueForNode: withCdpName( + async (params?: cdp.types.ts.CSS.SetEffectivePropertyValueForNodeParams) => + commands["CSS.setEffectivePropertyValueForNode"].result.parse( + await send( + "CSS.setEffectivePropertyValueForNode", + commands["CSS.setEffectivePropertyValueForNode"].params.parse(params ?? {}), + ), + ), + "CSS.setEffectivePropertyValueForNode", + "command", + ), + setPropertyRulePropertyName: withCdpName( + async (params?: cdp.types.ts.CSS.SetPropertyRulePropertyNameParams) => + commands["CSS.setPropertyRulePropertyName"].result.parse( + await send( + "CSS.setPropertyRulePropertyName", + commands["CSS.setPropertyRulePropertyName"].params.parse(params ?? {}), + ), + ), + "CSS.setPropertyRulePropertyName", + "command", + ), + setKeyframeKey: withCdpName( + async (params?: cdp.types.ts.CSS.SetKeyframeKeyParams) => + commands["CSS.setKeyframeKey"].result.parse( + await send("CSS.setKeyframeKey", commands["CSS.setKeyframeKey"].params.parse(params ?? {})), + ), + "CSS.setKeyframeKey", + "command", + ), + setMediaText: withCdpName( + async (params?: cdp.types.ts.CSS.SetMediaTextParams) => + commands["CSS.setMediaText"].result.parse( + await send("CSS.setMediaText", commands["CSS.setMediaText"].params.parse(params ?? {})), + ), + "CSS.setMediaText", + "command", + ), + setContainerQueryText: withCdpName( + async (params?: cdp.types.ts.CSS.SetContainerQueryTextParams) => + commands["CSS.setContainerQueryText"].result.parse( + await send("CSS.setContainerQueryText", commands["CSS.setContainerQueryText"].params.parse(params ?? {})), + ), + "CSS.setContainerQueryText", + "command", + ), + setSupportsText: withCdpName( + async (params?: cdp.types.ts.CSS.SetSupportsTextParams) => + commands["CSS.setSupportsText"].result.parse( + await send("CSS.setSupportsText", commands["CSS.setSupportsText"].params.parse(params ?? {})), + ), + "CSS.setSupportsText", + "command", + ), + setNavigationText: withCdpName( + async (params?: cdp.types.ts.CSS.SetNavigationTextParams) => + commands["CSS.setNavigationText"].result.parse( + await send("CSS.setNavigationText", commands["CSS.setNavigationText"].params.parse(params ?? {})), + ), + "CSS.setNavigationText", + "command", + ), + setScopeText: withCdpName( + async (params?: cdp.types.ts.CSS.SetScopeTextParams) => + commands["CSS.setScopeText"].result.parse( + await send("CSS.setScopeText", commands["CSS.setScopeText"].params.parse(params ?? {})), + ), + "CSS.setScopeText", + "command", + ), + setRuleSelector: withCdpName( + async (params?: cdp.types.ts.CSS.SetRuleSelectorParams) => + commands["CSS.setRuleSelector"].result.parse( + await send("CSS.setRuleSelector", commands["CSS.setRuleSelector"].params.parse(params ?? {})), + ), + "CSS.setRuleSelector", + "command", + ), + setStyleSheetText: withCdpName( + async (params?: cdp.types.ts.CSS.SetStyleSheetTextParams) => + commands["CSS.setStyleSheetText"].result.parse( + await send("CSS.setStyleSheetText", commands["CSS.setStyleSheetText"].params.parse(params ?? {})), + ), + "CSS.setStyleSheetText", + "command", + ), + setStyleTexts: withCdpName( + async (params?: cdp.types.ts.CSS.SetStyleTextsParams) => + commands["CSS.setStyleTexts"].result.parse( + await send("CSS.setStyleTexts", commands["CSS.setStyleTexts"].params.parse(params ?? {})), + ), + "CSS.setStyleTexts", + "command", + ), + startRuleUsageTracking: withCdpName( + async (params?: cdp.types.ts.CSS.StartRuleUsageTrackingParams) => + commands["CSS.startRuleUsageTracking"].result.parse( + await send("CSS.startRuleUsageTracking", commands["CSS.startRuleUsageTracking"].params.parse(params ?? {})), + ), + "CSS.startRuleUsageTracking", + "command", + ), + stopRuleUsageTracking: withCdpName( + async (params?: cdp.types.ts.CSS.StopRuleUsageTrackingParams) => + commands["CSS.stopRuleUsageTracking"].result.parse( + await send("CSS.stopRuleUsageTracking", commands["CSS.stopRuleUsageTracking"].params.parse(params ?? {})), + ), + "CSS.stopRuleUsageTracking", + "command", + ), + takeCoverageDelta: withCdpName( + async (params?: cdp.types.ts.CSS.TakeCoverageDeltaParams) => + commands["CSS.takeCoverageDelta"].result.parse( + await send("CSS.takeCoverageDelta", commands["CSS.takeCoverageDelta"].params.parse(params ?? {})), + ), + "CSS.takeCoverageDelta", + "command", + ), + setLocalFontsEnabled: withCdpName( + async (params?: cdp.types.ts.CSS.SetLocalFontsEnabledParams) => + commands["CSS.setLocalFontsEnabled"].result.parse( + await send("CSS.setLocalFontsEnabled", commands["CSS.setLocalFontsEnabled"].params.parse(params ?? {})), + ), + "CSS.setLocalFontsEnabled", + "command", + ), + fontsUpdated: withCdpName(events["CSS.fontsUpdated"], "CSS.fontsUpdated", "event"), + mediaQueryResultChanged: withCdpName( + events["CSS.mediaQueryResultChanged"], + "CSS.mediaQueryResultChanged", + "event", + ), + styleSheetAdded: withCdpName(events["CSS.styleSheetAdded"], "CSS.styleSheetAdded", "event"), + styleSheetChanged: withCdpName(events["CSS.styleSheetChanged"], "CSS.styleSheetChanged", "event"), + styleSheetRemoved: withCdpName(events["CSS.styleSheetRemoved"], "CSS.styleSheetRemoved", "event"), + computedStyleUpdated: withCdpName(events["CSS.computedStyleUpdated"], "CSS.computedStyleUpdated", "event"), }, Debugger: { - continueToLocation: withCdpName(async (params?: unknown) => commands["Debugger.continueToLocation"].result.parse(await send("Debugger.continueToLocation", commands["Debugger.continueToLocation"].params.parse(params ?? {}))), "Debugger.continueToLocation", "command"), - disable: withCdpName(async (params?: unknown) => commands["Debugger.disable"].result.parse(await send("Debugger.disable", commands["Debugger.disable"].params.parse(params ?? {}))), "Debugger.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Debugger.enable"].result.parse(await send("Debugger.enable", commands["Debugger.enable"].params.parse(params ?? {}))), "Debugger.enable", "command"), - evaluateOnCallFrame: withCdpName(async (params?: unknown) => commands["Debugger.evaluateOnCallFrame"].result.parse(await send("Debugger.evaluateOnCallFrame", commands["Debugger.evaluateOnCallFrame"].params.parse(params ?? {}))), "Debugger.evaluateOnCallFrame", "command"), - getPossibleBreakpoints: withCdpName(async (params?: unknown) => commands["Debugger.getPossibleBreakpoints"].result.parse(await send("Debugger.getPossibleBreakpoints", commands["Debugger.getPossibleBreakpoints"].params.parse(params ?? {}))), "Debugger.getPossibleBreakpoints", "command"), - getScriptSource: withCdpName(async (params?: unknown) => commands["Debugger.getScriptSource"].result.parse(await send("Debugger.getScriptSource", commands["Debugger.getScriptSource"].params.parse(params ?? {}))), "Debugger.getScriptSource", "command"), - disassembleWasmModule: withCdpName(async (params?: unknown) => commands["Debugger.disassembleWasmModule"].result.parse(await send("Debugger.disassembleWasmModule", commands["Debugger.disassembleWasmModule"].params.parse(params ?? {}))), "Debugger.disassembleWasmModule", "command"), - nextWasmDisassemblyChunk: withCdpName(async (params?: unknown) => commands["Debugger.nextWasmDisassemblyChunk"].result.parse(await send("Debugger.nextWasmDisassemblyChunk", commands["Debugger.nextWasmDisassemblyChunk"].params.parse(params ?? {}))), "Debugger.nextWasmDisassemblyChunk", "command"), - getWasmBytecode: withCdpName(async (params?: unknown) => commands["Debugger.getWasmBytecode"].result.parse(await send("Debugger.getWasmBytecode", commands["Debugger.getWasmBytecode"].params.parse(params ?? {}))), "Debugger.getWasmBytecode", "command"), - getStackTrace: withCdpName(async (params?: unknown) => commands["Debugger.getStackTrace"].result.parse(await send("Debugger.getStackTrace", commands["Debugger.getStackTrace"].params.parse(params ?? {}))), "Debugger.getStackTrace", "command"), - pause: withCdpName(async (params?: unknown) => commands["Debugger.pause"].result.parse(await send("Debugger.pause", commands["Debugger.pause"].params.parse(params ?? {}))), "Debugger.pause", "command"), - pauseOnAsyncCall: withCdpName(async (params?: unknown) => commands["Debugger.pauseOnAsyncCall"].result.parse(await send("Debugger.pauseOnAsyncCall", commands["Debugger.pauseOnAsyncCall"].params.parse(params ?? {}))), "Debugger.pauseOnAsyncCall", "command"), - removeBreakpoint: withCdpName(async (params?: unknown) => commands["Debugger.removeBreakpoint"].result.parse(await send("Debugger.removeBreakpoint", commands["Debugger.removeBreakpoint"].params.parse(params ?? {}))), "Debugger.removeBreakpoint", "command"), - restartFrame: withCdpName(async (params?: unknown) => commands["Debugger.restartFrame"].result.parse(await send("Debugger.restartFrame", commands["Debugger.restartFrame"].params.parse(params ?? {}))), "Debugger.restartFrame", "command"), - resume: withCdpName(async (params?: unknown) => commands["Debugger.resume"].result.parse(await send("Debugger.resume", commands["Debugger.resume"].params.parse(params ?? {}))), "Debugger.resume", "command"), - searchInContent: withCdpName(async (params?: unknown) => commands["Debugger.searchInContent"].result.parse(await send("Debugger.searchInContent", commands["Debugger.searchInContent"].params.parse(params ?? {}))), "Debugger.searchInContent", "command"), - setAsyncCallStackDepth: withCdpName(async (params?: unknown) => commands["Debugger.setAsyncCallStackDepth"].result.parse(await send("Debugger.setAsyncCallStackDepth", commands["Debugger.setAsyncCallStackDepth"].params.parse(params ?? {}))), "Debugger.setAsyncCallStackDepth", "command"), - setBlackboxExecutionContexts: withCdpName(async (params?: unknown) => commands["Debugger.setBlackboxExecutionContexts"].result.parse(await send("Debugger.setBlackboxExecutionContexts", commands["Debugger.setBlackboxExecutionContexts"].params.parse(params ?? {}))), "Debugger.setBlackboxExecutionContexts", "command"), - setBlackboxPatterns: withCdpName(async (params?: unknown) => commands["Debugger.setBlackboxPatterns"].result.parse(await send("Debugger.setBlackboxPatterns", commands["Debugger.setBlackboxPatterns"].params.parse(params ?? {}))), "Debugger.setBlackboxPatterns", "command"), - setBlackboxedRanges: withCdpName(async (params?: unknown) => commands["Debugger.setBlackboxedRanges"].result.parse(await send("Debugger.setBlackboxedRanges", commands["Debugger.setBlackboxedRanges"].params.parse(params ?? {}))), "Debugger.setBlackboxedRanges", "command"), - setBreakpoint: withCdpName(async (params?: unknown) => commands["Debugger.setBreakpoint"].result.parse(await send("Debugger.setBreakpoint", commands["Debugger.setBreakpoint"].params.parse(params ?? {}))), "Debugger.setBreakpoint", "command"), - setInstrumentationBreakpoint: withCdpName(async (params?: unknown) => commands["Debugger.setInstrumentationBreakpoint"].result.parse(await send("Debugger.setInstrumentationBreakpoint", commands["Debugger.setInstrumentationBreakpoint"].params.parse(params ?? {}))), "Debugger.setInstrumentationBreakpoint", "command"), - setBreakpointByUrl: withCdpName(async (params?: unknown) => commands["Debugger.setBreakpointByUrl"].result.parse(await send("Debugger.setBreakpointByUrl", commands["Debugger.setBreakpointByUrl"].params.parse(params ?? {}))), "Debugger.setBreakpointByUrl", "command"), - setBreakpointOnFunctionCall: withCdpName(async (params?: unknown) => commands["Debugger.setBreakpointOnFunctionCall"].result.parse(await send("Debugger.setBreakpointOnFunctionCall", commands["Debugger.setBreakpointOnFunctionCall"].params.parse(params ?? {}))), "Debugger.setBreakpointOnFunctionCall", "command"), - setBreakpointsActive: withCdpName(async (params?: unknown) => commands["Debugger.setBreakpointsActive"].result.parse(await send("Debugger.setBreakpointsActive", commands["Debugger.setBreakpointsActive"].params.parse(params ?? {}))), "Debugger.setBreakpointsActive", "command"), - setPauseOnExceptions: withCdpName(async (params?: unknown) => commands["Debugger.setPauseOnExceptions"].result.parse(await send("Debugger.setPauseOnExceptions", commands["Debugger.setPauseOnExceptions"].params.parse(params ?? {}))), "Debugger.setPauseOnExceptions", "command"), - setReturnValue: withCdpName(async (params?: unknown) => commands["Debugger.setReturnValue"].result.parse(await send("Debugger.setReturnValue", commands["Debugger.setReturnValue"].params.parse(params ?? {}))), "Debugger.setReturnValue", "command"), - setScriptSource: withCdpName(async (params?: unknown) => commands["Debugger.setScriptSource"].result.parse(await send("Debugger.setScriptSource", commands["Debugger.setScriptSource"].params.parse(params ?? {}))), "Debugger.setScriptSource", "command"), - setSkipAllPauses: withCdpName(async (params?: unknown) => commands["Debugger.setSkipAllPauses"].result.parse(await send("Debugger.setSkipAllPauses", commands["Debugger.setSkipAllPauses"].params.parse(params ?? {}))), "Debugger.setSkipAllPauses", "command"), - setVariableValue: withCdpName(async (params?: unknown) => commands["Debugger.setVariableValue"].result.parse(await send("Debugger.setVariableValue", commands["Debugger.setVariableValue"].params.parse(params ?? {}))), "Debugger.setVariableValue", "command"), - stepInto: withCdpName(async (params?: unknown) => commands["Debugger.stepInto"].result.parse(await send("Debugger.stepInto", commands["Debugger.stepInto"].params.parse(params ?? {}))), "Debugger.stepInto", "command"), - stepOut: withCdpName(async (params?: unknown) => commands["Debugger.stepOut"].result.parse(await send("Debugger.stepOut", commands["Debugger.stepOut"].params.parse(params ?? {}))), "Debugger.stepOut", "command"), - stepOver: withCdpName(async (params?: unknown) => commands["Debugger.stepOver"].result.parse(await send("Debugger.stepOver", commands["Debugger.stepOver"].params.parse(params ?? {}))), "Debugger.stepOver", "command"), - breakpointResolved: events["Debugger.breakpointResolved"] as CdpEventAlias, - paused: events["Debugger.paused"] as CdpEventAlias, - resumed: events["Debugger.resumed"] as CdpEventAlias, - scriptFailedToParse: events["Debugger.scriptFailedToParse"] as CdpEventAlias, - scriptParsed: events["Debugger.scriptParsed"] as CdpEventAlias, + continueToLocation: withCdpName( + async (params?: cdp.types.ts.Debugger.ContinueToLocationParams) => + commands["Debugger.continueToLocation"].result.parse( + await send( + "Debugger.continueToLocation", + commands["Debugger.continueToLocation"].params.parse(params ?? {}), + ), + ), + "Debugger.continueToLocation", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Debugger.DisableParams) => + commands["Debugger.disable"].result.parse( + await send("Debugger.disable", commands["Debugger.disable"].params.parse(params ?? {})), + ), + "Debugger.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Debugger.EnableParams) => + commands["Debugger.enable"].result.parse( + await send("Debugger.enable", commands["Debugger.enable"].params.parse(params ?? {})), + ), + "Debugger.enable", + "command", + ), + evaluateOnCallFrame: withCdpName( + async (params?: cdp.types.ts.Debugger.EvaluateOnCallFrameParams) => + commands["Debugger.evaluateOnCallFrame"].result.parse( + await send( + "Debugger.evaluateOnCallFrame", + commands["Debugger.evaluateOnCallFrame"].params.parse(params ?? {}), + ), + ), + "Debugger.evaluateOnCallFrame", + "command", + ), + getPossibleBreakpoints: withCdpName( + async (params?: cdp.types.ts.Debugger.GetPossibleBreakpointsParams) => + commands["Debugger.getPossibleBreakpoints"].result.parse( + await send( + "Debugger.getPossibleBreakpoints", + commands["Debugger.getPossibleBreakpoints"].params.parse(params ?? {}), + ), + ), + "Debugger.getPossibleBreakpoints", + "command", + ), + getScriptSource: withCdpName( + async (params?: cdp.types.ts.Debugger.GetScriptSourceParams) => + commands["Debugger.getScriptSource"].result.parse( + await send("Debugger.getScriptSource", commands["Debugger.getScriptSource"].params.parse(params ?? {})), + ), + "Debugger.getScriptSource", + "command", + ), + disassembleWasmModule: withCdpName( + async (params?: cdp.types.ts.Debugger.DisassembleWasmModuleParams) => + commands["Debugger.disassembleWasmModule"].result.parse( + await send( + "Debugger.disassembleWasmModule", + commands["Debugger.disassembleWasmModule"].params.parse(params ?? {}), + ), + ), + "Debugger.disassembleWasmModule", + "command", + ), + nextWasmDisassemblyChunk: withCdpName( + async (params?: cdp.types.ts.Debugger.NextWasmDisassemblyChunkParams) => + commands["Debugger.nextWasmDisassemblyChunk"].result.parse( + await send( + "Debugger.nextWasmDisassemblyChunk", + commands["Debugger.nextWasmDisassemblyChunk"].params.parse(params ?? {}), + ), + ), + "Debugger.nextWasmDisassemblyChunk", + "command", + ), + getWasmBytecode: withCdpName( + async (params?: cdp.types.ts.Debugger.GetWasmBytecodeParams) => + commands["Debugger.getWasmBytecode"].result.parse( + await send("Debugger.getWasmBytecode", commands["Debugger.getWasmBytecode"].params.parse(params ?? {})), + ), + "Debugger.getWasmBytecode", + "command", + ), + getStackTrace: withCdpName( + async (params?: cdp.types.ts.Debugger.GetStackTraceParams) => + commands["Debugger.getStackTrace"].result.parse( + await send("Debugger.getStackTrace", commands["Debugger.getStackTrace"].params.parse(params ?? {})), + ), + "Debugger.getStackTrace", + "command", + ), + pause: withCdpName( + async (params?: cdp.types.ts.Debugger.PauseParams) => + commands["Debugger.pause"].result.parse( + await send("Debugger.pause", commands["Debugger.pause"].params.parse(params ?? {})), + ), + "Debugger.pause", + "command", + ), + pauseOnAsyncCall: withCdpName( + async (params?: cdp.types.ts.Debugger.PauseOnAsyncCallParams) => + commands["Debugger.pauseOnAsyncCall"].result.parse( + await send("Debugger.pauseOnAsyncCall", commands["Debugger.pauseOnAsyncCall"].params.parse(params ?? {})), + ), + "Debugger.pauseOnAsyncCall", + "command", + ), + removeBreakpoint: withCdpName( + async (params?: cdp.types.ts.Debugger.RemoveBreakpointParams) => + commands["Debugger.removeBreakpoint"].result.parse( + await send("Debugger.removeBreakpoint", commands["Debugger.removeBreakpoint"].params.parse(params ?? {})), + ), + "Debugger.removeBreakpoint", + "command", + ), + restartFrame: withCdpName( + async (params?: cdp.types.ts.Debugger.RestartFrameParams) => + commands["Debugger.restartFrame"].result.parse( + await send("Debugger.restartFrame", commands["Debugger.restartFrame"].params.parse(params ?? {})), + ), + "Debugger.restartFrame", + "command", + ), + resume: withCdpName( + async (params?: cdp.types.ts.Debugger.ResumeParams) => + commands["Debugger.resume"].result.parse( + await send("Debugger.resume", commands["Debugger.resume"].params.parse(params ?? {})), + ), + "Debugger.resume", + "command", + ), + searchInContent: withCdpName( + async (params?: cdp.types.ts.Debugger.SearchInContentParams) => + commands["Debugger.searchInContent"].result.parse( + await send("Debugger.searchInContent", commands["Debugger.searchInContent"].params.parse(params ?? {})), + ), + "Debugger.searchInContent", + "command", + ), + setAsyncCallStackDepth: withCdpName( + async (params?: cdp.types.ts.Debugger.SetAsyncCallStackDepthParams) => + commands["Debugger.setAsyncCallStackDepth"].result.parse( + await send( + "Debugger.setAsyncCallStackDepth", + commands["Debugger.setAsyncCallStackDepth"].params.parse(params ?? {}), + ), + ), + "Debugger.setAsyncCallStackDepth", + "command", + ), + setBlackboxExecutionContexts: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBlackboxExecutionContextsParams) => + commands["Debugger.setBlackboxExecutionContexts"].result.parse( + await send( + "Debugger.setBlackboxExecutionContexts", + commands["Debugger.setBlackboxExecutionContexts"].params.parse(params ?? {}), + ), + ), + "Debugger.setBlackboxExecutionContexts", + "command", + ), + setBlackboxPatterns: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBlackboxPatternsParams) => + commands["Debugger.setBlackboxPatterns"].result.parse( + await send( + "Debugger.setBlackboxPatterns", + commands["Debugger.setBlackboxPatterns"].params.parse(params ?? {}), + ), + ), + "Debugger.setBlackboxPatterns", + "command", + ), + setBlackboxedRanges: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBlackboxedRangesParams) => + commands["Debugger.setBlackboxedRanges"].result.parse( + await send( + "Debugger.setBlackboxedRanges", + commands["Debugger.setBlackboxedRanges"].params.parse(params ?? {}), + ), + ), + "Debugger.setBlackboxedRanges", + "command", + ), + setBreakpoint: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBreakpointParams) => + commands["Debugger.setBreakpoint"].result.parse( + await send("Debugger.setBreakpoint", commands["Debugger.setBreakpoint"].params.parse(params ?? {})), + ), + "Debugger.setBreakpoint", + "command", + ), + setInstrumentationBreakpoint: withCdpName( + async (params?: cdp.types.ts.Debugger.SetInstrumentationBreakpointParams) => + commands["Debugger.setInstrumentationBreakpoint"].result.parse( + await send( + "Debugger.setInstrumentationBreakpoint", + commands["Debugger.setInstrumentationBreakpoint"].params.parse(params ?? {}), + ), + ), + "Debugger.setInstrumentationBreakpoint", + "command", + ), + setBreakpointByUrl: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBreakpointByUrlParams) => + commands["Debugger.setBreakpointByUrl"].result.parse( + await send( + "Debugger.setBreakpointByUrl", + commands["Debugger.setBreakpointByUrl"].params.parse(params ?? {}), + ), + ), + "Debugger.setBreakpointByUrl", + "command", + ), + setBreakpointOnFunctionCall: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBreakpointOnFunctionCallParams) => + commands["Debugger.setBreakpointOnFunctionCall"].result.parse( + await send( + "Debugger.setBreakpointOnFunctionCall", + commands["Debugger.setBreakpointOnFunctionCall"].params.parse(params ?? {}), + ), + ), + "Debugger.setBreakpointOnFunctionCall", + "command", + ), + setBreakpointsActive: withCdpName( + async (params?: cdp.types.ts.Debugger.SetBreakpointsActiveParams) => + commands["Debugger.setBreakpointsActive"].result.parse( + await send( + "Debugger.setBreakpointsActive", + commands["Debugger.setBreakpointsActive"].params.parse(params ?? {}), + ), + ), + "Debugger.setBreakpointsActive", + "command", + ), + setPauseOnExceptions: withCdpName( + async (params?: cdp.types.ts.Debugger.SetPauseOnExceptionsParams) => + commands["Debugger.setPauseOnExceptions"].result.parse( + await send( + "Debugger.setPauseOnExceptions", + commands["Debugger.setPauseOnExceptions"].params.parse(params ?? {}), + ), + ), + "Debugger.setPauseOnExceptions", + "command", + ), + setReturnValue: withCdpName( + async (params?: cdp.types.ts.Debugger.SetReturnValueParams) => + commands["Debugger.setReturnValue"].result.parse( + await send("Debugger.setReturnValue", commands["Debugger.setReturnValue"].params.parse(params ?? {})), + ), + "Debugger.setReturnValue", + "command", + ), + setScriptSource: withCdpName( + async (params?: cdp.types.ts.Debugger.SetScriptSourceParams) => + commands["Debugger.setScriptSource"].result.parse( + await send("Debugger.setScriptSource", commands["Debugger.setScriptSource"].params.parse(params ?? {})), + ), + "Debugger.setScriptSource", + "command", + ), + setSkipAllPauses: withCdpName( + async (params?: cdp.types.ts.Debugger.SetSkipAllPausesParams) => + commands["Debugger.setSkipAllPauses"].result.parse( + await send("Debugger.setSkipAllPauses", commands["Debugger.setSkipAllPauses"].params.parse(params ?? {})), + ), + "Debugger.setSkipAllPauses", + "command", + ), + setVariableValue: withCdpName( + async (params?: cdp.types.ts.Debugger.SetVariableValueParams) => + commands["Debugger.setVariableValue"].result.parse( + await send("Debugger.setVariableValue", commands["Debugger.setVariableValue"].params.parse(params ?? {})), + ), + "Debugger.setVariableValue", + "command", + ), + stepInto: withCdpName( + async (params?: cdp.types.ts.Debugger.StepIntoParams) => + commands["Debugger.stepInto"].result.parse( + await send("Debugger.stepInto", commands["Debugger.stepInto"].params.parse(params ?? {})), + ), + "Debugger.stepInto", + "command", + ), + stepOut: withCdpName( + async (params?: cdp.types.ts.Debugger.StepOutParams) => + commands["Debugger.stepOut"].result.parse( + await send("Debugger.stepOut", commands["Debugger.stepOut"].params.parse(params ?? {})), + ), + "Debugger.stepOut", + "command", + ), + stepOver: withCdpName( + async (params?: cdp.types.ts.Debugger.StepOverParams) => + commands["Debugger.stepOver"].result.parse( + await send("Debugger.stepOver", commands["Debugger.stepOver"].params.parse(params ?? {})), + ), + "Debugger.stepOver", + "command", + ), + breakpointResolved: withCdpName(events["Debugger.breakpointResolved"], "Debugger.breakpointResolved", "event"), + paused: withCdpName(events["Debugger.paused"], "Debugger.paused", "event"), + resumed: withCdpName(events["Debugger.resumed"], "Debugger.resumed", "event"), + scriptFailedToParse: withCdpName(events["Debugger.scriptFailedToParse"], "Debugger.scriptFailedToParse", "event"), + scriptParsed: withCdpName(events["Debugger.scriptParsed"], "Debugger.scriptParsed", "event"), }, DeviceAccess: { - enable: withCdpName(async (params?: unknown) => commands["DeviceAccess.enable"].result.parse(await send("DeviceAccess.enable", commands["DeviceAccess.enable"].params.parse(params ?? {}))), "DeviceAccess.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["DeviceAccess.disable"].result.parse(await send("DeviceAccess.disable", commands["DeviceAccess.disable"].params.parse(params ?? {}))), "DeviceAccess.disable", "command"), - selectPrompt: withCdpName(async (params?: unknown) => commands["DeviceAccess.selectPrompt"].result.parse(await send("DeviceAccess.selectPrompt", commands["DeviceAccess.selectPrompt"].params.parse(params ?? {}))), "DeviceAccess.selectPrompt", "command"), - cancelPrompt: withCdpName(async (params?: unknown) => commands["DeviceAccess.cancelPrompt"].result.parse(await send("DeviceAccess.cancelPrompt", commands["DeviceAccess.cancelPrompt"].params.parse(params ?? {}))), "DeviceAccess.cancelPrompt", "command"), - deviceRequestPrompted: events["DeviceAccess.deviceRequestPrompted"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.DeviceAccess.EnableParams) => + commands["DeviceAccess.enable"].result.parse( + await send("DeviceAccess.enable", commands["DeviceAccess.enable"].params.parse(params ?? {})), + ), + "DeviceAccess.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.DeviceAccess.DisableParams) => + commands["DeviceAccess.disable"].result.parse( + await send("DeviceAccess.disable", commands["DeviceAccess.disable"].params.parse(params ?? {})), + ), + "DeviceAccess.disable", + "command", + ), + selectPrompt: withCdpName( + async (params?: cdp.types.ts.DeviceAccess.SelectPromptParams) => + commands["DeviceAccess.selectPrompt"].result.parse( + await send("DeviceAccess.selectPrompt", commands["DeviceAccess.selectPrompt"].params.parse(params ?? {})), + ), + "DeviceAccess.selectPrompt", + "command", + ), + cancelPrompt: withCdpName( + async (params?: cdp.types.ts.DeviceAccess.CancelPromptParams) => + commands["DeviceAccess.cancelPrompt"].result.parse( + await send("DeviceAccess.cancelPrompt", commands["DeviceAccess.cancelPrompt"].params.parse(params ?? {})), + ), + "DeviceAccess.cancelPrompt", + "command", + ), + deviceRequestPrompted: withCdpName( + events["DeviceAccess.deviceRequestPrompted"], + "DeviceAccess.deviceRequestPrompted", + "event", + ), }, DeviceOrientation: { - clearDeviceOrientationOverride: withCdpName(async (params?: unknown) => commands["DeviceOrientation.clearDeviceOrientationOverride"].result.parse(await send("DeviceOrientation.clearDeviceOrientationOverride", commands["DeviceOrientation.clearDeviceOrientationOverride"].params.parse(params ?? {}))), "DeviceOrientation.clearDeviceOrientationOverride", "command"), - setDeviceOrientationOverride: withCdpName(async (params?: unknown) => commands["DeviceOrientation.setDeviceOrientationOverride"].result.parse(await send("DeviceOrientation.setDeviceOrientationOverride", commands["DeviceOrientation.setDeviceOrientationOverride"].params.parse(params ?? {}))), "DeviceOrientation.setDeviceOrientationOverride", "command"), + clearDeviceOrientationOverride: withCdpName( + async (params?: cdp.types.ts.DeviceOrientation.ClearDeviceOrientationOverrideParams) => + commands["DeviceOrientation.clearDeviceOrientationOverride"].result.parse( + await send( + "DeviceOrientation.clearDeviceOrientationOverride", + commands["DeviceOrientation.clearDeviceOrientationOverride"].params.parse(params ?? {}), + ), + ), + "DeviceOrientation.clearDeviceOrientationOverride", + "command", + ), + setDeviceOrientationOverride: withCdpName( + async (params?: cdp.types.ts.DeviceOrientation.SetDeviceOrientationOverrideParams) => + commands["DeviceOrientation.setDeviceOrientationOverride"].result.parse( + await send( + "DeviceOrientation.setDeviceOrientationOverride", + commands["DeviceOrientation.setDeviceOrientationOverride"].params.parse(params ?? {}), + ), + ), + "DeviceOrientation.setDeviceOrientationOverride", + "command", + ), }, DOM: { - collectClassNamesFromSubtree: withCdpName(async (params?: unknown) => commands["DOM.collectClassNamesFromSubtree"].result.parse(await send("DOM.collectClassNamesFromSubtree", commands["DOM.collectClassNamesFromSubtree"].params.parse(params ?? {}))), "DOM.collectClassNamesFromSubtree", "command"), - copyTo: withCdpName(async (params?: unknown) => commands["DOM.copyTo"].result.parse(await send("DOM.copyTo", commands["DOM.copyTo"].params.parse(params ?? {}))), "DOM.copyTo", "command"), - describeNode: withCdpName(async (params?: unknown) => commands["DOM.describeNode"].result.parse(await send("DOM.describeNode", commands["DOM.describeNode"].params.parse(params ?? {}))), "DOM.describeNode", "command"), - scrollIntoViewIfNeeded: withCdpName(async (params?: unknown) => commands["DOM.scrollIntoViewIfNeeded"].result.parse(await send("DOM.scrollIntoViewIfNeeded", commands["DOM.scrollIntoViewIfNeeded"].params.parse(params ?? {}))), "DOM.scrollIntoViewIfNeeded", "command"), - disable: withCdpName(async (params?: unknown) => commands["DOM.disable"].result.parse(await send("DOM.disable", commands["DOM.disable"].params.parse(params ?? {}))), "DOM.disable", "command"), - discardSearchResults: withCdpName(async (params?: unknown) => commands["DOM.discardSearchResults"].result.parse(await send("DOM.discardSearchResults", commands["DOM.discardSearchResults"].params.parse(params ?? {}))), "DOM.discardSearchResults", "command"), - enable: withCdpName(async (params?: unknown) => commands["DOM.enable"].result.parse(await send("DOM.enable", commands["DOM.enable"].params.parse(params ?? {}))), "DOM.enable", "command"), - focus: withCdpName(async (params?: unknown) => commands["DOM.focus"].result.parse(await send("DOM.focus", commands["DOM.focus"].params.parse(params ?? {}))), "DOM.focus", "command"), - getAttributes: withCdpName(async (params?: unknown) => commands["DOM.getAttributes"].result.parse(await send("DOM.getAttributes", commands["DOM.getAttributes"].params.parse(params ?? {}))), "DOM.getAttributes", "command"), - getBoxModel: withCdpName(async (params?: unknown) => commands["DOM.getBoxModel"].result.parse(await send("DOM.getBoxModel", commands["DOM.getBoxModel"].params.parse(params ?? {}))), "DOM.getBoxModel", "command"), - getContentQuads: withCdpName(async (params?: unknown) => commands["DOM.getContentQuads"].result.parse(await send("DOM.getContentQuads", commands["DOM.getContentQuads"].params.parse(params ?? {}))), "DOM.getContentQuads", "command"), - getDocument: withCdpName(async (params?: unknown) => commands["DOM.getDocument"].result.parse(await send("DOM.getDocument", commands["DOM.getDocument"].params.parse(params ?? {}))), "DOM.getDocument", "command"), - getFlattenedDocument: withCdpName(async (params?: unknown) => commands["DOM.getFlattenedDocument"].result.parse(await send("DOM.getFlattenedDocument", commands["DOM.getFlattenedDocument"].params.parse(params ?? {}))), "DOM.getFlattenedDocument", "command"), - getNodesForSubtreeByStyle: withCdpName(async (params?: unknown) => commands["DOM.getNodesForSubtreeByStyle"].result.parse(await send("DOM.getNodesForSubtreeByStyle", commands["DOM.getNodesForSubtreeByStyle"].params.parse(params ?? {}))), "DOM.getNodesForSubtreeByStyle", "command"), - getNodeForLocation: withCdpName(async (params?: unknown) => commands["DOM.getNodeForLocation"].result.parse(await send("DOM.getNodeForLocation", commands["DOM.getNodeForLocation"].params.parse(params ?? {}))), "DOM.getNodeForLocation", "command"), - getOuterHTML: withCdpName(async (params?: unknown) => commands["DOM.getOuterHTML"].result.parse(await send("DOM.getOuterHTML", commands["DOM.getOuterHTML"].params.parse(params ?? {}))), "DOM.getOuterHTML", "command"), - getRelayoutBoundary: withCdpName(async (params?: unknown) => commands["DOM.getRelayoutBoundary"].result.parse(await send("DOM.getRelayoutBoundary", commands["DOM.getRelayoutBoundary"].params.parse(params ?? {}))), "DOM.getRelayoutBoundary", "command"), - getSearchResults: withCdpName(async (params?: unknown) => commands["DOM.getSearchResults"].result.parse(await send("DOM.getSearchResults", commands["DOM.getSearchResults"].params.parse(params ?? {}))), "DOM.getSearchResults", "command"), - hideHighlight: withCdpName(async (params?: unknown) => commands["DOM.hideHighlight"].result.parse(await send("DOM.hideHighlight", commands["DOM.hideHighlight"].params.parse(params ?? {}))), "DOM.hideHighlight", "command"), - highlightNode: withCdpName(async (params?: unknown) => commands["DOM.highlightNode"].result.parse(await send("DOM.highlightNode", commands["DOM.highlightNode"].params.parse(params ?? {}))), "DOM.highlightNode", "command"), - highlightRect: withCdpName(async (params?: unknown) => commands["DOM.highlightRect"].result.parse(await send("DOM.highlightRect", commands["DOM.highlightRect"].params.parse(params ?? {}))), "DOM.highlightRect", "command"), - markUndoableState: withCdpName(async (params?: unknown) => commands["DOM.markUndoableState"].result.parse(await send("DOM.markUndoableState", commands["DOM.markUndoableState"].params.parse(params ?? {}))), "DOM.markUndoableState", "command"), - moveTo: withCdpName(async (params?: unknown) => commands["DOM.moveTo"].result.parse(await send("DOM.moveTo", commands["DOM.moveTo"].params.parse(params ?? {}))), "DOM.moveTo", "command"), - performSearch: withCdpName(async (params?: unknown) => commands["DOM.performSearch"].result.parse(await send("DOM.performSearch", commands["DOM.performSearch"].params.parse(params ?? {}))), "DOM.performSearch", "command"), - pushNodeByPathToFrontend: withCdpName(async (params?: unknown) => commands["DOM.pushNodeByPathToFrontend"].result.parse(await send("DOM.pushNodeByPathToFrontend", commands["DOM.pushNodeByPathToFrontend"].params.parse(params ?? {}))), "DOM.pushNodeByPathToFrontend", "command"), - pushNodesByBackendIdsToFrontend: withCdpName(async (params?: unknown) => commands["DOM.pushNodesByBackendIdsToFrontend"].result.parse(await send("DOM.pushNodesByBackendIdsToFrontend", commands["DOM.pushNodesByBackendIdsToFrontend"].params.parse(params ?? {}))), "DOM.pushNodesByBackendIdsToFrontend", "command"), - querySelector: withCdpName(async (params?: unknown) => commands["DOM.querySelector"].result.parse(await send("DOM.querySelector", commands["DOM.querySelector"].params.parse(params ?? {}))), "DOM.querySelector", "command"), - querySelectorAll: withCdpName(async (params?: unknown) => commands["DOM.querySelectorAll"].result.parse(await send("DOM.querySelectorAll", commands["DOM.querySelectorAll"].params.parse(params ?? {}))), "DOM.querySelectorAll", "command"), - getTopLayerElements: withCdpName(async (params?: unknown) => commands["DOM.getTopLayerElements"].result.parse(await send("DOM.getTopLayerElements", commands["DOM.getTopLayerElements"].params.parse(params ?? {}))), "DOM.getTopLayerElements", "command"), - getElementByRelation: withCdpName(async (params?: unknown) => commands["DOM.getElementByRelation"].result.parse(await send("DOM.getElementByRelation", commands["DOM.getElementByRelation"].params.parse(params ?? {}))), "DOM.getElementByRelation", "command"), - redo: withCdpName(async (params?: unknown) => commands["DOM.redo"].result.parse(await send("DOM.redo", commands["DOM.redo"].params.parse(params ?? {}))), "DOM.redo", "command"), - removeAttribute: withCdpName(async (params?: unknown) => commands["DOM.removeAttribute"].result.parse(await send("DOM.removeAttribute", commands["DOM.removeAttribute"].params.parse(params ?? {}))), "DOM.removeAttribute", "command"), - removeNode: withCdpName(async (params?: unknown) => commands["DOM.removeNode"].result.parse(await send("DOM.removeNode", commands["DOM.removeNode"].params.parse(params ?? {}))), "DOM.removeNode", "command"), - requestChildNodes: withCdpName(async (params?: unknown) => commands["DOM.requestChildNodes"].result.parse(await send("DOM.requestChildNodes", commands["DOM.requestChildNodes"].params.parse(params ?? {}))), "DOM.requestChildNodes", "command"), - requestNode: withCdpName(async (params?: unknown) => commands["DOM.requestNode"].result.parse(await send("DOM.requestNode", commands["DOM.requestNode"].params.parse(params ?? {}))), "DOM.requestNode", "command"), - resolveNode: withCdpName(async (params?: unknown) => commands["DOM.resolveNode"].result.parse(await send("DOM.resolveNode", commands["DOM.resolveNode"].params.parse(params ?? {}))), "DOM.resolveNode", "command"), - setAttributeValue: withCdpName(async (params?: unknown) => commands["DOM.setAttributeValue"].result.parse(await send("DOM.setAttributeValue", commands["DOM.setAttributeValue"].params.parse(params ?? {}))), "DOM.setAttributeValue", "command"), - setAttributesAsText: withCdpName(async (params?: unknown) => commands["DOM.setAttributesAsText"].result.parse(await send("DOM.setAttributesAsText", commands["DOM.setAttributesAsText"].params.parse(params ?? {}))), "DOM.setAttributesAsText", "command"), - setFileInputFiles: withCdpName(async (params?: unknown) => commands["DOM.setFileInputFiles"].result.parse(await send("DOM.setFileInputFiles", commands["DOM.setFileInputFiles"].params.parse(params ?? {}))), "DOM.setFileInputFiles", "command"), - setNodeStackTracesEnabled: withCdpName(async (params?: unknown) => commands["DOM.setNodeStackTracesEnabled"].result.parse(await send("DOM.setNodeStackTracesEnabled", commands["DOM.setNodeStackTracesEnabled"].params.parse(params ?? {}))), "DOM.setNodeStackTracesEnabled", "command"), - getNodeStackTraces: withCdpName(async (params?: unknown) => commands["DOM.getNodeStackTraces"].result.parse(await send("DOM.getNodeStackTraces", commands["DOM.getNodeStackTraces"].params.parse(params ?? {}))), "DOM.getNodeStackTraces", "command"), - getFileInfo: withCdpName(async (params?: unknown) => commands["DOM.getFileInfo"].result.parse(await send("DOM.getFileInfo", commands["DOM.getFileInfo"].params.parse(params ?? {}))), "DOM.getFileInfo", "command"), - getDetachedDomNodes: withCdpName(async (params?: unknown) => commands["DOM.getDetachedDomNodes"].result.parse(await send("DOM.getDetachedDomNodes", commands["DOM.getDetachedDomNodes"].params.parse(params ?? {}))), "DOM.getDetachedDomNodes", "command"), - setInspectedNode: withCdpName(async (params?: unknown) => commands["DOM.setInspectedNode"].result.parse(await send("DOM.setInspectedNode", commands["DOM.setInspectedNode"].params.parse(params ?? {}))), "DOM.setInspectedNode", "command"), - setNodeName: withCdpName(async (params?: unknown) => commands["DOM.setNodeName"].result.parse(await send("DOM.setNodeName", commands["DOM.setNodeName"].params.parse(params ?? {}))), "DOM.setNodeName", "command"), - setNodeValue: withCdpName(async (params?: unknown) => commands["DOM.setNodeValue"].result.parse(await send("DOM.setNodeValue", commands["DOM.setNodeValue"].params.parse(params ?? {}))), "DOM.setNodeValue", "command"), - setOuterHTML: withCdpName(async (params?: unknown) => commands["DOM.setOuterHTML"].result.parse(await send("DOM.setOuterHTML", commands["DOM.setOuterHTML"].params.parse(params ?? {}))), "DOM.setOuterHTML", "command"), - undo: withCdpName(async (params?: unknown) => commands["DOM.undo"].result.parse(await send("DOM.undo", commands["DOM.undo"].params.parse(params ?? {}))), "DOM.undo", "command"), - getFrameOwner: withCdpName(async (params?: unknown) => commands["DOM.getFrameOwner"].result.parse(await send("DOM.getFrameOwner", commands["DOM.getFrameOwner"].params.parse(params ?? {}))), "DOM.getFrameOwner", "command"), - getContainerForNode: withCdpName(async (params?: unknown) => commands["DOM.getContainerForNode"].result.parse(await send("DOM.getContainerForNode", commands["DOM.getContainerForNode"].params.parse(params ?? {}))), "DOM.getContainerForNode", "command"), - getQueryingDescendantsForContainer: withCdpName(async (params?: unknown) => commands["DOM.getQueryingDescendantsForContainer"].result.parse(await send("DOM.getQueryingDescendantsForContainer", commands["DOM.getQueryingDescendantsForContainer"].params.parse(params ?? {}))), "DOM.getQueryingDescendantsForContainer", "command"), - getAnchorElement: withCdpName(async (params?: unknown) => commands["DOM.getAnchorElement"].result.parse(await send("DOM.getAnchorElement", commands["DOM.getAnchorElement"].params.parse(params ?? {}))), "DOM.getAnchorElement", "command"), - forceShowPopover: withCdpName(async (params?: unknown) => commands["DOM.forceShowPopover"].result.parse(await send("DOM.forceShowPopover", commands["DOM.forceShowPopover"].params.parse(params ?? {}))), "DOM.forceShowPopover", "command"), - attributeModified: events["DOM.attributeModified"] as CdpEventAlias, - adoptedStyleSheetsModified: events["DOM.adoptedStyleSheetsModified"] as CdpEventAlias, - attributeRemoved: events["DOM.attributeRemoved"] as CdpEventAlias, - characterDataModified: events["DOM.characterDataModified"] as CdpEventAlias, - childNodeCountUpdated: events["DOM.childNodeCountUpdated"] as CdpEventAlias, - childNodeInserted: events["DOM.childNodeInserted"] as CdpEventAlias, - childNodeRemoved: events["DOM.childNodeRemoved"] as CdpEventAlias, - distributedNodesUpdated: events["DOM.distributedNodesUpdated"] as CdpEventAlias, - documentUpdated: events["DOM.documentUpdated"] as CdpEventAlias, - inlineStyleInvalidated: events["DOM.inlineStyleInvalidated"] as CdpEventAlias, - pseudoElementAdded: events["DOM.pseudoElementAdded"] as CdpEventAlias, - topLayerElementsUpdated: events["DOM.topLayerElementsUpdated"] as CdpEventAlias, - scrollableFlagUpdated: events["DOM.scrollableFlagUpdated"] as CdpEventAlias, - adRelatedStateUpdated: events["DOM.adRelatedStateUpdated"] as CdpEventAlias, - affectedByStartingStylesFlagUpdated: events["DOM.affectedByStartingStylesFlagUpdated"] as CdpEventAlias, - pseudoElementRemoved: events["DOM.pseudoElementRemoved"] as CdpEventAlias, - setChildNodes: events["DOM.setChildNodes"] as CdpEventAlias, - shadowRootPopped: events["DOM.shadowRootPopped"] as CdpEventAlias, - shadowRootPushed: events["DOM.shadowRootPushed"] as CdpEventAlias, + collectClassNamesFromSubtree: withCdpName( + async (params?: cdp.types.ts.DOM.CollectClassNamesFromSubtreeParams) => + commands["DOM.collectClassNamesFromSubtree"].result.parse( + await send( + "DOM.collectClassNamesFromSubtree", + commands["DOM.collectClassNamesFromSubtree"].params.parse(params ?? {}), + ), + ), + "DOM.collectClassNamesFromSubtree", + "command", + ), + copyTo: withCdpName( + async (params?: cdp.types.ts.DOM.CopyToParams) => + commands["DOM.copyTo"].result.parse( + await send("DOM.copyTo", commands["DOM.copyTo"].params.parse(params ?? {})), + ), + "DOM.copyTo", + "command", + ), + describeNode: withCdpName( + async (params?: cdp.types.ts.DOM.DescribeNodeParams) => + commands["DOM.describeNode"].result.parse( + await send("DOM.describeNode", commands["DOM.describeNode"].params.parse(params ?? {})), + ), + "DOM.describeNode", + "command", + ), + scrollIntoViewIfNeeded: withCdpName( + async (params?: cdp.types.ts.DOM.ScrollIntoViewIfNeededParams) => + commands["DOM.scrollIntoViewIfNeeded"].result.parse( + await send("DOM.scrollIntoViewIfNeeded", commands["DOM.scrollIntoViewIfNeeded"].params.parse(params ?? {})), + ), + "DOM.scrollIntoViewIfNeeded", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.DOM.DisableParams) => + commands["DOM.disable"].result.parse( + await send("DOM.disable", commands["DOM.disable"].params.parse(params ?? {})), + ), + "DOM.disable", + "command", + ), + discardSearchResults: withCdpName( + async (params?: cdp.types.ts.DOM.DiscardSearchResultsParams) => + commands["DOM.discardSearchResults"].result.parse( + await send("DOM.discardSearchResults", commands["DOM.discardSearchResults"].params.parse(params ?? {})), + ), + "DOM.discardSearchResults", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.DOM.EnableParams) => + commands["DOM.enable"].result.parse( + await send("DOM.enable", commands["DOM.enable"].params.parse(params ?? {})), + ), + "DOM.enable", + "command", + ), + focus: withCdpName( + async (params?: cdp.types.ts.DOM.FocusParams) => + commands["DOM.focus"].result.parse(await send("DOM.focus", commands["DOM.focus"].params.parse(params ?? {}))), + "DOM.focus", + "command", + ), + getAttributes: withCdpName( + async (params?: cdp.types.ts.DOM.GetAttributesParams) => + commands["DOM.getAttributes"].result.parse( + await send("DOM.getAttributes", commands["DOM.getAttributes"].params.parse(params ?? {})), + ), + "DOM.getAttributes", + "command", + ), + getBoxModel: withCdpName( + async (params?: cdp.types.ts.DOM.GetBoxModelParams) => + commands["DOM.getBoxModel"].result.parse( + await send("DOM.getBoxModel", commands["DOM.getBoxModel"].params.parse(params ?? {})), + ), + "DOM.getBoxModel", + "command", + ), + getContentQuads: withCdpName( + async (params?: cdp.types.ts.DOM.GetContentQuadsParams) => + commands["DOM.getContentQuads"].result.parse( + await send("DOM.getContentQuads", commands["DOM.getContentQuads"].params.parse(params ?? {})), + ), + "DOM.getContentQuads", + "command", + ), + getDocument: withCdpName( + async (params?: cdp.types.ts.DOM.GetDocumentParams) => + commands["DOM.getDocument"].result.parse( + await send("DOM.getDocument", commands["DOM.getDocument"].params.parse(params ?? {})), + ), + "DOM.getDocument", + "command", + ), + getFlattenedDocument: withCdpName( + async (params?: cdp.types.ts.DOM.GetFlattenedDocumentParams) => + commands["DOM.getFlattenedDocument"].result.parse( + await send("DOM.getFlattenedDocument", commands["DOM.getFlattenedDocument"].params.parse(params ?? {})), + ), + "DOM.getFlattenedDocument", + "command", + ), + getNodesForSubtreeByStyle: withCdpName( + async (params?: cdp.types.ts.DOM.GetNodesForSubtreeByStyleParams) => + commands["DOM.getNodesForSubtreeByStyle"].result.parse( + await send( + "DOM.getNodesForSubtreeByStyle", + commands["DOM.getNodesForSubtreeByStyle"].params.parse(params ?? {}), + ), + ), + "DOM.getNodesForSubtreeByStyle", + "command", + ), + getNodeForLocation: withCdpName( + async (params?: cdp.types.ts.DOM.GetNodeForLocationParams) => + commands["DOM.getNodeForLocation"].result.parse( + await send("DOM.getNodeForLocation", commands["DOM.getNodeForLocation"].params.parse(params ?? {})), + ), + "DOM.getNodeForLocation", + "command", + ), + getOuterHTML: withCdpName( + async (params?: cdp.types.ts.DOM.GetOuterHTMLParams) => + commands["DOM.getOuterHTML"].result.parse( + await send("DOM.getOuterHTML", commands["DOM.getOuterHTML"].params.parse(params ?? {})), + ), + "DOM.getOuterHTML", + "command", + ), + getRelayoutBoundary: withCdpName( + async (params?: cdp.types.ts.DOM.GetRelayoutBoundaryParams) => + commands["DOM.getRelayoutBoundary"].result.parse( + await send("DOM.getRelayoutBoundary", commands["DOM.getRelayoutBoundary"].params.parse(params ?? {})), + ), + "DOM.getRelayoutBoundary", + "command", + ), + getSearchResults: withCdpName( + async (params?: cdp.types.ts.DOM.GetSearchResultsParams) => + commands["DOM.getSearchResults"].result.parse( + await send("DOM.getSearchResults", commands["DOM.getSearchResults"].params.parse(params ?? {})), + ), + "DOM.getSearchResults", + "command", + ), + hideHighlight: withCdpName( + async (params?: cdp.types.ts.DOM.HideHighlightParams) => + commands["DOM.hideHighlight"].result.parse( + await send("DOM.hideHighlight", commands["DOM.hideHighlight"].params.parse(params ?? {})), + ), + "DOM.hideHighlight", + "command", + ), + highlightNode: withCdpName( + async (params?: cdp.types.ts.DOM.HighlightNodeParams) => + commands["DOM.highlightNode"].result.parse( + await send("DOM.highlightNode", commands["DOM.highlightNode"].params.parse(params ?? {})), + ), + "DOM.highlightNode", + "command", + ), + highlightRect: withCdpName( + async (params?: cdp.types.ts.DOM.HighlightRectParams) => + commands["DOM.highlightRect"].result.parse( + await send("DOM.highlightRect", commands["DOM.highlightRect"].params.parse(params ?? {})), + ), + "DOM.highlightRect", + "command", + ), + markUndoableState: withCdpName( + async (params?: cdp.types.ts.DOM.MarkUndoableStateParams) => + commands["DOM.markUndoableState"].result.parse( + await send("DOM.markUndoableState", commands["DOM.markUndoableState"].params.parse(params ?? {})), + ), + "DOM.markUndoableState", + "command", + ), + moveTo: withCdpName( + async (params?: cdp.types.ts.DOM.MoveToParams) => + commands["DOM.moveTo"].result.parse( + await send("DOM.moveTo", commands["DOM.moveTo"].params.parse(params ?? {})), + ), + "DOM.moveTo", + "command", + ), + performSearch: withCdpName( + async (params?: cdp.types.ts.DOM.PerformSearchParams) => + commands["DOM.performSearch"].result.parse( + await send("DOM.performSearch", commands["DOM.performSearch"].params.parse(params ?? {})), + ), + "DOM.performSearch", + "command", + ), + pushNodeByPathToFrontend: withCdpName( + async (params?: cdp.types.ts.DOM.PushNodeByPathToFrontendParams) => + commands["DOM.pushNodeByPathToFrontend"].result.parse( + await send( + "DOM.pushNodeByPathToFrontend", + commands["DOM.pushNodeByPathToFrontend"].params.parse(params ?? {}), + ), + ), + "DOM.pushNodeByPathToFrontend", + "command", + ), + pushNodesByBackendIdsToFrontend: withCdpName( + async (params?: cdp.types.ts.DOM.PushNodesByBackendIdsToFrontendParams) => + commands["DOM.pushNodesByBackendIdsToFrontend"].result.parse( + await send( + "DOM.pushNodesByBackendIdsToFrontend", + commands["DOM.pushNodesByBackendIdsToFrontend"].params.parse(params ?? {}), + ), + ), + "DOM.pushNodesByBackendIdsToFrontend", + "command", + ), + querySelector: withCdpName( + async (params?: cdp.types.ts.DOM.QuerySelectorParams) => + commands["DOM.querySelector"].result.parse( + await send("DOM.querySelector", commands["DOM.querySelector"].params.parse(params ?? {})), + ), + "DOM.querySelector", + "command", + ), + querySelectorAll: withCdpName( + async (params?: cdp.types.ts.DOM.QuerySelectorAllParams) => + commands["DOM.querySelectorAll"].result.parse( + await send("DOM.querySelectorAll", commands["DOM.querySelectorAll"].params.parse(params ?? {})), + ), + "DOM.querySelectorAll", + "command", + ), + getTopLayerElements: withCdpName( + async (params?: cdp.types.ts.DOM.GetTopLayerElementsParams) => + commands["DOM.getTopLayerElements"].result.parse( + await send("DOM.getTopLayerElements", commands["DOM.getTopLayerElements"].params.parse(params ?? {})), + ), + "DOM.getTopLayerElements", + "command", + ), + getElementByRelation: withCdpName( + async (params?: cdp.types.ts.DOM.GetElementByRelationParams) => + commands["DOM.getElementByRelation"].result.parse( + await send("DOM.getElementByRelation", commands["DOM.getElementByRelation"].params.parse(params ?? {})), + ), + "DOM.getElementByRelation", + "command", + ), + redo: withCdpName( + async (params?: cdp.types.ts.DOM.RedoParams) => + commands["DOM.redo"].result.parse(await send("DOM.redo", commands["DOM.redo"].params.parse(params ?? {}))), + "DOM.redo", + "command", + ), + removeAttribute: withCdpName( + async (params?: cdp.types.ts.DOM.RemoveAttributeParams) => + commands["DOM.removeAttribute"].result.parse( + await send("DOM.removeAttribute", commands["DOM.removeAttribute"].params.parse(params ?? {})), + ), + "DOM.removeAttribute", + "command", + ), + removeNode: withCdpName( + async (params?: cdp.types.ts.DOM.RemoveNodeParams) => + commands["DOM.removeNode"].result.parse( + await send("DOM.removeNode", commands["DOM.removeNode"].params.parse(params ?? {})), + ), + "DOM.removeNode", + "command", + ), + requestChildNodes: withCdpName( + async (params?: cdp.types.ts.DOM.RequestChildNodesParams) => + commands["DOM.requestChildNodes"].result.parse( + await send("DOM.requestChildNodes", commands["DOM.requestChildNodes"].params.parse(params ?? {})), + ), + "DOM.requestChildNodes", + "command", + ), + requestNode: withCdpName( + async (params?: cdp.types.ts.DOM.RequestNodeParams) => + commands["DOM.requestNode"].result.parse( + await send("DOM.requestNode", commands["DOM.requestNode"].params.parse(params ?? {})), + ), + "DOM.requestNode", + "command", + ), + resolveNode: withCdpName( + async (params?: cdp.types.ts.DOM.ResolveNodeParams) => + commands["DOM.resolveNode"].result.parse( + await send("DOM.resolveNode", commands["DOM.resolveNode"].params.parse(params ?? {})), + ), + "DOM.resolveNode", + "command", + ), + setAttributeValue: withCdpName( + async (params?: cdp.types.ts.DOM.SetAttributeValueParams) => + commands["DOM.setAttributeValue"].result.parse( + await send("DOM.setAttributeValue", commands["DOM.setAttributeValue"].params.parse(params ?? {})), + ), + "DOM.setAttributeValue", + "command", + ), + setAttributesAsText: withCdpName( + async (params?: cdp.types.ts.DOM.SetAttributesAsTextParams) => + commands["DOM.setAttributesAsText"].result.parse( + await send("DOM.setAttributesAsText", commands["DOM.setAttributesAsText"].params.parse(params ?? {})), + ), + "DOM.setAttributesAsText", + "command", + ), + setFileInputFiles: withCdpName( + async (params?: cdp.types.ts.DOM.SetFileInputFilesParams) => + commands["DOM.setFileInputFiles"].result.parse( + await send("DOM.setFileInputFiles", commands["DOM.setFileInputFiles"].params.parse(params ?? {})), + ), + "DOM.setFileInputFiles", + "command", + ), + setNodeStackTracesEnabled: withCdpName( + async (params?: cdp.types.ts.DOM.SetNodeStackTracesEnabledParams) => + commands["DOM.setNodeStackTracesEnabled"].result.parse( + await send( + "DOM.setNodeStackTracesEnabled", + commands["DOM.setNodeStackTracesEnabled"].params.parse(params ?? {}), + ), + ), + "DOM.setNodeStackTracesEnabled", + "command", + ), + getNodeStackTraces: withCdpName( + async (params?: cdp.types.ts.DOM.GetNodeStackTracesParams) => + commands["DOM.getNodeStackTraces"].result.parse( + await send("DOM.getNodeStackTraces", commands["DOM.getNodeStackTraces"].params.parse(params ?? {})), + ), + "DOM.getNodeStackTraces", + "command", + ), + getFileInfo: withCdpName( + async (params?: cdp.types.ts.DOM.GetFileInfoParams) => + commands["DOM.getFileInfo"].result.parse( + await send("DOM.getFileInfo", commands["DOM.getFileInfo"].params.parse(params ?? {})), + ), + "DOM.getFileInfo", + "command", + ), + getDetachedDomNodes: withCdpName( + async (params?: cdp.types.ts.DOM.GetDetachedDomNodesParams) => + commands["DOM.getDetachedDomNodes"].result.parse( + await send("DOM.getDetachedDomNodes", commands["DOM.getDetachedDomNodes"].params.parse(params ?? {})), + ), + "DOM.getDetachedDomNodes", + "command", + ), + setInspectedNode: withCdpName( + async (params?: cdp.types.ts.DOM.SetInspectedNodeParams) => + commands["DOM.setInspectedNode"].result.parse( + await send("DOM.setInspectedNode", commands["DOM.setInspectedNode"].params.parse(params ?? {})), + ), + "DOM.setInspectedNode", + "command", + ), + setNodeName: withCdpName( + async (params?: cdp.types.ts.DOM.SetNodeNameParams) => + commands["DOM.setNodeName"].result.parse( + await send("DOM.setNodeName", commands["DOM.setNodeName"].params.parse(params ?? {})), + ), + "DOM.setNodeName", + "command", + ), + setNodeValue: withCdpName( + async (params?: cdp.types.ts.DOM.SetNodeValueParams) => + commands["DOM.setNodeValue"].result.parse( + await send("DOM.setNodeValue", commands["DOM.setNodeValue"].params.parse(params ?? {})), + ), + "DOM.setNodeValue", + "command", + ), + setOuterHTML: withCdpName( + async (params?: cdp.types.ts.DOM.SetOuterHTMLParams) => + commands["DOM.setOuterHTML"].result.parse( + await send("DOM.setOuterHTML", commands["DOM.setOuterHTML"].params.parse(params ?? {})), + ), + "DOM.setOuterHTML", + "command", + ), + undo: withCdpName( + async (params?: cdp.types.ts.DOM.UndoParams) => + commands["DOM.undo"].result.parse(await send("DOM.undo", commands["DOM.undo"].params.parse(params ?? {}))), + "DOM.undo", + "command", + ), + getFrameOwner: withCdpName( + async (params?: cdp.types.ts.DOM.GetFrameOwnerParams) => + commands["DOM.getFrameOwner"].result.parse( + await send("DOM.getFrameOwner", commands["DOM.getFrameOwner"].params.parse(params ?? {})), + ), + "DOM.getFrameOwner", + "command", + ), + getContainerForNode: withCdpName( + async (params?: cdp.types.ts.DOM.GetContainerForNodeParams) => + commands["DOM.getContainerForNode"].result.parse( + await send("DOM.getContainerForNode", commands["DOM.getContainerForNode"].params.parse(params ?? {})), + ), + "DOM.getContainerForNode", + "command", + ), + getQueryingDescendantsForContainer: withCdpName( + async (params?: cdp.types.ts.DOM.GetQueryingDescendantsForContainerParams) => + commands["DOM.getQueryingDescendantsForContainer"].result.parse( + await send( + "DOM.getQueryingDescendantsForContainer", + commands["DOM.getQueryingDescendantsForContainer"].params.parse(params ?? {}), + ), + ), + "DOM.getQueryingDescendantsForContainer", + "command", + ), + getAnchorElement: withCdpName( + async (params?: cdp.types.ts.DOM.GetAnchorElementParams) => + commands["DOM.getAnchorElement"].result.parse( + await send("DOM.getAnchorElement", commands["DOM.getAnchorElement"].params.parse(params ?? {})), + ), + "DOM.getAnchorElement", + "command", + ), + forceShowPopover: withCdpName( + async (params?: cdp.types.ts.DOM.ForceShowPopoverParams) => + commands["DOM.forceShowPopover"].result.parse( + await send("DOM.forceShowPopover", commands["DOM.forceShowPopover"].params.parse(params ?? {})), + ), + "DOM.forceShowPopover", + "command", + ), + attributeModified: withCdpName(events["DOM.attributeModified"], "DOM.attributeModified", "event"), + adoptedStyleSheetsModified: withCdpName( + events["DOM.adoptedStyleSheetsModified"], + "DOM.adoptedStyleSheetsModified", + "event", + ), + attributeRemoved: withCdpName(events["DOM.attributeRemoved"], "DOM.attributeRemoved", "event"), + characterDataModified: withCdpName(events["DOM.characterDataModified"], "DOM.characterDataModified", "event"), + childNodeCountUpdated: withCdpName(events["DOM.childNodeCountUpdated"], "DOM.childNodeCountUpdated", "event"), + childNodeInserted: withCdpName(events["DOM.childNodeInserted"], "DOM.childNodeInserted", "event"), + childNodeRemoved: withCdpName(events["DOM.childNodeRemoved"], "DOM.childNodeRemoved", "event"), + distributedNodesUpdated: withCdpName( + events["DOM.distributedNodesUpdated"], + "DOM.distributedNodesUpdated", + "event", + ), + documentUpdated: withCdpName(events["DOM.documentUpdated"], "DOM.documentUpdated", "event"), + inlineStyleInvalidated: withCdpName(events["DOM.inlineStyleInvalidated"], "DOM.inlineStyleInvalidated", "event"), + pseudoElementAdded: withCdpName(events["DOM.pseudoElementAdded"], "DOM.pseudoElementAdded", "event"), + topLayerElementsUpdated: withCdpName( + events["DOM.topLayerElementsUpdated"], + "DOM.topLayerElementsUpdated", + "event", + ), + scrollableFlagUpdated: withCdpName(events["DOM.scrollableFlagUpdated"], "DOM.scrollableFlagUpdated", "event"), + adRelatedStateUpdated: withCdpName(events["DOM.adRelatedStateUpdated"], "DOM.adRelatedStateUpdated", "event"), + affectedByStartingStylesFlagUpdated: withCdpName( + events["DOM.affectedByStartingStylesFlagUpdated"], + "DOM.affectedByStartingStylesFlagUpdated", + "event", + ), + pseudoElementRemoved: withCdpName(events["DOM.pseudoElementRemoved"], "DOM.pseudoElementRemoved", "event"), + setChildNodes: withCdpName(events["DOM.setChildNodes"], "DOM.setChildNodes", "event"), + shadowRootPopped: withCdpName(events["DOM.shadowRootPopped"], "DOM.shadowRootPopped", "event"), + shadowRootPushed: withCdpName(events["DOM.shadowRootPushed"], "DOM.shadowRootPushed", "event"), }, DOMDebugger: { - getEventListeners: withCdpName(async (params?: unknown) => commands["DOMDebugger.getEventListeners"].result.parse(await send("DOMDebugger.getEventListeners", commands["DOMDebugger.getEventListeners"].params.parse(params ?? {}))), "DOMDebugger.getEventListeners", "command"), - removeDOMBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.removeDOMBreakpoint"].result.parse(await send("DOMDebugger.removeDOMBreakpoint", commands["DOMDebugger.removeDOMBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.removeDOMBreakpoint", "command"), - removeEventListenerBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.removeEventListenerBreakpoint"].result.parse(await send("DOMDebugger.removeEventListenerBreakpoint", commands["DOMDebugger.removeEventListenerBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.removeEventListenerBreakpoint", "command"), - removeInstrumentationBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.removeInstrumentationBreakpoint"].result.parse(await send("DOMDebugger.removeInstrumentationBreakpoint", commands["DOMDebugger.removeInstrumentationBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.removeInstrumentationBreakpoint", "command"), - removeXHRBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.removeXHRBreakpoint"].result.parse(await send("DOMDebugger.removeXHRBreakpoint", commands["DOMDebugger.removeXHRBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.removeXHRBreakpoint", "command"), - setBreakOnCSPViolation: withCdpName(async (params?: unknown) => commands["DOMDebugger.setBreakOnCSPViolation"].result.parse(await send("DOMDebugger.setBreakOnCSPViolation", commands["DOMDebugger.setBreakOnCSPViolation"].params.parse(params ?? {}))), "DOMDebugger.setBreakOnCSPViolation", "command"), - setDOMBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.setDOMBreakpoint"].result.parse(await send("DOMDebugger.setDOMBreakpoint", commands["DOMDebugger.setDOMBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.setDOMBreakpoint", "command"), - setEventListenerBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.setEventListenerBreakpoint"].result.parse(await send("DOMDebugger.setEventListenerBreakpoint", commands["DOMDebugger.setEventListenerBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.setEventListenerBreakpoint", "command"), - setInstrumentationBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.setInstrumentationBreakpoint"].result.parse(await send("DOMDebugger.setInstrumentationBreakpoint", commands["DOMDebugger.setInstrumentationBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.setInstrumentationBreakpoint", "command"), - setXHRBreakpoint: withCdpName(async (params?: unknown) => commands["DOMDebugger.setXHRBreakpoint"].result.parse(await send("DOMDebugger.setXHRBreakpoint", commands["DOMDebugger.setXHRBreakpoint"].params.parse(params ?? {}))), "DOMDebugger.setXHRBreakpoint", "command"), + getEventListeners: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.GetEventListenersParams) => + commands["DOMDebugger.getEventListeners"].result.parse( + await send( + "DOMDebugger.getEventListeners", + commands["DOMDebugger.getEventListeners"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.getEventListeners", + "command", + ), + removeDOMBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.RemoveDOMBreakpointParams) => + commands["DOMDebugger.removeDOMBreakpoint"].result.parse( + await send( + "DOMDebugger.removeDOMBreakpoint", + commands["DOMDebugger.removeDOMBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.removeDOMBreakpoint", + "command", + ), + removeEventListenerBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.RemoveEventListenerBreakpointParams) => + commands["DOMDebugger.removeEventListenerBreakpoint"].result.parse( + await send( + "DOMDebugger.removeEventListenerBreakpoint", + commands["DOMDebugger.removeEventListenerBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.removeEventListenerBreakpoint", + "command", + ), + removeInstrumentationBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.RemoveInstrumentationBreakpointParams) => + commands["DOMDebugger.removeInstrumentationBreakpoint"].result.parse( + await send( + "DOMDebugger.removeInstrumentationBreakpoint", + commands["DOMDebugger.removeInstrumentationBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.removeInstrumentationBreakpoint", + "command", + ), + removeXHRBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.RemoveXHRBreakpointParams) => + commands["DOMDebugger.removeXHRBreakpoint"].result.parse( + await send( + "DOMDebugger.removeXHRBreakpoint", + commands["DOMDebugger.removeXHRBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.removeXHRBreakpoint", + "command", + ), + setBreakOnCSPViolation: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.SetBreakOnCSPViolationParams) => + commands["DOMDebugger.setBreakOnCSPViolation"].result.parse( + await send( + "DOMDebugger.setBreakOnCSPViolation", + commands["DOMDebugger.setBreakOnCSPViolation"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.setBreakOnCSPViolation", + "command", + ), + setDOMBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.SetDOMBreakpointParams) => + commands["DOMDebugger.setDOMBreakpoint"].result.parse( + await send( + "DOMDebugger.setDOMBreakpoint", + commands["DOMDebugger.setDOMBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.setDOMBreakpoint", + "command", + ), + setEventListenerBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.SetEventListenerBreakpointParams) => + commands["DOMDebugger.setEventListenerBreakpoint"].result.parse( + await send( + "DOMDebugger.setEventListenerBreakpoint", + commands["DOMDebugger.setEventListenerBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.setEventListenerBreakpoint", + "command", + ), + setInstrumentationBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.SetInstrumentationBreakpointParams) => + commands["DOMDebugger.setInstrumentationBreakpoint"].result.parse( + await send( + "DOMDebugger.setInstrumentationBreakpoint", + commands["DOMDebugger.setInstrumentationBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.setInstrumentationBreakpoint", + "command", + ), + setXHRBreakpoint: withCdpName( + async (params?: cdp.types.ts.DOMDebugger.SetXHRBreakpointParams) => + commands["DOMDebugger.setXHRBreakpoint"].result.parse( + await send( + "DOMDebugger.setXHRBreakpoint", + commands["DOMDebugger.setXHRBreakpoint"].params.parse(params ?? {}), + ), + ), + "DOMDebugger.setXHRBreakpoint", + "command", + ), }, DOMSnapshot: { - disable: withCdpName(async (params?: unknown) => commands["DOMSnapshot.disable"].result.parse(await send("DOMSnapshot.disable", commands["DOMSnapshot.disable"].params.parse(params ?? {}))), "DOMSnapshot.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["DOMSnapshot.enable"].result.parse(await send("DOMSnapshot.enable", commands["DOMSnapshot.enable"].params.parse(params ?? {}))), "DOMSnapshot.enable", "command"), - getSnapshot: withCdpName(async (params?: unknown) => commands["DOMSnapshot.getSnapshot"].result.parse(await send("DOMSnapshot.getSnapshot", commands["DOMSnapshot.getSnapshot"].params.parse(params ?? {}))), "DOMSnapshot.getSnapshot", "command"), - captureSnapshot: withCdpName(async (params?: unknown) => commands["DOMSnapshot.captureSnapshot"].result.parse(await send("DOMSnapshot.captureSnapshot", commands["DOMSnapshot.captureSnapshot"].params.parse(params ?? {}))), "DOMSnapshot.captureSnapshot", "command"), + disable: withCdpName( + async (params?: cdp.types.ts.DOMSnapshot.DisableParams) => + commands["DOMSnapshot.disable"].result.parse( + await send("DOMSnapshot.disable", commands["DOMSnapshot.disable"].params.parse(params ?? {})), + ), + "DOMSnapshot.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.DOMSnapshot.EnableParams) => + commands["DOMSnapshot.enable"].result.parse( + await send("DOMSnapshot.enable", commands["DOMSnapshot.enable"].params.parse(params ?? {})), + ), + "DOMSnapshot.enable", + "command", + ), + getSnapshot: withCdpName( + async (params?: cdp.types.ts.DOMSnapshot.GetSnapshotParams) => + commands["DOMSnapshot.getSnapshot"].result.parse( + await send("DOMSnapshot.getSnapshot", commands["DOMSnapshot.getSnapshot"].params.parse(params ?? {})), + ), + "DOMSnapshot.getSnapshot", + "command", + ), + captureSnapshot: withCdpName( + async (params?: cdp.types.ts.DOMSnapshot.CaptureSnapshotParams) => + commands["DOMSnapshot.captureSnapshot"].result.parse( + await send( + "DOMSnapshot.captureSnapshot", + commands["DOMSnapshot.captureSnapshot"].params.parse(params ?? {}), + ), + ), + "DOMSnapshot.captureSnapshot", + "command", + ), }, DOMStorage: { - clear: withCdpName(async (params?: unknown) => commands["DOMStorage.clear"].result.parse(await send("DOMStorage.clear", commands["DOMStorage.clear"].params.parse(params ?? {}))), "DOMStorage.clear", "command"), - disable: withCdpName(async (params?: unknown) => commands["DOMStorage.disable"].result.parse(await send("DOMStorage.disable", commands["DOMStorage.disable"].params.parse(params ?? {}))), "DOMStorage.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["DOMStorage.enable"].result.parse(await send("DOMStorage.enable", commands["DOMStorage.enable"].params.parse(params ?? {}))), "DOMStorage.enable", "command"), - getDOMStorageItems: withCdpName(async (params?: unknown) => commands["DOMStorage.getDOMStorageItems"].result.parse(await send("DOMStorage.getDOMStorageItems", commands["DOMStorage.getDOMStorageItems"].params.parse(params ?? {}))), "DOMStorage.getDOMStorageItems", "command"), - removeDOMStorageItem: withCdpName(async (params?: unknown) => commands["DOMStorage.removeDOMStorageItem"].result.parse(await send("DOMStorage.removeDOMStorageItem", commands["DOMStorage.removeDOMStorageItem"].params.parse(params ?? {}))), "DOMStorage.removeDOMStorageItem", "command"), - setDOMStorageItem: withCdpName(async (params?: unknown) => commands["DOMStorage.setDOMStorageItem"].result.parse(await send("DOMStorage.setDOMStorageItem", commands["DOMStorage.setDOMStorageItem"].params.parse(params ?? {}))), "DOMStorage.setDOMStorageItem", "command"), - domStorageItemAdded: events["DOMStorage.domStorageItemAdded"] as CdpEventAlias, - domStorageItemRemoved: events["DOMStorage.domStorageItemRemoved"] as CdpEventAlias, - domStorageItemUpdated: events["DOMStorage.domStorageItemUpdated"] as CdpEventAlias, - domStorageItemsCleared: events["DOMStorage.domStorageItemsCleared"] as CdpEventAlias, + clear: withCdpName( + async (params?: cdp.types.ts.DOMStorage.ClearParams) => + commands["DOMStorage.clear"].result.parse( + await send("DOMStorage.clear", commands["DOMStorage.clear"].params.parse(params ?? {})), + ), + "DOMStorage.clear", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.DOMStorage.DisableParams) => + commands["DOMStorage.disable"].result.parse( + await send("DOMStorage.disable", commands["DOMStorage.disable"].params.parse(params ?? {})), + ), + "DOMStorage.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.DOMStorage.EnableParams) => + commands["DOMStorage.enable"].result.parse( + await send("DOMStorage.enable", commands["DOMStorage.enable"].params.parse(params ?? {})), + ), + "DOMStorage.enable", + "command", + ), + getDOMStorageItems: withCdpName( + async (params?: cdp.types.ts.DOMStorage.GetDOMStorageItemsParams) => + commands["DOMStorage.getDOMStorageItems"].result.parse( + await send( + "DOMStorage.getDOMStorageItems", + commands["DOMStorage.getDOMStorageItems"].params.parse(params ?? {}), + ), + ), + "DOMStorage.getDOMStorageItems", + "command", + ), + removeDOMStorageItem: withCdpName( + async (params?: cdp.types.ts.DOMStorage.RemoveDOMStorageItemParams) => + commands["DOMStorage.removeDOMStorageItem"].result.parse( + await send( + "DOMStorage.removeDOMStorageItem", + commands["DOMStorage.removeDOMStorageItem"].params.parse(params ?? {}), + ), + ), + "DOMStorage.removeDOMStorageItem", + "command", + ), + setDOMStorageItem: withCdpName( + async (params?: cdp.types.ts.DOMStorage.SetDOMStorageItemParams) => + commands["DOMStorage.setDOMStorageItem"].result.parse( + await send( + "DOMStorage.setDOMStorageItem", + commands["DOMStorage.setDOMStorageItem"].params.parse(params ?? {}), + ), + ), + "DOMStorage.setDOMStorageItem", + "command", + ), + domStorageItemAdded: withCdpName( + events["DOMStorage.domStorageItemAdded"], + "DOMStorage.domStorageItemAdded", + "event", + ), + domStorageItemRemoved: withCdpName( + events["DOMStorage.domStorageItemRemoved"], + "DOMStorage.domStorageItemRemoved", + "event", + ), + domStorageItemUpdated: withCdpName( + events["DOMStorage.domStorageItemUpdated"], + "DOMStorage.domStorageItemUpdated", + "event", + ), + domStorageItemsCleared: withCdpName( + events["DOMStorage.domStorageItemsCleared"], + "DOMStorage.domStorageItemsCleared", + "event", + ), }, Emulation: { - canEmulate: withCdpName(async (params?: unknown) => commands["Emulation.canEmulate"].result.parse(await send("Emulation.canEmulate", commands["Emulation.canEmulate"].params.parse(params ?? {}))), "Emulation.canEmulate", "command"), - clearDeviceMetricsOverride: withCdpName(async (params?: unknown) => commands["Emulation.clearDeviceMetricsOverride"].result.parse(await send("Emulation.clearDeviceMetricsOverride", commands["Emulation.clearDeviceMetricsOverride"].params.parse(params ?? {}))), "Emulation.clearDeviceMetricsOverride", "command"), - clearGeolocationOverride: withCdpName(async (params?: unknown) => commands["Emulation.clearGeolocationOverride"].result.parse(await send("Emulation.clearGeolocationOverride", commands["Emulation.clearGeolocationOverride"].params.parse(params ?? {}))), "Emulation.clearGeolocationOverride", "command"), - resetPageScaleFactor: withCdpName(async (params?: unknown) => commands["Emulation.resetPageScaleFactor"].result.parse(await send("Emulation.resetPageScaleFactor", commands["Emulation.resetPageScaleFactor"].params.parse(params ?? {}))), "Emulation.resetPageScaleFactor", "command"), - setFocusEmulationEnabled: withCdpName(async (params?: unknown) => commands["Emulation.setFocusEmulationEnabled"].result.parse(await send("Emulation.setFocusEmulationEnabled", commands["Emulation.setFocusEmulationEnabled"].params.parse(params ?? {}))), "Emulation.setFocusEmulationEnabled", "command"), - setAutoDarkModeOverride: withCdpName(async (params?: unknown) => commands["Emulation.setAutoDarkModeOverride"].result.parse(await send("Emulation.setAutoDarkModeOverride", commands["Emulation.setAutoDarkModeOverride"].params.parse(params ?? {}))), "Emulation.setAutoDarkModeOverride", "command"), - setCPUThrottlingRate: withCdpName(async (params?: unknown) => commands["Emulation.setCPUThrottlingRate"].result.parse(await send("Emulation.setCPUThrottlingRate", commands["Emulation.setCPUThrottlingRate"].params.parse(params ?? {}))), "Emulation.setCPUThrottlingRate", "command"), - setDefaultBackgroundColorOverride: withCdpName(async (params?: unknown) => commands["Emulation.setDefaultBackgroundColorOverride"].result.parse(await send("Emulation.setDefaultBackgroundColorOverride", commands["Emulation.setDefaultBackgroundColorOverride"].params.parse(params ?? {}))), "Emulation.setDefaultBackgroundColorOverride", "command"), - setSafeAreaInsetsOverride: withCdpName(async (params?: unknown) => commands["Emulation.setSafeAreaInsetsOverride"].result.parse(await send("Emulation.setSafeAreaInsetsOverride", commands["Emulation.setSafeAreaInsetsOverride"].params.parse(params ?? {}))), "Emulation.setSafeAreaInsetsOverride", "command"), - setDeviceMetricsOverride: withCdpName(async (params?: unknown) => commands["Emulation.setDeviceMetricsOverride"].result.parse(await send("Emulation.setDeviceMetricsOverride", commands["Emulation.setDeviceMetricsOverride"].params.parse(params ?? {}))), "Emulation.setDeviceMetricsOverride", "command"), - setDevicePostureOverride: withCdpName(async (params?: unknown) => commands["Emulation.setDevicePostureOverride"].result.parse(await send("Emulation.setDevicePostureOverride", commands["Emulation.setDevicePostureOverride"].params.parse(params ?? {}))), "Emulation.setDevicePostureOverride", "command"), - clearDevicePostureOverride: withCdpName(async (params?: unknown) => commands["Emulation.clearDevicePostureOverride"].result.parse(await send("Emulation.clearDevicePostureOverride", commands["Emulation.clearDevicePostureOverride"].params.parse(params ?? {}))), "Emulation.clearDevicePostureOverride", "command"), - setDisplayFeaturesOverride: withCdpName(async (params?: unknown) => commands["Emulation.setDisplayFeaturesOverride"].result.parse(await send("Emulation.setDisplayFeaturesOverride", commands["Emulation.setDisplayFeaturesOverride"].params.parse(params ?? {}))), "Emulation.setDisplayFeaturesOverride", "command"), - clearDisplayFeaturesOverride: withCdpName(async (params?: unknown) => commands["Emulation.clearDisplayFeaturesOverride"].result.parse(await send("Emulation.clearDisplayFeaturesOverride", commands["Emulation.clearDisplayFeaturesOverride"].params.parse(params ?? {}))), "Emulation.clearDisplayFeaturesOverride", "command"), - setScrollbarsHidden: withCdpName(async (params?: unknown) => commands["Emulation.setScrollbarsHidden"].result.parse(await send("Emulation.setScrollbarsHidden", commands["Emulation.setScrollbarsHidden"].params.parse(params ?? {}))), "Emulation.setScrollbarsHidden", "command"), - setDocumentCookieDisabled: withCdpName(async (params?: unknown) => commands["Emulation.setDocumentCookieDisabled"].result.parse(await send("Emulation.setDocumentCookieDisabled", commands["Emulation.setDocumentCookieDisabled"].params.parse(params ?? {}))), "Emulation.setDocumentCookieDisabled", "command"), - setEmitTouchEventsForMouse: withCdpName(async (params?: unknown) => commands["Emulation.setEmitTouchEventsForMouse"].result.parse(await send("Emulation.setEmitTouchEventsForMouse", commands["Emulation.setEmitTouchEventsForMouse"].params.parse(params ?? {}))), "Emulation.setEmitTouchEventsForMouse", "command"), - setEmulatedMedia: withCdpName(async (params?: unknown) => commands["Emulation.setEmulatedMedia"].result.parse(await send("Emulation.setEmulatedMedia", commands["Emulation.setEmulatedMedia"].params.parse(params ?? {}))), "Emulation.setEmulatedMedia", "command"), - setEmulatedVisionDeficiency: withCdpName(async (params?: unknown) => commands["Emulation.setEmulatedVisionDeficiency"].result.parse(await send("Emulation.setEmulatedVisionDeficiency", commands["Emulation.setEmulatedVisionDeficiency"].params.parse(params ?? {}))), "Emulation.setEmulatedVisionDeficiency", "command"), - setEmulatedOSTextScale: withCdpName(async (params?: unknown) => commands["Emulation.setEmulatedOSTextScale"].result.parse(await send("Emulation.setEmulatedOSTextScale", commands["Emulation.setEmulatedOSTextScale"].params.parse(params ?? {}))), "Emulation.setEmulatedOSTextScale", "command"), - setGeolocationOverride: withCdpName(async (params?: unknown) => commands["Emulation.setGeolocationOverride"].result.parse(await send("Emulation.setGeolocationOverride", commands["Emulation.setGeolocationOverride"].params.parse(params ?? {}))), "Emulation.setGeolocationOverride", "command"), - getOverriddenSensorInformation: withCdpName(async (params?: unknown) => commands["Emulation.getOverriddenSensorInformation"].result.parse(await send("Emulation.getOverriddenSensorInformation", commands["Emulation.getOverriddenSensorInformation"].params.parse(params ?? {}))), "Emulation.getOverriddenSensorInformation", "command"), - setSensorOverrideEnabled: withCdpName(async (params?: unknown) => commands["Emulation.setSensorOverrideEnabled"].result.parse(await send("Emulation.setSensorOverrideEnabled", commands["Emulation.setSensorOverrideEnabled"].params.parse(params ?? {}))), "Emulation.setSensorOverrideEnabled", "command"), - setSensorOverrideReadings: withCdpName(async (params?: unknown) => commands["Emulation.setSensorOverrideReadings"].result.parse(await send("Emulation.setSensorOverrideReadings", commands["Emulation.setSensorOverrideReadings"].params.parse(params ?? {}))), "Emulation.setSensorOverrideReadings", "command"), - setPressureSourceOverrideEnabled: withCdpName(async (params?: unknown) => commands["Emulation.setPressureSourceOverrideEnabled"].result.parse(await send("Emulation.setPressureSourceOverrideEnabled", commands["Emulation.setPressureSourceOverrideEnabled"].params.parse(params ?? {}))), "Emulation.setPressureSourceOverrideEnabled", "command"), - setPressureStateOverride: withCdpName(async (params?: unknown) => commands["Emulation.setPressureStateOverride"].result.parse(await send("Emulation.setPressureStateOverride", commands["Emulation.setPressureStateOverride"].params.parse(params ?? {}))), "Emulation.setPressureStateOverride", "command"), - setPressureDataOverride: withCdpName(async (params?: unknown) => commands["Emulation.setPressureDataOverride"].result.parse(await send("Emulation.setPressureDataOverride", commands["Emulation.setPressureDataOverride"].params.parse(params ?? {}))), "Emulation.setPressureDataOverride", "command"), - setIdleOverride: withCdpName(async (params?: unknown) => commands["Emulation.setIdleOverride"].result.parse(await send("Emulation.setIdleOverride", commands["Emulation.setIdleOverride"].params.parse(params ?? {}))), "Emulation.setIdleOverride", "command"), - clearIdleOverride: withCdpName(async (params?: unknown) => commands["Emulation.clearIdleOverride"].result.parse(await send("Emulation.clearIdleOverride", commands["Emulation.clearIdleOverride"].params.parse(params ?? {}))), "Emulation.clearIdleOverride", "command"), - setNavigatorOverrides: withCdpName(async (params?: unknown) => commands["Emulation.setNavigatorOverrides"].result.parse(await send("Emulation.setNavigatorOverrides", commands["Emulation.setNavigatorOverrides"].params.parse(params ?? {}))), "Emulation.setNavigatorOverrides", "command"), - setPageScaleFactor: withCdpName(async (params?: unknown) => commands["Emulation.setPageScaleFactor"].result.parse(await send("Emulation.setPageScaleFactor", commands["Emulation.setPageScaleFactor"].params.parse(params ?? {}))), "Emulation.setPageScaleFactor", "command"), - setScriptExecutionDisabled: withCdpName(async (params?: unknown) => commands["Emulation.setScriptExecutionDisabled"].result.parse(await send("Emulation.setScriptExecutionDisabled", commands["Emulation.setScriptExecutionDisabled"].params.parse(params ?? {}))), "Emulation.setScriptExecutionDisabled", "command"), - setTouchEmulationEnabled: withCdpName(async (params?: unknown) => commands["Emulation.setTouchEmulationEnabled"].result.parse(await send("Emulation.setTouchEmulationEnabled", commands["Emulation.setTouchEmulationEnabled"].params.parse(params ?? {}))), "Emulation.setTouchEmulationEnabled", "command"), - setVirtualTimePolicy: withCdpName(async (params?: unknown) => commands["Emulation.setVirtualTimePolicy"].result.parse(await send("Emulation.setVirtualTimePolicy", commands["Emulation.setVirtualTimePolicy"].params.parse(params ?? {}))), "Emulation.setVirtualTimePolicy", "command"), - setLocaleOverride: withCdpName(async (params?: unknown) => commands["Emulation.setLocaleOverride"].result.parse(await send("Emulation.setLocaleOverride", commands["Emulation.setLocaleOverride"].params.parse(params ?? {}))), "Emulation.setLocaleOverride", "command"), - setTimezoneOverride: withCdpName(async (params?: unknown) => commands["Emulation.setTimezoneOverride"].result.parse(await send("Emulation.setTimezoneOverride", commands["Emulation.setTimezoneOverride"].params.parse(params ?? {}))), "Emulation.setTimezoneOverride", "command"), - setVisibleSize: withCdpName(async (params?: unknown) => commands["Emulation.setVisibleSize"].result.parse(await send("Emulation.setVisibleSize", commands["Emulation.setVisibleSize"].params.parse(params ?? {}))), "Emulation.setVisibleSize", "command"), - setDisabledImageTypes: withCdpName(async (params?: unknown) => commands["Emulation.setDisabledImageTypes"].result.parse(await send("Emulation.setDisabledImageTypes", commands["Emulation.setDisabledImageTypes"].params.parse(params ?? {}))), "Emulation.setDisabledImageTypes", "command"), - setDataSaverOverride: withCdpName(async (params?: unknown) => commands["Emulation.setDataSaverOverride"].result.parse(await send("Emulation.setDataSaverOverride", commands["Emulation.setDataSaverOverride"].params.parse(params ?? {}))), "Emulation.setDataSaverOverride", "command"), - setHardwareConcurrencyOverride: withCdpName(async (params?: unknown) => commands["Emulation.setHardwareConcurrencyOverride"].result.parse(await send("Emulation.setHardwareConcurrencyOverride", commands["Emulation.setHardwareConcurrencyOverride"].params.parse(params ?? {}))), "Emulation.setHardwareConcurrencyOverride", "command"), - setUserAgentOverride: withCdpName(async (params?: unknown) => commands["Emulation.setUserAgentOverride"].result.parse(await send("Emulation.setUserAgentOverride", commands["Emulation.setUserAgentOverride"].params.parse(params ?? {}))), "Emulation.setUserAgentOverride", "command"), - setAutomationOverride: withCdpName(async (params?: unknown) => commands["Emulation.setAutomationOverride"].result.parse(await send("Emulation.setAutomationOverride", commands["Emulation.setAutomationOverride"].params.parse(params ?? {}))), "Emulation.setAutomationOverride", "command"), - setSmallViewportHeightDifferenceOverride: withCdpName(async (params?: unknown) => commands["Emulation.setSmallViewportHeightDifferenceOverride"].result.parse(await send("Emulation.setSmallViewportHeightDifferenceOverride", commands["Emulation.setSmallViewportHeightDifferenceOverride"].params.parse(params ?? {}))), "Emulation.setSmallViewportHeightDifferenceOverride", "command"), - getScreenInfos: withCdpName(async (params?: unknown) => commands["Emulation.getScreenInfos"].result.parse(await send("Emulation.getScreenInfos", commands["Emulation.getScreenInfos"].params.parse(params ?? {}))), "Emulation.getScreenInfos", "command"), - addScreen: withCdpName(async (params?: unknown) => commands["Emulation.addScreen"].result.parse(await send("Emulation.addScreen", commands["Emulation.addScreen"].params.parse(params ?? {}))), "Emulation.addScreen", "command"), - updateScreen: withCdpName(async (params?: unknown) => commands["Emulation.updateScreen"].result.parse(await send("Emulation.updateScreen", commands["Emulation.updateScreen"].params.parse(params ?? {}))), "Emulation.updateScreen", "command"), - removeScreen: withCdpName(async (params?: unknown) => commands["Emulation.removeScreen"].result.parse(await send("Emulation.removeScreen", commands["Emulation.removeScreen"].params.parse(params ?? {}))), "Emulation.removeScreen", "command"), - setPrimaryScreen: withCdpName(async (params?: unknown) => commands["Emulation.setPrimaryScreen"].result.parse(await send("Emulation.setPrimaryScreen", commands["Emulation.setPrimaryScreen"].params.parse(params ?? {}))), "Emulation.setPrimaryScreen", "command"), - virtualTimeBudgetExpired: events["Emulation.virtualTimeBudgetExpired"] as CdpEventAlias, - screenOrientationLockChanged: events["Emulation.screenOrientationLockChanged"] as CdpEventAlias, + canEmulate: withCdpName( + async (params?: cdp.types.ts.Emulation.CanEmulateParams) => + commands["Emulation.canEmulate"].result.parse( + await send("Emulation.canEmulate", commands["Emulation.canEmulate"].params.parse(params ?? {})), + ), + "Emulation.canEmulate", + "command", + ), + clearDeviceMetricsOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.ClearDeviceMetricsOverrideParams) => + commands["Emulation.clearDeviceMetricsOverride"].result.parse( + await send( + "Emulation.clearDeviceMetricsOverride", + commands["Emulation.clearDeviceMetricsOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.clearDeviceMetricsOverride", + "command", + ), + clearGeolocationOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.ClearGeolocationOverrideParams) => + commands["Emulation.clearGeolocationOverride"].result.parse( + await send( + "Emulation.clearGeolocationOverride", + commands["Emulation.clearGeolocationOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.clearGeolocationOverride", + "command", + ), + resetPageScaleFactor: withCdpName( + async (params?: cdp.types.ts.Emulation.ResetPageScaleFactorParams) => + commands["Emulation.resetPageScaleFactor"].result.parse( + await send( + "Emulation.resetPageScaleFactor", + commands["Emulation.resetPageScaleFactor"].params.parse(params ?? {}), + ), + ), + "Emulation.resetPageScaleFactor", + "command", + ), + setFocusEmulationEnabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetFocusEmulationEnabledParams) => + commands["Emulation.setFocusEmulationEnabled"].result.parse( + await send( + "Emulation.setFocusEmulationEnabled", + commands["Emulation.setFocusEmulationEnabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setFocusEmulationEnabled", + "command", + ), + setAutoDarkModeOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetAutoDarkModeOverrideParams) => + commands["Emulation.setAutoDarkModeOverride"].result.parse( + await send( + "Emulation.setAutoDarkModeOverride", + commands["Emulation.setAutoDarkModeOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setAutoDarkModeOverride", + "command", + ), + setCPUThrottlingRate: withCdpName( + async (params?: cdp.types.ts.Emulation.SetCPUThrottlingRateParams) => + commands["Emulation.setCPUThrottlingRate"].result.parse( + await send( + "Emulation.setCPUThrottlingRate", + commands["Emulation.setCPUThrottlingRate"].params.parse(params ?? {}), + ), + ), + "Emulation.setCPUThrottlingRate", + "command", + ), + setDefaultBackgroundColorOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDefaultBackgroundColorOverrideParams) => + commands["Emulation.setDefaultBackgroundColorOverride"].result.parse( + await send( + "Emulation.setDefaultBackgroundColorOverride", + commands["Emulation.setDefaultBackgroundColorOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setDefaultBackgroundColorOverride", + "command", + ), + setSafeAreaInsetsOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetSafeAreaInsetsOverrideParams) => + commands["Emulation.setSafeAreaInsetsOverride"].result.parse( + await send( + "Emulation.setSafeAreaInsetsOverride", + commands["Emulation.setSafeAreaInsetsOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setSafeAreaInsetsOverride", + "command", + ), + setDeviceMetricsOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDeviceMetricsOverrideParams) => + commands["Emulation.setDeviceMetricsOverride"].result.parse( + await send( + "Emulation.setDeviceMetricsOverride", + commands["Emulation.setDeviceMetricsOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setDeviceMetricsOverride", + "command", + ), + setDevicePostureOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDevicePostureOverrideParams) => + commands["Emulation.setDevicePostureOverride"].result.parse( + await send( + "Emulation.setDevicePostureOverride", + commands["Emulation.setDevicePostureOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setDevicePostureOverride", + "command", + ), + clearDevicePostureOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.ClearDevicePostureOverrideParams) => + commands["Emulation.clearDevicePostureOverride"].result.parse( + await send( + "Emulation.clearDevicePostureOverride", + commands["Emulation.clearDevicePostureOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.clearDevicePostureOverride", + "command", + ), + setDisplayFeaturesOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDisplayFeaturesOverrideParams) => + commands["Emulation.setDisplayFeaturesOverride"].result.parse( + await send( + "Emulation.setDisplayFeaturesOverride", + commands["Emulation.setDisplayFeaturesOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setDisplayFeaturesOverride", + "command", + ), + clearDisplayFeaturesOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.ClearDisplayFeaturesOverrideParams) => + commands["Emulation.clearDisplayFeaturesOverride"].result.parse( + await send( + "Emulation.clearDisplayFeaturesOverride", + commands["Emulation.clearDisplayFeaturesOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.clearDisplayFeaturesOverride", + "command", + ), + setScrollbarsHidden: withCdpName( + async (params?: cdp.types.ts.Emulation.SetScrollbarsHiddenParams) => + commands["Emulation.setScrollbarsHidden"].result.parse( + await send( + "Emulation.setScrollbarsHidden", + commands["Emulation.setScrollbarsHidden"].params.parse(params ?? {}), + ), + ), + "Emulation.setScrollbarsHidden", + "command", + ), + setDocumentCookieDisabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDocumentCookieDisabledParams) => + commands["Emulation.setDocumentCookieDisabled"].result.parse( + await send( + "Emulation.setDocumentCookieDisabled", + commands["Emulation.setDocumentCookieDisabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setDocumentCookieDisabled", + "command", + ), + setEmitTouchEventsForMouse: withCdpName( + async (params?: cdp.types.ts.Emulation.SetEmitTouchEventsForMouseParams) => + commands["Emulation.setEmitTouchEventsForMouse"].result.parse( + await send( + "Emulation.setEmitTouchEventsForMouse", + commands["Emulation.setEmitTouchEventsForMouse"].params.parse(params ?? {}), + ), + ), + "Emulation.setEmitTouchEventsForMouse", + "command", + ), + setEmulatedMedia: withCdpName( + async (params?: cdp.types.ts.Emulation.SetEmulatedMediaParams) => + commands["Emulation.setEmulatedMedia"].result.parse( + await send("Emulation.setEmulatedMedia", commands["Emulation.setEmulatedMedia"].params.parse(params ?? {})), + ), + "Emulation.setEmulatedMedia", + "command", + ), + setEmulatedVisionDeficiency: withCdpName( + async (params?: cdp.types.ts.Emulation.SetEmulatedVisionDeficiencyParams) => + commands["Emulation.setEmulatedVisionDeficiency"].result.parse( + await send( + "Emulation.setEmulatedVisionDeficiency", + commands["Emulation.setEmulatedVisionDeficiency"].params.parse(params ?? {}), + ), + ), + "Emulation.setEmulatedVisionDeficiency", + "command", + ), + setEmulatedOSTextScale: withCdpName( + async (params?: cdp.types.ts.Emulation.SetEmulatedOSTextScaleParams) => + commands["Emulation.setEmulatedOSTextScale"].result.parse( + await send( + "Emulation.setEmulatedOSTextScale", + commands["Emulation.setEmulatedOSTextScale"].params.parse(params ?? {}), + ), + ), + "Emulation.setEmulatedOSTextScale", + "command", + ), + setGeolocationOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetGeolocationOverrideParams) => + commands["Emulation.setGeolocationOverride"].result.parse( + await send( + "Emulation.setGeolocationOverride", + commands["Emulation.setGeolocationOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setGeolocationOverride", + "command", + ), + getOverriddenSensorInformation: withCdpName( + async (params?: cdp.types.ts.Emulation.GetOverriddenSensorInformationParams) => + commands["Emulation.getOverriddenSensorInformation"].result.parse( + await send( + "Emulation.getOverriddenSensorInformation", + commands["Emulation.getOverriddenSensorInformation"].params.parse(params ?? {}), + ), + ), + "Emulation.getOverriddenSensorInformation", + "command", + ), + setSensorOverrideEnabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetSensorOverrideEnabledParams) => + commands["Emulation.setSensorOverrideEnabled"].result.parse( + await send( + "Emulation.setSensorOverrideEnabled", + commands["Emulation.setSensorOverrideEnabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setSensorOverrideEnabled", + "command", + ), + setSensorOverrideReadings: withCdpName( + async (params?: cdp.types.ts.Emulation.SetSensorOverrideReadingsParams) => + commands["Emulation.setSensorOverrideReadings"].result.parse( + await send( + "Emulation.setSensorOverrideReadings", + commands["Emulation.setSensorOverrideReadings"].params.parse(params ?? {}), + ), + ), + "Emulation.setSensorOverrideReadings", + "command", + ), + setPressureSourceOverrideEnabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetPressureSourceOverrideEnabledParams) => + commands["Emulation.setPressureSourceOverrideEnabled"].result.parse( + await send( + "Emulation.setPressureSourceOverrideEnabled", + commands["Emulation.setPressureSourceOverrideEnabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setPressureSourceOverrideEnabled", + "command", + ), + setPressureStateOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetPressureStateOverrideParams) => + commands["Emulation.setPressureStateOverride"].result.parse( + await send( + "Emulation.setPressureStateOverride", + commands["Emulation.setPressureStateOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setPressureStateOverride", + "command", + ), + setPressureDataOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetPressureDataOverrideParams) => + commands["Emulation.setPressureDataOverride"].result.parse( + await send( + "Emulation.setPressureDataOverride", + commands["Emulation.setPressureDataOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setPressureDataOverride", + "command", + ), + setIdleOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetIdleOverrideParams) => + commands["Emulation.setIdleOverride"].result.parse( + await send("Emulation.setIdleOverride", commands["Emulation.setIdleOverride"].params.parse(params ?? {})), + ), + "Emulation.setIdleOverride", + "command", + ), + clearIdleOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.ClearIdleOverrideParams) => + commands["Emulation.clearIdleOverride"].result.parse( + await send( + "Emulation.clearIdleOverride", + commands["Emulation.clearIdleOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.clearIdleOverride", + "command", + ), + setNavigatorOverrides: withCdpName( + async (params?: cdp.types.ts.Emulation.SetNavigatorOverridesParams) => + commands["Emulation.setNavigatorOverrides"].result.parse( + await send( + "Emulation.setNavigatorOverrides", + commands["Emulation.setNavigatorOverrides"].params.parse(params ?? {}), + ), + ), + "Emulation.setNavigatorOverrides", + "command", + ), + setPageScaleFactor: withCdpName( + async (params?: cdp.types.ts.Emulation.SetPageScaleFactorParams) => + commands["Emulation.setPageScaleFactor"].result.parse( + await send( + "Emulation.setPageScaleFactor", + commands["Emulation.setPageScaleFactor"].params.parse(params ?? {}), + ), + ), + "Emulation.setPageScaleFactor", + "command", + ), + setScriptExecutionDisabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetScriptExecutionDisabledParams) => + commands["Emulation.setScriptExecutionDisabled"].result.parse( + await send( + "Emulation.setScriptExecutionDisabled", + commands["Emulation.setScriptExecutionDisabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setScriptExecutionDisabled", + "command", + ), + setTouchEmulationEnabled: withCdpName( + async (params?: cdp.types.ts.Emulation.SetTouchEmulationEnabledParams) => + commands["Emulation.setTouchEmulationEnabled"].result.parse( + await send( + "Emulation.setTouchEmulationEnabled", + commands["Emulation.setTouchEmulationEnabled"].params.parse(params ?? {}), + ), + ), + "Emulation.setTouchEmulationEnabled", + "command", + ), + setVirtualTimePolicy: withCdpName( + async (params?: cdp.types.ts.Emulation.SetVirtualTimePolicyParams) => + commands["Emulation.setVirtualTimePolicy"].result.parse( + await send( + "Emulation.setVirtualTimePolicy", + commands["Emulation.setVirtualTimePolicy"].params.parse(params ?? {}), + ), + ), + "Emulation.setVirtualTimePolicy", + "command", + ), + setLocaleOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetLocaleOverrideParams) => + commands["Emulation.setLocaleOverride"].result.parse( + await send( + "Emulation.setLocaleOverride", + commands["Emulation.setLocaleOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setLocaleOverride", + "command", + ), + setTimezoneOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetTimezoneOverrideParams) => + commands["Emulation.setTimezoneOverride"].result.parse( + await send( + "Emulation.setTimezoneOverride", + commands["Emulation.setTimezoneOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setTimezoneOverride", + "command", + ), + setVisibleSize: withCdpName( + async (params?: cdp.types.ts.Emulation.SetVisibleSizeParams) => + commands["Emulation.setVisibleSize"].result.parse( + await send("Emulation.setVisibleSize", commands["Emulation.setVisibleSize"].params.parse(params ?? {})), + ), + "Emulation.setVisibleSize", + "command", + ), + setDisabledImageTypes: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDisabledImageTypesParams) => + commands["Emulation.setDisabledImageTypes"].result.parse( + await send( + "Emulation.setDisabledImageTypes", + commands["Emulation.setDisabledImageTypes"].params.parse(params ?? {}), + ), + ), + "Emulation.setDisabledImageTypes", + "command", + ), + setDataSaverOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetDataSaverOverrideParams) => + commands["Emulation.setDataSaverOverride"].result.parse( + await send( + "Emulation.setDataSaverOverride", + commands["Emulation.setDataSaverOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setDataSaverOverride", + "command", + ), + setHardwareConcurrencyOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetHardwareConcurrencyOverrideParams) => + commands["Emulation.setHardwareConcurrencyOverride"].result.parse( + await send( + "Emulation.setHardwareConcurrencyOverride", + commands["Emulation.setHardwareConcurrencyOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setHardwareConcurrencyOverride", + "command", + ), + setUserAgentOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetUserAgentOverrideParams) => + commands["Emulation.setUserAgentOverride"].result.parse( + await send( + "Emulation.setUserAgentOverride", + commands["Emulation.setUserAgentOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setUserAgentOverride", + "command", + ), + setAutomationOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetAutomationOverrideParams) => + commands["Emulation.setAutomationOverride"].result.parse( + await send( + "Emulation.setAutomationOverride", + commands["Emulation.setAutomationOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setAutomationOverride", + "command", + ), + setSmallViewportHeightDifferenceOverride: withCdpName( + async (params?: cdp.types.ts.Emulation.SetSmallViewportHeightDifferenceOverrideParams) => + commands["Emulation.setSmallViewportHeightDifferenceOverride"].result.parse( + await send( + "Emulation.setSmallViewportHeightDifferenceOverride", + commands["Emulation.setSmallViewportHeightDifferenceOverride"].params.parse(params ?? {}), + ), + ), + "Emulation.setSmallViewportHeightDifferenceOverride", + "command", + ), + getScreenInfos: withCdpName( + async (params?: cdp.types.ts.Emulation.GetScreenInfosParams) => + commands["Emulation.getScreenInfos"].result.parse( + await send("Emulation.getScreenInfos", commands["Emulation.getScreenInfos"].params.parse(params ?? {})), + ), + "Emulation.getScreenInfos", + "command", + ), + addScreen: withCdpName( + async (params?: cdp.types.ts.Emulation.AddScreenParams) => + commands["Emulation.addScreen"].result.parse( + await send("Emulation.addScreen", commands["Emulation.addScreen"].params.parse(params ?? {})), + ), + "Emulation.addScreen", + "command", + ), + updateScreen: withCdpName( + async (params?: cdp.types.ts.Emulation.UpdateScreenParams) => + commands["Emulation.updateScreen"].result.parse( + await send("Emulation.updateScreen", commands["Emulation.updateScreen"].params.parse(params ?? {})), + ), + "Emulation.updateScreen", + "command", + ), + removeScreen: withCdpName( + async (params?: cdp.types.ts.Emulation.RemoveScreenParams) => + commands["Emulation.removeScreen"].result.parse( + await send("Emulation.removeScreen", commands["Emulation.removeScreen"].params.parse(params ?? {})), + ), + "Emulation.removeScreen", + "command", + ), + setPrimaryScreen: withCdpName( + async (params?: cdp.types.ts.Emulation.SetPrimaryScreenParams) => + commands["Emulation.setPrimaryScreen"].result.parse( + await send("Emulation.setPrimaryScreen", commands["Emulation.setPrimaryScreen"].params.parse(params ?? {})), + ), + "Emulation.setPrimaryScreen", + "command", + ), + virtualTimeBudgetExpired: withCdpName( + events["Emulation.virtualTimeBudgetExpired"], + "Emulation.virtualTimeBudgetExpired", + "event", + ), + screenOrientationLockChanged: withCdpName( + events["Emulation.screenOrientationLockChanged"], + "Emulation.screenOrientationLockChanged", + "event", + ), }, EventBreakpoints: { - setInstrumentationBreakpoint: withCdpName(async (params?: unknown) => commands["EventBreakpoints.setInstrumentationBreakpoint"].result.parse(await send("EventBreakpoints.setInstrumentationBreakpoint", commands["EventBreakpoints.setInstrumentationBreakpoint"].params.parse(params ?? {}))), "EventBreakpoints.setInstrumentationBreakpoint", "command"), - removeInstrumentationBreakpoint: withCdpName(async (params?: unknown) => commands["EventBreakpoints.removeInstrumentationBreakpoint"].result.parse(await send("EventBreakpoints.removeInstrumentationBreakpoint", commands["EventBreakpoints.removeInstrumentationBreakpoint"].params.parse(params ?? {}))), "EventBreakpoints.removeInstrumentationBreakpoint", "command"), - disable: withCdpName(async (params?: unknown) => commands["EventBreakpoints.disable"].result.parse(await send("EventBreakpoints.disable", commands["EventBreakpoints.disable"].params.parse(params ?? {}))), "EventBreakpoints.disable", "command"), + setInstrumentationBreakpoint: withCdpName( + async (params?: cdp.types.ts.EventBreakpoints.SetInstrumentationBreakpointParams) => + commands["EventBreakpoints.setInstrumentationBreakpoint"].result.parse( + await send( + "EventBreakpoints.setInstrumentationBreakpoint", + commands["EventBreakpoints.setInstrumentationBreakpoint"].params.parse(params ?? {}), + ), + ), + "EventBreakpoints.setInstrumentationBreakpoint", + "command", + ), + removeInstrumentationBreakpoint: withCdpName( + async (params?: cdp.types.ts.EventBreakpoints.RemoveInstrumentationBreakpointParams) => + commands["EventBreakpoints.removeInstrumentationBreakpoint"].result.parse( + await send( + "EventBreakpoints.removeInstrumentationBreakpoint", + commands["EventBreakpoints.removeInstrumentationBreakpoint"].params.parse(params ?? {}), + ), + ), + "EventBreakpoints.removeInstrumentationBreakpoint", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.EventBreakpoints.DisableParams) => + commands["EventBreakpoints.disable"].result.parse( + await send("EventBreakpoints.disable", commands["EventBreakpoints.disable"].params.parse(params ?? {})), + ), + "EventBreakpoints.disable", + "command", + ), }, Extensions: { - triggerAction: withCdpName(async (params?: unknown) => commands["Extensions.triggerAction"].result.parse(await send("Extensions.triggerAction", commands["Extensions.triggerAction"].params.parse(params ?? {}))), "Extensions.triggerAction", "command"), - loadUnpacked: withCdpName(async (params?: unknown) => commands["Extensions.loadUnpacked"].result.parse(await send("Extensions.loadUnpacked", commands["Extensions.loadUnpacked"].params.parse(params ?? {}))), "Extensions.loadUnpacked", "command"), - getExtensions: withCdpName(async (params?: unknown) => commands["Extensions.getExtensions"].result.parse(await send("Extensions.getExtensions", commands["Extensions.getExtensions"].params.parse(params ?? {}))), "Extensions.getExtensions", "command"), - uninstall: withCdpName(async (params?: unknown) => commands["Extensions.uninstall"].result.parse(await send("Extensions.uninstall", commands["Extensions.uninstall"].params.parse(params ?? {}))), "Extensions.uninstall", "command"), - getStorageItems: withCdpName(async (params?: unknown) => commands["Extensions.getStorageItems"].result.parse(await send("Extensions.getStorageItems", commands["Extensions.getStorageItems"].params.parse(params ?? {}))), "Extensions.getStorageItems", "command"), - removeStorageItems: withCdpName(async (params?: unknown) => commands["Extensions.removeStorageItems"].result.parse(await send("Extensions.removeStorageItems", commands["Extensions.removeStorageItems"].params.parse(params ?? {}))), "Extensions.removeStorageItems", "command"), - clearStorageItems: withCdpName(async (params?: unknown) => commands["Extensions.clearStorageItems"].result.parse(await send("Extensions.clearStorageItems", commands["Extensions.clearStorageItems"].params.parse(params ?? {}))), "Extensions.clearStorageItems", "command"), - setStorageItems: withCdpName(async (params?: unknown) => commands["Extensions.setStorageItems"].result.parse(await send("Extensions.setStorageItems", commands["Extensions.setStorageItems"].params.parse(params ?? {}))), "Extensions.setStorageItems", "command"), + triggerAction: withCdpName( + async (params?: cdp.types.ts.Extensions.TriggerActionParams) => + commands["Extensions.triggerAction"].result.parse( + await send("Extensions.triggerAction", commands["Extensions.triggerAction"].params.parse(params ?? {})), + ), + "Extensions.triggerAction", + "command", + ), + loadUnpacked: withCdpName( + async (params?: cdp.types.ts.Extensions.LoadUnpackedParams) => + commands["Extensions.loadUnpacked"].result.parse( + await send("Extensions.loadUnpacked", commands["Extensions.loadUnpacked"].params.parse(params ?? {})), + ), + "Extensions.loadUnpacked", + "command", + ), + getExtensions: withCdpName( + async (params?: cdp.types.ts.Extensions.GetExtensionsParams) => + commands["Extensions.getExtensions"].result.parse( + await send("Extensions.getExtensions", commands["Extensions.getExtensions"].params.parse(params ?? {})), + ), + "Extensions.getExtensions", + "command", + ), + uninstall: withCdpName( + async (params?: cdp.types.ts.Extensions.UninstallParams) => + commands["Extensions.uninstall"].result.parse( + await send("Extensions.uninstall", commands["Extensions.uninstall"].params.parse(params ?? {})), + ), + "Extensions.uninstall", + "command", + ), + getStorageItems: withCdpName( + async (params?: cdp.types.ts.Extensions.GetStorageItemsParams) => + commands["Extensions.getStorageItems"].result.parse( + await send("Extensions.getStorageItems", commands["Extensions.getStorageItems"].params.parse(params ?? {})), + ), + "Extensions.getStorageItems", + "command", + ), + removeStorageItems: withCdpName( + async (params?: cdp.types.ts.Extensions.RemoveStorageItemsParams) => + commands["Extensions.removeStorageItems"].result.parse( + await send( + "Extensions.removeStorageItems", + commands["Extensions.removeStorageItems"].params.parse(params ?? {}), + ), + ), + "Extensions.removeStorageItems", + "command", + ), + clearStorageItems: withCdpName( + async (params?: cdp.types.ts.Extensions.ClearStorageItemsParams) => + commands["Extensions.clearStorageItems"].result.parse( + await send( + "Extensions.clearStorageItems", + commands["Extensions.clearStorageItems"].params.parse(params ?? {}), + ), + ), + "Extensions.clearStorageItems", + "command", + ), + setStorageItems: withCdpName( + async (params?: cdp.types.ts.Extensions.SetStorageItemsParams) => + commands["Extensions.setStorageItems"].result.parse( + await send("Extensions.setStorageItems", commands["Extensions.setStorageItems"].params.parse(params ?? {})), + ), + "Extensions.setStorageItems", + "command", + ), }, FedCm: { - enable: withCdpName(async (params?: unknown) => commands["FedCm.enable"].result.parse(await send("FedCm.enable", commands["FedCm.enable"].params.parse(params ?? {}))), "FedCm.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["FedCm.disable"].result.parse(await send("FedCm.disable", commands["FedCm.disable"].params.parse(params ?? {}))), "FedCm.disable", "command"), - selectAccount: withCdpName(async (params?: unknown) => commands["FedCm.selectAccount"].result.parse(await send("FedCm.selectAccount", commands["FedCm.selectAccount"].params.parse(params ?? {}))), "FedCm.selectAccount", "command"), - clickDialogButton: withCdpName(async (params?: unknown) => commands["FedCm.clickDialogButton"].result.parse(await send("FedCm.clickDialogButton", commands["FedCm.clickDialogButton"].params.parse(params ?? {}))), "FedCm.clickDialogButton", "command"), - openUrl: withCdpName(async (params?: unknown) => commands["FedCm.openUrl"].result.parse(await send("FedCm.openUrl", commands["FedCm.openUrl"].params.parse(params ?? {}))), "FedCm.openUrl", "command"), - dismissDialog: withCdpName(async (params?: unknown) => commands["FedCm.dismissDialog"].result.parse(await send("FedCm.dismissDialog", commands["FedCm.dismissDialog"].params.parse(params ?? {}))), "FedCm.dismissDialog", "command"), - resetCooldown: withCdpName(async (params?: unknown) => commands["FedCm.resetCooldown"].result.parse(await send("FedCm.resetCooldown", commands["FedCm.resetCooldown"].params.parse(params ?? {}))), "FedCm.resetCooldown", "command"), - dialogShown: events["FedCm.dialogShown"] as CdpEventAlias, - dialogClosed: events["FedCm.dialogClosed"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.FedCm.EnableParams) => + commands["FedCm.enable"].result.parse( + await send("FedCm.enable", commands["FedCm.enable"].params.parse(params ?? {})), + ), + "FedCm.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.FedCm.DisableParams) => + commands["FedCm.disable"].result.parse( + await send("FedCm.disable", commands["FedCm.disable"].params.parse(params ?? {})), + ), + "FedCm.disable", + "command", + ), + selectAccount: withCdpName( + async (params?: cdp.types.ts.FedCm.SelectAccountParams) => + commands["FedCm.selectAccount"].result.parse( + await send("FedCm.selectAccount", commands["FedCm.selectAccount"].params.parse(params ?? {})), + ), + "FedCm.selectAccount", + "command", + ), + clickDialogButton: withCdpName( + async (params?: cdp.types.ts.FedCm.ClickDialogButtonParams) => + commands["FedCm.clickDialogButton"].result.parse( + await send("FedCm.clickDialogButton", commands["FedCm.clickDialogButton"].params.parse(params ?? {})), + ), + "FedCm.clickDialogButton", + "command", + ), + openUrl: withCdpName( + async (params?: cdp.types.ts.FedCm.OpenUrlParams) => + commands["FedCm.openUrl"].result.parse( + await send("FedCm.openUrl", commands["FedCm.openUrl"].params.parse(params ?? {})), + ), + "FedCm.openUrl", + "command", + ), + dismissDialog: withCdpName( + async (params?: cdp.types.ts.FedCm.DismissDialogParams) => + commands["FedCm.dismissDialog"].result.parse( + await send("FedCm.dismissDialog", commands["FedCm.dismissDialog"].params.parse(params ?? {})), + ), + "FedCm.dismissDialog", + "command", + ), + resetCooldown: withCdpName( + async (params?: cdp.types.ts.FedCm.ResetCooldownParams) => + commands["FedCm.resetCooldown"].result.parse( + await send("FedCm.resetCooldown", commands["FedCm.resetCooldown"].params.parse(params ?? {})), + ), + "FedCm.resetCooldown", + "command", + ), + dialogShown: withCdpName(events["FedCm.dialogShown"], "FedCm.dialogShown", "event"), + dialogClosed: withCdpName(events["FedCm.dialogClosed"], "FedCm.dialogClosed", "event"), }, Fetch: { - disable: withCdpName(async (params?: unknown) => commands["Fetch.disable"].result.parse(await send("Fetch.disable", commands["Fetch.disable"].params.parse(params ?? {}))), "Fetch.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Fetch.enable"].result.parse(await send("Fetch.enable", commands["Fetch.enable"].params.parse(params ?? {}))), "Fetch.enable", "command"), - failRequest: withCdpName(async (params?: unknown) => commands["Fetch.failRequest"].result.parse(await send("Fetch.failRequest", commands["Fetch.failRequest"].params.parse(params ?? {}))), "Fetch.failRequest", "command"), - fulfillRequest: withCdpName(async (params?: unknown) => commands["Fetch.fulfillRequest"].result.parse(await send("Fetch.fulfillRequest", commands["Fetch.fulfillRequest"].params.parse(params ?? {}))), "Fetch.fulfillRequest", "command"), - continueRequest: withCdpName(async (params?: unknown) => commands["Fetch.continueRequest"].result.parse(await send("Fetch.continueRequest", commands["Fetch.continueRequest"].params.parse(params ?? {}))), "Fetch.continueRequest", "command"), - continueWithAuth: withCdpName(async (params?: unknown) => commands["Fetch.continueWithAuth"].result.parse(await send("Fetch.continueWithAuth", commands["Fetch.continueWithAuth"].params.parse(params ?? {}))), "Fetch.continueWithAuth", "command"), - continueResponse: withCdpName(async (params?: unknown) => commands["Fetch.continueResponse"].result.parse(await send("Fetch.continueResponse", commands["Fetch.continueResponse"].params.parse(params ?? {}))), "Fetch.continueResponse", "command"), - getResponseBody: withCdpName(async (params?: unknown) => commands["Fetch.getResponseBody"].result.parse(await send("Fetch.getResponseBody", commands["Fetch.getResponseBody"].params.parse(params ?? {}))), "Fetch.getResponseBody", "command"), - takeResponseBodyAsStream: withCdpName(async (params?: unknown) => commands["Fetch.takeResponseBodyAsStream"].result.parse(await send("Fetch.takeResponseBodyAsStream", commands["Fetch.takeResponseBodyAsStream"].params.parse(params ?? {}))), "Fetch.takeResponseBodyAsStream", "command"), - requestPaused: events["Fetch.requestPaused"] as CdpEventAlias, - authRequired: events["Fetch.authRequired"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Fetch.DisableParams) => + commands["Fetch.disable"].result.parse( + await send("Fetch.disable", commands["Fetch.disable"].params.parse(params ?? {})), + ), + "Fetch.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Fetch.EnableParams) => + commands["Fetch.enable"].result.parse( + await send("Fetch.enable", commands["Fetch.enable"].params.parse(params ?? {})), + ), + "Fetch.enable", + "command", + ), + failRequest: withCdpName( + async (params?: cdp.types.ts.Fetch.FailRequestParams) => + commands["Fetch.failRequest"].result.parse( + await send("Fetch.failRequest", commands["Fetch.failRequest"].params.parse(params ?? {})), + ), + "Fetch.failRequest", + "command", + ), + fulfillRequest: withCdpName( + async (params?: cdp.types.ts.Fetch.FulfillRequestParams) => + commands["Fetch.fulfillRequest"].result.parse( + await send("Fetch.fulfillRequest", commands["Fetch.fulfillRequest"].params.parse(params ?? {})), + ), + "Fetch.fulfillRequest", + "command", + ), + continueRequest: withCdpName( + async (params?: cdp.types.ts.Fetch.ContinueRequestParams) => + commands["Fetch.continueRequest"].result.parse( + await send("Fetch.continueRequest", commands["Fetch.continueRequest"].params.parse(params ?? {})), + ), + "Fetch.continueRequest", + "command", + ), + continueWithAuth: withCdpName( + async (params?: cdp.types.ts.Fetch.ContinueWithAuthParams) => + commands["Fetch.continueWithAuth"].result.parse( + await send("Fetch.continueWithAuth", commands["Fetch.continueWithAuth"].params.parse(params ?? {})), + ), + "Fetch.continueWithAuth", + "command", + ), + continueResponse: withCdpName( + async (params?: cdp.types.ts.Fetch.ContinueResponseParams) => + commands["Fetch.continueResponse"].result.parse( + await send("Fetch.continueResponse", commands["Fetch.continueResponse"].params.parse(params ?? {})), + ), + "Fetch.continueResponse", + "command", + ), + getResponseBody: withCdpName( + async (params?: cdp.types.ts.Fetch.GetResponseBodyParams) => + commands["Fetch.getResponseBody"].result.parse( + await send("Fetch.getResponseBody", commands["Fetch.getResponseBody"].params.parse(params ?? {})), + ), + "Fetch.getResponseBody", + "command", + ), + takeResponseBodyAsStream: withCdpName( + async (params?: cdp.types.ts.Fetch.TakeResponseBodyAsStreamParams) => + commands["Fetch.takeResponseBodyAsStream"].result.parse( + await send( + "Fetch.takeResponseBodyAsStream", + commands["Fetch.takeResponseBodyAsStream"].params.parse(params ?? {}), + ), + ), + "Fetch.takeResponseBodyAsStream", + "command", + ), + requestPaused: withCdpName(events["Fetch.requestPaused"], "Fetch.requestPaused", "event"), + authRequired: withCdpName(events["Fetch.authRequired"], "Fetch.authRequired", "event"), }, FileSystem: { - getDirectory: withCdpName(async (params?: unknown) => commands["FileSystem.getDirectory"].result.parse(await send("FileSystem.getDirectory", commands["FileSystem.getDirectory"].params.parse(params ?? {}))), "FileSystem.getDirectory", "command"), + getDirectory: withCdpName( + async (params?: cdp.types.ts.FileSystem.GetDirectoryParams) => + commands["FileSystem.getDirectory"].result.parse( + await send("FileSystem.getDirectory", commands["FileSystem.getDirectory"].params.parse(params ?? {})), + ), + "FileSystem.getDirectory", + "command", + ), }, HeadlessExperimental: { - beginFrame: withCdpName(async (params?: unknown) => commands["HeadlessExperimental.beginFrame"].result.parse(await send("HeadlessExperimental.beginFrame", commands["HeadlessExperimental.beginFrame"].params.parse(params ?? {}))), "HeadlessExperimental.beginFrame", "command"), - disable: withCdpName(async (params?: unknown) => commands["HeadlessExperimental.disable"].result.parse(await send("HeadlessExperimental.disable", commands["HeadlessExperimental.disable"].params.parse(params ?? {}))), "HeadlessExperimental.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["HeadlessExperimental.enable"].result.parse(await send("HeadlessExperimental.enable", commands["HeadlessExperimental.enable"].params.parse(params ?? {}))), "HeadlessExperimental.enable", "command"), + beginFrame: withCdpName( + async (params?: cdp.types.ts.HeadlessExperimental.BeginFrameParams) => + commands["HeadlessExperimental.beginFrame"].result.parse( + await send( + "HeadlessExperimental.beginFrame", + commands["HeadlessExperimental.beginFrame"].params.parse(params ?? {}), + ), + ), + "HeadlessExperimental.beginFrame", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.HeadlessExperimental.DisableParams) => + commands["HeadlessExperimental.disable"].result.parse( + await send( + "HeadlessExperimental.disable", + commands["HeadlessExperimental.disable"].params.parse(params ?? {}), + ), + ), + "HeadlessExperimental.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.HeadlessExperimental.EnableParams) => + commands["HeadlessExperimental.enable"].result.parse( + await send( + "HeadlessExperimental.enable", + commands["HeadlessExperimental.enable"].params.parse(params ?? {}), + ), + ), + "HeadlessExperimental.enable", + "command", + ), }, HeapProfiler: { - addInspectedHeapObject: withCdpName(async (params?: unknown) => commands["HeapProfiler.addInspectedHeapObject"].result.parse(await send("HeapProfiler.addInspectedHeapObject", commands["HeapProfiler.addInspectedHeapObject"].params.parse(params ?? {}))), "HeapProfiler.addInspectedHeapObject", "command"), - collectGarbage: withCdpName(async (params?: unknown) => commands["HeapProfiler.collectGarbage"].result.parse(await send("HeapProfiler.collectGarbage", commands["HeapProfiler.collectGarbage"].params.parse(params ?? {}))), "HeapProfiler.collectGarbage", "command"), - disable: withCdpName(async (params?: unknown) => commands["HeapProfiler.disable"].result.parse(await send("HeapProfiler.disable", commands["HeapProfiler.disable"].params.parse(params ?? {}))), "HeapProfiler.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["HeapProfiler.enable"].result.parse(await send("HeapProfiler.enable", commands["HeapProfiler.enable"].params.parse(params ?? {}))), "HeapProfiler.enable", "command"), - getHeapObjectId: withCdpName(async (params?: unknown) => commands["HeapProfiler.getHeapObjectId"].result.parse(await send("HeapProfiler.getHeapObjectId", commands["HeapProfiler.getHeapObjectId"].params.parse(params ?? {}))), "HeapProfiler.getHeapObjectId", "command"), - getObjectByHeapObjectId: withCdpName(async (params?: unknown) => commands["HeapProfiler.getObjectByHeapObjectId"].result.parse(await send("HeapProfiler.getObjectByHeapObjectId", commands["HeapProfiler.getObjectByHeapObjectId"].params.parse(params ?? {}))), "HeapProfiler.getObjectByHeapObjectId", "command"), - getSamplingProfile: withCdpName(async (params?: unknown) => commands["HeapProfiler.getSamplingProfile"].result.parse(await send("HeapProfiler.getSamplingProfile", commands["HeapProfiler.getSamplingProfile"].params.parse(params ?? {}))), "HeapProfiler.getSamplingProfile", "command"), - startSampling: withCdpName(async (params?: unknown) => commands["HeapProfiler.startSampling"].result.parse(await send("HeapProfiler.startSampling", commands["HeapProfiler.startSampling"].params.parse(params ?? {}))), "HeapProfiler.startSampling", "command"), - startTrackingHeapObjects: withCdpName(async (params?: unknown) => commands["HeapProfiler.startTrackingHeapObjects"].result.parse(await send("HeapProfiler.startTrackingHeapObjects", commands["HeapProfiler.startTrackingHeapObjects"].params.parse(params ?? {}))), "HeapProfiler.startTrackingHeapObjects", "command"), - stopSampling: withCdpName(async (params?: unknown) => commands["HeapProfiler.stopSampling"].result.parse(await send("HeapProfiler.stopSampling", commands["HeapProfiler.stopSampling"].params.parse(params ?? {}))), "HeapProfiler.stopSampling", "command"), - stopTrackingHeapObjects: withCdpName(async (params?: unknown) => commands["HeapProfiler.stopTrackingHeapObjects"].result.parse(await send("HeapProfiler.stopTrackingHeapObjects", commands["HeapProfiler.stopTrackingHeapObjects"].params.parse(params ?? {}))), "HeapProfiler.stopTrackingHeapObjects", "command"), - takeHeapSnapshot: withCdpName(async (params?: unknown) => commands["HeapProfiler.takeHeapSnapshot"].result.parse(await send("HeapProfiler.takeHeapSnapshot", commands["HeapProfiler.takeHeapSnapshot"].params.parse(params ?? {}))), "HeapProfiler.takeHeapSnapshot", "command"), - addHeapSnapshotChunk: events["HeapProfiler.addHeapSnapshotChunk"] as CdpEventAlias, - heapStatsUpdate: events["HeapProfiler.heapStatsUpdate"] as CdpEventAlias, - lastSeenObjectId: events["HeapProfiler.lastSeenObjectId"] as CdpEventAlias, - reportHeapSnapshotProgress: events["HeapProfiler.reportHeapSnapshotProgress"] as CdpEventAlias, - resetProfiles: events["HeapProfiler.resetProfiles"] as CdpEventAlias, + addInspectedHeapObject: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.AddInspectedHeapObjectParams) => + commands["HeapProfiler.addInspectedHeapObject"].result.parse( + await send( + "HeapProfiler.addInspectedHeapObject", + commands["HeapProfiler.addInspectedHeapObject"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.addInspectedHeapObject", + "command", + ), + collectGarbage: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.CollectGarbageParams) => + commands["HeapProfiler.collectGarbage"].result.parse( + await send( + "HeapProfiler.collectGarbage", + commands["HeapProfiler.collectGarbage"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.collectGarbage", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.DisableParams) => + commands["HeapProfiler.disable"].result.parse( + await send("HeapProfiler.disable", commands["HeapProfiler.disable"].params.parse(params ?? {})), + ), + "HeapProfiler.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.EnableParams) => + commands["HeapProfiler.enable"].result.parse( + await send("HeapProfiler.enable", commands["HeapProfiler.enable"].params.parse(params ?? {})), + ), + "HeapProfiler.enable", + "command", + ), + getHeapObjectId: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.GetHeapObjectIdParams) => + commands["HeapProfiler.getHeapObjectId"].result.parse( + await send( + "HeapProfiler.getHeapObjectId", + commands["HeapProfiler.getHeapObjectId"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.getHeapObjectId", + "command", + ), + getObjectByHeapObjectId: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.GetObjectByHeapObjectIdParams) => + commands["HeapProfiler.getObjectByHeapObjectId"].result.parse( + await send( + "HeapProfiler.getObjectByHeapObjectId", + commands["HeapProfiler.getObjectByHeapObjectId"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.getObjectByHeapObjectId", + "command", + ), + getSamplingProfile: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.GetSamplingProfileParams) => + commands["HeapProfiler.getSamplingProfile"].result.parse( + await send( + "HeapProfiler.getSamplingProfile", + commands["HeapProfiler.getSamplingProfile"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.getSamplingProfile", + "command", + ), + startSampling: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.StartSamplingParams) => + commands["HeapProfiler.startSampling"].result.parse( + await send("HeapProfiler.startSampling", commands["HeapProfiler.startSampling"].params.parse(params ?? {})), + ), + "HeapProfiler.startSampling", + "command", + ), + startTrackingHeapObjects: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.StartTrackingHeapObjectsParams) => + commands["HeapProfiler.startTrackingHeapObjects"].result.parse( + await send( + "HeapProfiler.startTrackingHeapObjects", + commands["HeapProfiler.startTrackingHeapObjects"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.startTrackingHeapObjects", + "command", + ), + stopSampling: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.StopSamplingParams) => + commands["HeapProfiler.stopSampling"].result.parse( + await send("HeapProfiler.stopSampling", commands["HeapProfiler.stopSampling"].params.parse(params ?? {})), + ), + "HeapProfiler.stopSampling", + "command", + ), + stopTrackingHeapObjects: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.StopTrackingHeapObjectsParams) => + commands["HeapProfiler.stopTrackingHeapObjects"].result.parse( + await send( + "HeapProfiler.stopTrackingHeapObjects", + commands["HeapProfiler.stopTrackingHeapObjects"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.stopTrackingHeapObjects", + "command", + ), + takeHeapSnapshot: withCdpName( + async (params?: cdp.types.ts.HeapProfiler.TakeHeapSnapshotParams) => + commands["HeapProfiler.takeHeapSnapshot"].result.parse( + await send( + "HeapProfiler.takeHeapSnapshot", + commands["HeapProfiler.takeHeapSnapshot"].params.parse(params ?? {}), + ), + ), + "HeapProfiler.takeHeapSnapshot", + "command", + ), + addHeapSnapshotChunk: withCdpName( + events["HeapProfiler.addHeapSnapshotChunk"], + "HeapProfiler.addHeapSnapshotChunk", + "event", + ), + heapStatsUpdate: withCdpName(events["HeapProfiler.heapStatsUpdate"], "HeapProfiler.heapStatsUpdate", "event"), + lastSeenObjectId: withCdpName(events["HeapProfiler.lastSeenObjectId"], "HeapProfiler.lastSeenObjectId", "event"), + reportHeapSnapshotProgress: withCdpName( + events["HeapProfiler.reportHeapSnapshotProgress"], + "HeapProfiler.reportHeapSnapshotProgress", + "event", + ), + resetProfiles: withCdpName(events["HeapProfiler.resetProfiles"], "HeapProfiler.resetProfiles", "event"), }, IndexedDB: { - clearObjectStore: withCdpName(async (params?: unknown) => commands["IndexedDB.clearObjectStore"].result.parse(await send("IndexedDB.clearObjectStore", commands["IndexedDB.clearObjectStore"].params.parse(params ?? {}))), "IndexedDB.clearObjectStore", "command"), - deleteDatabase: withCdpName(async (params?: unknown) => commands["IndexedDB.deleteDatabase"].result.parse(await send("IndexedDB.deleteDatabase", commands["IndexedDB.deleteDatabase"].params.parse(params ?? {}))), "IndexedDB.deleteDatabase", "command"), - deleteObjectStoreEntries: withCdpName(async (params?: unknown) => commands["IndexedDB.deleteObjectStoreEntries"].result.parse(await send("IndexedDB.deleteObjectStoreEntries", commands["IndexedDB.deleteObjectStoreEntries"].params.parse(params ?? {}))), "IndexedDB.deleteObjectStoreEntries", "command"), - disable: withCdpName(async (params?: unknown) => commands["IndexedDB.disable"].result.parse(await send("IndexedDB.disable", commands["IndexedDB.disable"].params.parse(params ?? {}))), "IndexedDB.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["IndexedDB.enable"].result.parse(await send("IndexedDB.enable", commands["IndexedDB.enable"].params.parse(params ?? {}))), "IndexedDB.enable", "command"), - requestData: withCdpName(async (params?: unknown) => commands["IndexedDB.requestData"].result.parse(await send("IndexedDB.requestData", commands["IndexedDB.requestData"].params.parse(params ?? {}))), "IndexedDB.requestData", "command"), - getMetadata: withCdpName(async (params?: unknown) => commands["IndexedDB.getMetadata"].result.parse(await send("IndexedDB.getMetadata", commands["IndexedDB.getMetadata"].params.parse(params ?? {}))), "IndexedDB.getMetadata", "command"), - requestDatabase: withCdpName(async (params?: unknown) => commands["IndexedDB.requestDatabase"].result.parse(await send("IndexedDB.requestDatabase", commands["IndexedDB.requestDatabase"].params.parse(params ?? {}))), "IndexedDB.requestDatabase", "command"), - requestDatabaseNames: withCdpName(async (params?: unknown) => commands["IndexedDB.requestDatabaseNames"].result.parse(await send("IndexedDB.requestDatabaseNames", commands["IndexedDB.requestDatabaseNames"].params.parse(params ?? {}))), "IndexedDB.requestDatabaseNames", "command"), + clearObjectStore: withCdpName( + async (params?: cdp.types.ts.IndexedDB.ClearObjectStoreParams) => + commands["IndexedDB.clearObjectStore"].result.parse( + await send("IndexedDB.clearObjectStore", commands["IndexedDB.clearObjectStore"].params.parse(params ?? {})), + ), + "IndexedDB.clearObjectStore", + "command", + ), + deleteDatabase: withCdpName( + async (params?: cdp.types.ts.IndexedDB.DeleteDatabaseParams) => + commands["IndexedDB.deleteDatabase"].result.parse( + await send("IndexedDB.deleteDatabase", commands["IndexedDB.deleteDatabase"].params.parse(params ?? {})), + ), + "IndexedDB.deleteDatabase", + "command", + ), + deleteObjectStoreEntries: withCdpName( + async (params?: cdp.types.ts.IndexedDB.DeleteObjectStoreEntriesParams) => + commands["IndexedDB.deleteObjectStoreEntries"].result.parse( + await send( + "IndexedDB.deleteObjectStoreEntries", + commands["IndexedDB.deleteObjectStoreEntries"].params.parse(params ?? {}), + ), + ), + "IndexedDB.deleteObjectStoreEntries", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.IndexedDB.DisableParams) => + commands["IndexedDB.disable"].result.parse( + await send("IndexedDB.disable", commands["IndexedDB.disable"].params.parse(params ?? {})), + ), + "IndexedDB.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.IndexedDB.EnableParams) => + commands["IndexedDB.enable"].result.parse( + await send("IndexedDB.enable", commands["IndexedDB.enable"].params.parse(params ?? {})), + ), + "IndexedDB.enable", + "command", + ), + requestData: withCdpName( + async (params?: cdp.types.ts.IndexedDB.RequestDataParams) => + commands["IndexedDB.requestData"].result.parse( + await send("IndexedDB.requestData", commands["IndexedDB.requestData"].params.parse(params ?? {})), + ), + "IndexedDB.requestData", + "command", + ), + getMetadata: withCdpName( + async (params?: cdp.types.ts.IndexedDB.GetMetadataParams) => + commands["IndexedDB.getMetadata"].result.parse( + await send("IndexedDB.getMetadata", commands["IndexedDB.getMetadata"].params.parse(params ?? {})), + ), + "IndexedDB.getMetadata", + "command", + ), + requestDatabase: withCdpName( + async (params?: cdp.types.ts.IndexedDB.RequestDatabaseParams) => + commands["IndexedDB.requestDatabase"].result.parse( + await send("IndexedDB.requestDatabase", commands["IndexedDB.requestDatabase"].params.parse(params ?? {})), + ), + "IndexedDB.requestDatabase", + "command", + ), + requestDatabaseNames: withCdpName( + async (params?: cdp.types.ts.IndexedDB.RequestDatabaseNamesParams) => + commands["IndexedDB.requestDatabaseNames"].result.parse( + await send( + "IndexedDB.requestDatabaseNames", + commands["IndexedDB.requestDatabaseNames"].params.parse(params ?? {}), + ), + ), + "IndexedDB.requestDatabaseNames", + "command", + ), }, Input: { - dispatchDragEvent: withCdpName(async (params?: unknown) => commands["Input.dispatchDragEvent"].result.parse(await send("Input.dispatchDragEvent", commands["Input.dispatchDragEvent"].params.parse(params ?? {}))), "Input.dispatchDragEvent", "command"), - dispatchKeyEvent: withCdpName(async (params?: unknown) => commands["Input.dispatchKeyEvent"].result.parse(await send("Input.dispatchKeyEvent", commands["Input.dispatchKeyEvent"].params.parse(params ?? {}))), "Input.dispatchKeyEvent", "command"), - insertText: withCdpName(async (params?: unknown) => commands["Input.insertText"].result.parse(await send("Input.insertText", commands["Input.insertText"].params.parse(params ?? {}))), "Input.insertText", "command"), - imeSetComposition: withCdpName(async (params?: unknown) => commands["Input.imeSetComposition"].result.parse(await send("Input.imeSetComposition", commands["Input.imeSetComposition"].params.parse(params ?? {}))), "Input.imeSetComposition", "command"), - dispatchMouseEvent: withCdpName(async (params?: unknown) => commands["Input.dispatchMouseEvent"].result.parse(await send("Input.dispatchMouseEvent", commands["Input.dispatchMouseEvent"].params.parse(params ?? {}))), "Input.dispatchMouseEvent", "command"), - dispatchTouchEvent: withCdpName(async (params?: unknown) => commands["Input.dispatchTouchEvent"].result.parse(await send("Input.dispatchTouchEvent", commands["Input.dispatchTouchEvent"].params.parse(params ?? {}))), "Input.dispatchTouchEvent", "command"), - cancelDragging: withCdpName(async (params?: unknown) => commands["Input.cancelDragging"].result.parse(await send("Input.cancelDragging", commands["Input.cancelDragging"].params.parse(params ?? {}))), "Input.cancelDragging", "command"), - emulateTouchFromMouseEvent: withCdpName(async (params?: unknown) => commands["Input.emulateTouchFromMouseEvent"].result.parse(await send("Input.emulateTouchFromMouseEvent", commands["Input.emulateTouchFromMouseEvent"].params.parse(params ?? {}))), "Input.emulateTouchFromMouseEvent", "command"), - setIgnoreInputEvents: withCdpName(async (params?: unknown) => commands["Input.setIgnoreInputEvents"].result.parse(await send("Input.setIgnoreInputEvents", commands["Input.setIgnoreInputEvents"].params.parse(params ?? {}))), "Input.setIgnoreInputEvents", "command"), - setInterceptDrags: withCdpName(async (params?: unknown) => commands["Input.setInterceptDrags"].result.parse(await send("Input.setInterceptDrags", commands["Input.setInterceptDrags"].params.parse(params ?? {}))), "Input.setInterceptDrags", "command"), - synthesizePinchGesture: withCdpName(async (params?: unknown) => commands["Input.synthesizePinchGesture"].result.parse(await send("Input.synthesizePinchGesture", commands["Input.synthesizePinchGesture"].params.parse(params ?? {}))), "Input.synthesizePinchGesture", "command"), - synthesizeScrollGesture: withCdpName(async (params?: unknown) => commands["Input.synthesizeScrollGesture"].result.parse(await send("Input.synthesizeScrollGesture", commands["Input.synthesizeScrollGesture"].params.parse(params ?? {}))), "Input.synthesizeScrollGesture", "command"), - synthesizeTapGesture: withCdpName(async (params?: unknown) => commands["Input.synthesizeTapGesture"].result.parse(await send("Input.synthesizeTapGesture", commands["Input.synthesizeTapGesture"].params.parse(params ?? {}))), "Input.synthesizeTapGesture", "command"), - dragIntercepted: events["Input.dragIntercepted"] as CdpEventAlias, + dispatchDragEvent: withCdpName( + async (params?: cdp.types.ts.Input.DispatchDragEventParams) => + commands["Input.dispatchDragEvent"].result.parse( + await send("Input.dispatchDragEvent", commands["Input.dispatchDragEvent"].params.parse(params ?? {})), + ), + "Input.dispatchDragEvent", + "command", + ), + dispatchKeyEvent: withCdpName( + async (params?: cdp.types.ts.Input.DispatchKeyEventParams) => + commands["Input.dispatchKeyEvent"].result.parse( + await send("Input.dispatchKeyEvent", commands["Input.dispatchKeyEvent"].params.parse(params ?? {})), + ), + "Input.dispatchKeyEvent", + "command", + ), + insertText: withCdpName( + async (params?: cdp.types.ts.Input.InsertTextParams) => + commands["Input.insertText"].result.parse( + await send("Input.insertText", commands["Input.insertText"].params.parse(params ?? {})), + ), + "Input.insertText", + "command", + ), + imeSetComposition: withCdpName( + async (params?: cdp.types.ts.Input.ImeSetCompositionParams) => + commands["Input.imeSetComposition"].result.parse( + await send("Input.imeSetComposition", commands["Input.imeSetComposition"].params.parse(params ?? {})), + ), + "Input.imeSetComposition", + "command", + ), + dispatchMouseEvent: withCdpName( + async (params?: cdp.types.ts.Input.DispatchMouseEventParams) => + commands["Input.dispatchMouseEvent"].result.parse( + await send("Input.dispatchMouseEvent", commands["Input.dispatchMouseEvent"].params.parse(params ?? {})), + ), + "Input.dispatchMouseEvent", + "command", + ), + dispatchTouchEvent: withCdpName( + async (params?: cdp.types.ts.Input.DispatchTouchEventParams) => + commands["Input.dispatchTouchEvent"].result.parse( + await send("Input.dispatchTouchEvent", commands["Input.dispatchTouchEvent"].params.parse(params ?? {})), + ), + "Input.dispatchTouchEvent", + "command", + ), + cancelDragging: withCdpName( + async (params?: cdp.types.ts.Input.CancelDraggingParams) => + commands["Input.cancelDragging"].result.parse( + await send("Input.cancelDragging", commands["Input.cancelDragging"].params.parse(params ?? {})), + ), + "Input.cancelDragging", + "command", + ), + emulateTouchFromMouseEvent: withCdpName( + async (params?: cdp.types.ts.Input.EmulateTouchFromMouseEventParams) => + commands["Input.emulateTouchFromMouseEvent"].result.parse( + await send( + "Input.emulateTouchFromMouseEvent", + commands["Input.emulateTouchFromMouseEvent"].params.parse(params ?? {}), + ), + ), + "Input.emulateTouchFromMouseEvent", + "command", + ), + setIgnoreInputEvents: withCdpName( + async (params?: cdp.types.ts.Input.SetIgnoreInputEventsParams) => + commands["Input.setIgnoreInputEvents"].result.parse( + await send("Input.setIgnoreInputEvents", commands["Input.setIgnoreInputEvents"].params.parse(params ?? {})), + ), + "Input.setIgnoreInputEvents", + "command", + ), + setInterceptDrags: withCdpName( + async (params?: cdp.types.ts.Input.SetInterceptDragsParams) => + commands["Input.setInterceptDrags"].result.parse( + await send("Input.setInterceptDrags", commands["Input.setInterceptDrags"].params.parse(params ?? {})), + ), + "Input.setInterceptDrags", + "command", + ), + synthesizePinchGesture: withCdpName( + async (params?: cdp.types.ts.Input.SynthesizePinchGestureParams) => + commands["Input.synthesizePinchGesture"].result.parse( + await send( + "Input.synthesizePinchGesture", + commands["Input.synthesizePinchGesture"].params.parse(params ?? {}), + ), + ), + "Input.synthesizePinchGesture", + "command", + ), + synthesizeScrollGesture: withCdpName( + async (params?: cdp.types.ts.Input.SynthesizeScrollGestureParams) => + commands["Input.synthesizeScrollGesture"].result.parse( + await send( + "Input.synthesizeScrollGesture", + commands["Input.synthesizeScrollGesture"].params.parse(params ?? {}), + ), + ), + "Input.synthesizeScrollGesture", + "command", + ), + synthesizeTapGesture: withCdpName( + async (params?: cdp.types.ts.Input.SynthesizeTapGestureParams) => + commands["Input.synthesizeTapGesture"].result.parse( + await send("Input.synthesizeTapGesture", commands["Input.synthesizeTapGesture"].params.parse(params ?? {})), + ), + "Input.synthesizeTapGesture", + "command", + ), + dragIntercepted: withCdpName(events["Input.dragIntercepted"], "Input.dragIntercepted", "event"), }, Inspector: { - disable: withCdpName(async (params?: unknown) => commands["Inspector.disable"].result.parse(await send("Inspector.disable", commands["Inspector.disable"].params.parse(params ?? {}))), "Inspector.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Inspector.enable"].result.parse(await send("Inspector.enable", commands["Inspector.enable"].params.parse(params ?? {}))), "Inspector.enable", "command"), - detached: events["Inspector.detached"] as CdpEventAlias, - targetCrashed: events["Inspector.targetCrashed"] as CdpEventAlias, - targetReloadedAfterCrash: events["Inspector.targetReloadedAfterCrash"] as CdpEventAlias, - workerScriptLoaded: events["Inspector.workerScriptLoaded"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Inspector.DisableParams) => + commands["Inspector.disable"].result.parse( + await send("Inspector.disable", commands["Inspector.disable"].params.parse(params ?? {})), + ), + "Inspector.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Inspector.EnableParams) => + commands["Inspector.enable"].result.parse( + await send("Inspector.enable", commands["Inspector.enable"].params.parse(params ?? {})), + ), + "Inspector.enable", + "command", + ), + detached: withCdpName(events["Inspector.detached"], "Inspector.detached", "event"), + targetCrashed: withCdpName(events["Inspector.targetCrashed"], "Inspector.targetCrashed", "event"), + targetReloadedAfterCrash: withCdpName( + events["Inspector.targetReloadedAfterCrash"], + "Inspector.targetReloadedAfterCrash", + "event", + ), + workerScriptLoaded: withCdpName(events["Inspector.workerScriptLoaded"], "Inspector.workerScriptLoaded", "event"), }, IO: { - close: withCdpName(async (params?: unknown) => commands["IO.close"].result.parse(await send("IO.close", commands["IO.close"].params.parse(params ?? {}))), "IO.close", "command"), - read: withCdpName(async (params?: unknown) => commands["IO.read"].result.parse(await send("IO.read", commands["IO.read"].params.parse(params ?? {}))), "IO.read", "command"), - resolveBlob: withCdpName(async (params?: unknown) => commands["IO.resolveBlob"].result.parse(await send("IO.resolveBlob", commands["IO.resolveBlob"].params.parse(params ?? {}))), "IO.resolveBlob", "command"), + close: withCdpName( + async (params?: cdp.types.ts.IO.CloseParams) => + commands["IO.close"].result.parse(await send("IO.close", commands["IO.close"].params.parse(params ?? {}))), + "IO.close", + "command", + ), + read: withCdpName( + async (params?: cdp.types.ts.IO.ReadParams) => + commands["IO.read"].result.parse(await send("IO.read", commands["IO.read"].params.parse(params ?? {}))), + "IO.read", + "command", + ), + resolveBlob: withCdpName( + async (params?: cdp.types.ts.IO.ResolveBlobParams) => + commands["IO.resolveBlob"].result.parse( + await send("IO.resolveBlob", commands["IO.resolveBlob"].params.parse(params ?? {})), + ), + "IO.resolveBlob", + "command", + ), }, LayerTree: { - compositingReasons: withCdpName(async (params?: unknown) => commands["LayerTree.compositingReasons"].result.parse(await send("LayerTree.compositingReasons", commands["LayerTree.compositingReasons"].params.parse(params ?? {}))), "LayerTree.compositingReasons", "command"), - disable: withCdpName(async (params?: unknown) => commands["LayerTree.disable"].result.parse(await send("LayerTree.disable", commands["LayerTree.disable"].params.parse(params ?? {}))), "LayerTree.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["LayerTree.enable"].result.parse(await send("LayerTree.enable", commands["LayerTree.enable"].params.parse(params ?? {}))), "LayerTree.enable", "command"), - loadSnapshot: withCdpName(async (params?: unknown) => commands["LayerTree.loadSnapshot"].result.parse(await send("LayerTree.loadSnapshot", commands["LayerTree.loadSnapshot"].params.parse(params ?? {}))), "LayerTree.loadSnapshot", "command"), - makeSnapshot: withCdpName(async (params?: unknown) => commands["LayerTree.makeSnapshot"].result.parse(await send("LayerTree.makeSnapshot", commands["LayerTree.makeSnapshot"].params.parse(params ?? {}))), "LayerTree.makeSnapshot", "command"), - profileSnapshot: withCdpName(async (params?: unknown) => commands["LayerTree.profileSnapshot"].result.parse(await send("LayerTree.profileSnapshot", commands["LayerTree.profileSnapshot"].params.parse(params ?? {}))), "LayerTree.profileSnapshot", "command"), - releaseSnapshot: withCdpName(async (params?: unknown) => commands["LayerTree.releaseSnapshot"].result.parse(await send("LayerTree.releaseSnapshot", commands["LayerTree.releaseSnapshot"].params.parse(params ?? {}))), "LayerTree.releaseSnapshot", "command"), - replaySnapshot: withCdpName(async (params?: unknown) => commands["LayerTree.replaySnapshot"].result.parse(await send("LayerTree.replaySnapshot", commands["LayerTree.replaySnapshot"].params.parse(params ?? {}))), "LayerTree.replaySnapshot", "command"), - snapshotCommandLog: withCdpName(async (params?: unknown) => commands["LayerTree.snapshotCommandLog"].result.parse(await send("LayerTree.snapshotCommandLog", commands["LayerTree.snapshotCommandLog"].params.parse(params ?? {}))), "LayerTree.snapshotCommandLog", "command"), - layerPainted: events["LayerTree.layerPainted"] as CdpEventAlias, - layerTreeDidChange: events["LayerTree.layerTreeDidChange"] as CdpEventAlias, + compositingReasons: withCdpName( + async (params?: cdp.types.ts.LayerTree.CompositingReasonsParams) => + commands["LayerTree.compositingReasons"].result.parse( + await send( + "LayerTree.compositingReasons", + commands["LayerTree.compositingReasons"].params.parse(params ?? {}), + ), + ), + "LayerTree.compositingReasons", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.LayerTree.DisableParams) => + commands["LayerTree.disable"].result.parse( + await send("LayerTree.disable", commands["LayerTree.disable"].params.parse(params ?? {})), + ), + "LayerTree.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.LayerTree.EnableParams) => + commands["LayerTree.enable"].result.parse( + await send("LayerTree.enable", commands["LayerTree.enable"].params.parse(params ?? {})), + ), + "LayerTree.enable", + "command", + ), + loadSnapshot: withCdpName( + async (params?: cdp.types.ts.LayerTree.LoadSnapshotParams) => + commands["LayerTree.loadSnapshot"].result.parse( + await send("LayerTree.loadSnapshot", commands["LayerTree.loadSnapshot"].params.parse(params ?? {})), + ), + "LayerTree.loadSnapshot", + "command", + ), + makeSnapshot: withCdpName( + async (params?: cdp.types.ts.LayerTree.MakeSnapshotParams) => + commands["LayerTree.makeSnapshot"].result.parse( + await send("LayerTree.makeSnapshot", commands["LayerTree.makeSnapshot"].params.parse(params ?? {})), + ), + "LayerTree.makeSnapshot", + "command", + ), + profileSnapshot: withCdpName( + async (params?: cdp.types.ts.LayerTree.ProfileSnapshotParams) => + commands["LayerTree.profileSnapshot"].result.parse( + await send("LayerTree.profileSnapshot", commands["LayerTree.profileSnapshot"].params.parse(params ?? {})), + ), + "LayerTree.profileSnapshot", + "command", + ), + releaseSnapshot: withCdpName( + async (params?: cdp.types.ts.LayerTree.ReleaseSnapshotParams) => + commands["LayerTree.releaseSnapshot"].result.parse( + await send("LayerTree.releaseSnapshot", commands["LayerTree.releaseSnapshot"].params.parse(params ?? {})), + ), + "LayerTree.releaseSnapshot", + "command", + ), + replaySnapshot: withCdpName( + async (params?: cdp.types.ts.LayerTree.ReplaySnapshotParams) => + commands["LayerTree.replaySnapshot"].result.parse( + await send("LayerTree.replaySnapshot", commands["LayerTree.replaySnapshot"].params.parse(params ?? {})), + ), + "LayerTree.replaySnapshot", + "command", + ), + snapshotCommandLog: withCdpName( + async (params?: cdp.types.ts.LayerTree.SnapshotCommandLogParams) => + commands["LayerTree.snapshotCommandLog"].result.parse( + await send( + "LayerTree.snapshotCommandLog", + commands["LayerTree.snapshotCommandLog"].params.parse(params ?? {}), + ), + ), + "LayerTree.snapshotCommandLog", + "command", + ), + layerPainted: withCdpName(events["LayerTree.layerPainted"], "LayerTree.layerPainted", "event"), + layerTreeDidChange: withCdpName(events["LayerTree.layerTreeDidChange"], "LayerTree.layerTreeDidChange", "event"), }, Log: { - clear: withCdpName(async (params?: unknown) => commands["Log.clear"].result.parse(await send("Log.clear", commands["Log.clear"].params.parse(params ?? {}))), "Log.clear", "command"), - disable: withCdpName(async (params?: unknown) => commands["Log.disable"].result.parse(await send("Log.disable", commands["Log.disable"].params.parse(params ?? {}))), "Log.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Log.enable"].result.parse(await send("Log.enable", commands["Log.enable"].params.parse(params ?? {}))), "Log.enable", "command"), - startViolationsReport: withCdpName(async (params?: unknown) => commands["Log.startViolationsReport"].result.parse(await send("Log.startViolationsReport", commands["Log.startViolationsReport"].params.parse(params ?? {}))), "Log.startViolationsReport", "command"), - stopViolationsReport: withCdpName(async (params?: unknown) => commands["Log.stopViolationsReport"].result.parse(await send("Log.stopViolationsReport", commands["Log.stopViolationsReport"].params.parse(params ?? {}))), "Log.stopViolationsReport", "command"), - entryAdded: events["Log.entryAdded"] as CdpEventAlias, + clear: withCdpName( + async (params?: cdp.types.ts.Log.ClearParams) => + commands["Log.clear"].result.parse(await send("Log.clear", commands["Log.clear"].params.parse(params ?? {}))), + "Log.clear", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Log.DisableParams) => + commands["Log.disable"].result.parse( + await send("Log.disable", commands["Log.disable"].params.parse(params ?? {})), + ), + "Log.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Log.EnableParams) => + commands["Log.enable"].result.parse( + await send("Log.enable", commands["Log.enable"].params.parse(params ?? {})), + ), + "Log.enable", + "command", + ), + startViolationsReport: withCdpName( + async (params?: cdp.types.ts.Log.StartViolationsReportParams) => + commands["Log.startViolationsReport"].result.parse( + await send("Log.startViolationsReport", commands["Log.startViolationsReport"].params.parse(params ?? {})), + ), + "Log.startViolationsReport", + "command", + ), + stopViolationsReport: withCdpName( + async (params?: cdp.types.ts.Log.StopViolationsReportParams) => + commands["Log.stopViolationsReport"].result.parse( + await send("Log.stopViolationsReport", commands["Log.stopViolationsReport"].params.parse(params ?? {})), + ), + "Log.stopViolationsReport", + "command", + ), + entryAdded: withCdpName(events["Log.entryAdded"], "Log.entryAdded", "event"), }, Media: { - enable: withCdpName(async (params?: unknown) => commands["Media.enable"].result.parse(await send("Media.enable", commands["Media.enable"].params.parse(params ?? {}))), "Media.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["Media.disable"].result.parse(await send("Media.disable", commands["Media.disable"].params.parse(params ?? {}))), "Media.disable", "command"), - playerPropertiesChanged: events["Media.playerPropertiesChanged"] as CdpEventAlias, - playerEventsAdded: events["Media.playerEventsAdded"] as CdpEventAlias, - playerMessagesLogged: events["Media.playerMessagesLogged"] as CdpEventAlias, - playerErrorsRaised: events["Media.playerErrorsRaised"] as CdpEventAlias, - playerCreated: events["Media.playerCreated"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.Media.EnableParams) => + commands["Media.enable"].result.parse( + await send("Media.enable", commands["Media.enable"].params.parse(params ?? {})), + ), + "Media.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Media.DisableParams) => + commands["Media.disable"].result.parse( + await send("Media.disable", commands["Media.disable"].params.parse(params ?? {})), + ), + "Media.disable", + "command", + ), + playerPropertiesChanged: withCdpName( + events["Media.playerPropertiesChanged"], + "Media.playerPropertiesChanged", + "event", + ), + playerEventsAdded: withCdpName(events["Media.playerEventsAdded"], "Media.playerEventsAdded", "event"), + playerMessagesLogged: withCdpName(events["Media.playerMessagesLogged"], "Media.playerMessagesLogged", "event"), + playerErrorsRaised: withCdpName(events["Media.playerErrorsRaised"], "Media.playerErrorsRaised", "event"), + playerCreated: withCdpName(events["Media.playerCreated"], "Media.playerCreated", "event"), }, Memory: { - getDOMCounters: withCdpName(async (params?: unknown) => commands["Memory.getDOMCounters"].result.parse(await send("Memory.getDOMCounters", commands["Memory.getDOMCounters"].params.parse(params ?? {}))), "Memory.getDOMCounters", "command"), - getDOMCountersForLeakDetection: withCdpName(async (params?: unknown) => commands["Memory.getDOMCountersForLeakDetection"].result.parse(await send("Memory.getDOMCountersForLeakDetection", commands["Memory.getDOMCountersForLeakDetection"].params.parse(params ?? {}))), "Memory.getDOMCountersForLeakDetection", "command"), - prepareForLeakDetection: withCdpName(async (params?: unknown) => commands["Memory.prepareForLeakDetection"].result.parse(await send("Memory.prepareForLeakDetection", commands["Memory.prepareForLeakDetection"].params.parse(params ?? {}))), "Memory.prepareForLeakDetection", "command"), - forciblyPurgeJavaScriptMemory: withCdpName(async (params?: unknown) => commands["Memory.forciblyPurgeJavaScriptMemory"].result.parse(await send("Memory.forciblyPurgeJavaScriptMemory", commands["Memory.forciblyPurgeJavaScriptMemory"].params.parse(params ?? {}))), "Memory.forciblyPurgeJavaScriptMemory", "command"), - setPressureNotificationsSuppressed: withCdpName(async (params?: unknown) => commands["Memory.setPressureNotificationsSuppressed"].result.parse(await send("Memory.setPressureNotificationsSuppressed", commands["Memory.setPressureNotificationsSuppressed"].params.parse(params ?? {}))), "Memory.setPressureNotificationsSuppressed", "command"), - simulatePressureNotification: withCdpName(async (params?: unknown) => commands["Memory.simulatePressureNotification"].result.parse(await send("Memory.simulatePressureNotification", commands["Memory.simulatePressureNotification"].params.parse(params ?? {}))), "Memory.simulatePressureNotification", "command"), - startSampling: withCdpName(async (params?: unknown) => commands["Memory.startSampling"].result.parse(await send("Memory.startSampling", commands["Memory.startSampling"].params.parse(params ?? {}))), "Memory.startSampling", "command"), - stopSampling: withCdpName(async (params?: unknown) => commands["Memory.stopSampling"].result.parse(await send("Memory.stopSampling", commands["Memory.stopSampling"].params.parse(params ?? {}))), "Memory.stopSampling", "command"), - getAllTimeSamplingProfile: withCdpName(async (params?: unknown) => commands["Memory.getAllTimeSamplingProfile"].result.parse(await send("Memory.getAllTimeSamplingProfile", commands["Memory.getAllTimeSamplingProfile"].params.parse(params ?? {}))), "Memory.getAllTimeSamplingProfile", "command"), - getBrowserSamplingProfile: withCdpName(async (params?: unknown) => commands["Memory.getBrowserSamplingProfile"].result.parse(await send("Memory.getBrowserSamplingProfile", commands["Memory.getBrowserSamplingProfile"].params.parse(params ?? {}))), "Memory.getBrowserSamplingProfile", "command"), - getSamplingProfile: withCdpName(async (params?: unknown) => commands["Memory.getSamplingProfile"].result.parse(await send("Memory.getSamplingProfile", commands["Memory.getSamplingProfile"].params.parse(params ?? {}))), "Memory.getSamplingProfile", "command"), + getDOMCounters: withCdpName( + async (params?: cdp.types.ts.Memory.GetDOMCountersParams) => + commands["Memory.getDOMCounters"].result.parse( + await send("Memory.getDOMCounters", commands["Memory.getDOMCounters"].params.parse(params ?? {})), + ), + "Memory.getDOMCounters", + "command", + ), + getDOMCountersForLeakDetection: withCdpName( + async (params?: cdp.types.ts.Memory.GetDOMCountersForLeakDetectionParams) => + commands["Memory.getDOMCountersForLeakDetection"].result.parse( + await send( + "Memory.getDOMCountersForLeakDetection", + commands["Memory.getDOMCountersForLeakDetection"].params.parse(params ?? {}), + ), + ), + "Memory.getDOMCountersForLeakDetection", + "command", + ), + prepareForLeakDetection: withCdpName( + async (params?: cdp.types.ts.Memory.PrepareForLeakDetectionParams) => + commands["Memory.prepareForLeakDetection"].result.parse( + await send( + "Memory.prepareForLeakDetection", + commands["Memory.prepareForLeakDetection"].params.parse(params ?? {}), + ), + ), + "Memory.prepareForLeakDetection", + "command", + ), + forciblyPurgeJavaScriptMemory: withCdpName( + async (params?: cdp.types.ts.Memory.ForciblyPurgeJavaScriptMemoryParams) => + commands["Memory.forciblyPurgeJavaScriptMemory"].result.parse( + await send( + "Memory.forciblyPurgeJavaScriptMemory", + commands["Memory.forciblyPurgeJavaScriptMemory"].params.parse(params ?? {}), + ), + ), + "Memory.forciblyPurgeJavaScriptMemory", + "command", + ), + setPressureNotificationsSuppressed: withCdpName( + async (params?: cdp.types.ts.Memory.SetPressureNotificationsSuppressedParams) => + commands["Memory.setPressureNotificationsSuppressed"].result.parse( + await send( + "Memory.setPressureNotificationsSuppressed", + commands["Memory.setPressureNotificationsSuppressed"].params.parse(params ?? {}), + ), + ), + "Memory.setPressureNotificationsSuppressed", + "command", + ), + simulatePressureNotification: withCdpName( + async (params?: cdp.types.ts.Memory.SimulatePressureNotificationParams) => + commands["Memory.simulatePressureNotification"].result.parse( + await send( + "Memory.simulatePressureNotification", + commands["Memory.simulatePressureNotification"].params.parse(params ?? {}), + ), + ), + "Memory.simulatePressureNotification", + "command", + ), + startSampling: withCdpName( + async (params?: cdp.types.ts.Memory.StartSamplingParams) => + commands["Memory.startSampling"].result.parse( + await send("Memory.startSampling", commands["Memory.startSampling"].params.parse(params ?? {})), + ), + "Memory.startSampling", + "command", + ), + stopSampling: withCdpName( + async (params?: cdp.types.ts.Memory.StopSamplingParams) => + commands["Memory.stopSampling"].result.parse( + await send("Memory.stopSampling", commands["Memory.stopSampling"].params.parse(params ?? {})), + ), + "Memory.stopSampling", + "command", + ), + getAllTimeSamplingProfile: withCdpName( + async (params?: cdp.types.ts.Memory.GetAllTimeSamplingProfileParams) => + commands["Memory.getAllTimeSamplingProfile"].result.parse( + await send( + "Memory.getAllTimeSamplingProfile", + commands["Memory.getAllTimeSamplingProfile"].params.parse(params ?? {}), + ), + ), + "Memory.getAllTimeSamplingProfile", + "command", + ), + getBrowserSamplingProfile: withCdpName( + async (params?: cdp.types.ts.Memory.GetBrowserSamplingProfileParams) => + commands["Memory.getBrowserSamplingProfile"].result.parse( + await send( + "Memory.getBrowserSamplingProfile", + commands["Memory.getBrowserSamplingProfile"].params.parse(params ?? {}), + ), + ), + "Memory.getBrowserSamplingProfile", + "command", + ), + getSamplingProfile: withCdpName( + async (params?: cdp.types.ts.Memory.GetSamplingProfileParams) => + commands["Memory.getSamplingProfile"].result.parse( + await send("Memory.getSamplingProfile", commands["Memory.getSamplingProfile"].params.parse(params ?? {})), + ), + "Memory.getSamplingProfile", + "command", + ), }, Network: { - setAcceptedEncodings: withCdpName(async (params?: unknown) => commands["Network.setAcceptedEncodings"].result.parse(await send("Network.setAcceptedEncodings", commands["Network.setAcceptedEncodings"].params.parse(params ?? {}))), "Network.setAcceptedEncodings", "command"), - clearAcceptedEncodingsOverride: withCdpName(async (params?: unknown) => commands["Network.clearAcceptedEncodingsOverride"].result.parse(await send("Network.clearAcceptedEncodingsOverride", commands["Network.clearAcceptedEncodingsOverride"].params.parse(params ?? {}))), "Network.clearAcceptedEncodingsOverride", "command"), - canClearBrowserCache: withCdpName(async (params?: unknown) => commands["Network.canClearBrowserCache"].result.parse(await send("Network.canClearBrowserCache", commands["Network.canClearBrowserCache"].params.parse(params ?? {}))), "Network.canClearBrowserCache", "command"), - canClearBrowserCookies: withCdpName(async (params?: unknown) => commands["Network.canClearBrowserCookies"].result.parse(await send("Network.canClearBrowserCookies", commands["Network.canClearBrowserCookies"].params.parse(params ?? {}))), "Network.canClearBrowserCookies", "command"), - canEmulateNetworkConditions: withCdpName(async (params?: unknown) => commands["Network.canEmulateNetworkConditions"].result.parse(await send("Network.canEmulateNetworkConditions", commands["Network.canEmulateNetworkConditions"].params.parse(params ?? {}))), "Network.canEmulateNetworkConditions", "command"), - clearBrowserCache: withCdpName(async (params?: unknown) => commands["Network.clearBrowserCache"].result.parse(await send("Network.clearBrowserCache", commands["Network.clearBrowserCache"].params.parse(params ?? {}))), "Network.clearBrowserCache", "command"), - clearBrowserCookies: withCdpName(async (params?: unknown) => commands["Network.clearBrowserCookies"].result.parse(await send("Network.clearBrowserCookies", commands["Network.clearBrowserCookies"].params.parse(params ?? {}))), "Network.clearBrowserCookies", "command"), - continueInterceptedRequest: withCdpName(async (params?: unknown) => commands["Network.continueInterceptedRequest"].result.parse(await send("Network.continueInterceptedRequest", commands["Network.continueInterceptedRequest"].params.parse(params ?? {}))), "Network.continueInterceptedRequest", "command"), - deleteCookies: withCdpName(async (params?: unknown) => commands["Network.deleteCookies"].result.parse(await send("Network.deleteCookies", commands["Network.deleteCookies"].params.parse(params ?? {}))), "Network.deleteCookies", "command"), - disable: withCdpName(async (params?: unknown) => commands["Network.disable"].result.parse(await send("Network.disable", commands["Network.disable"].params.parse(params ?? {}))), "Network.disable", "command"), - emulateNetworkConditions: withCdpName(async (params?: unknown) => commands["Network.emulateNetworkConditions"].result.parse(await send("Network.emulateNetworkConditions", commands["Network.emulateNetworkConditions"].params.parse(params ?? {}))), "Network.emulateNetworkConditions", "command"), - emulateNetworkConditionsByRule: withCdpName(async (params?: unknown) => commands["Network.emulateNetworkConditionsByRule"].result.parse(await send("Network.emulateNetworkConditionsByRule", commands["Network.emulateNetworkConditionsByRule"].params.parse(params ?? {}))), "Network.emulateNetworkConditionsByRule", "command"), - overrideNetworkState: withCdpName(async (params?: unknown) => commands["Network.overrideNetworkState"].result.parse(await send("Network.overrideNetworkState", commands["Network.overrideNetworkState"].params.parse(params ?? {}))), "Network.overrideNetworkState", "command"), - enable: withCdpName(async (params?: unknown) => commands["Network.enable"].result.parse(await send("Network.enable", commands["Network.enable"].params.parse(params ?? {}))), "Network.enable", "command"), - configureDurableMessages: withCdpName(async (params?: unknown) => commands["Network.configureDurableMessages"].result.parse(await send("Network.configureDurableMessages", commands["Network.configureDurableMessages"].params.parse(params ?? {}))), "Network.configureDurableMessages", "command"), - getAllCookies: withCdpName(async (params?: unknown) => commands["Network.getAllCookies"].result.parse(await send("Network.getAllCookies", commands["Network.getAllCookies"].params.parse(params ?? {}))), "Network.getAllCookies", "command"), - getCertificate: withCdpName(async (params?: unknown) => commands["Network.getCertificate"].result.parse(await send("Network.getCertificate", commands["Network.getCertificate"].params.parse(params ?? {}))), "Network.getCertificate", "command"), - getCookies: withCdpName(async (params?: unknown) => commands["Network.getCookies"].result.parse(await send("Network.getCookies", commands["Network.getCookies"].params.parse(params ?? {}))), "Network.getCookies", "command"), - getResponseBody: withCdpName(async (params?: unknown) => commands["Network.getResponseBody"].result.parse(await send("Network.getResponseBody", commands["Network.getResponseBody"].params.parse(params ?? {}))), "Network.getResponseBody", "command"), - getRequestPostData: withCdpName(async (params?: unknown) => commands["Network.getRequestPostData"].result.parse(await send("Network.getRequestPostData", commands["Network.getRequestPostData"].params.parse(params ?? {}))), "Network.getRequestPostData", "command"), - getResponseBodyForInterception: withCdpName(async (params?: unknown) => commands["Network.getResponseBodyForInterception"].result.parse(await send("Network.getResponseBodyForInterception", commands["Network.getResponseBodyForInterception"].params.parse(params ?? {}))), "Network.getResponseBodyForInterception", "command"), - takeResponseBodyForInterceptionAsStream: withCdpName(async (params?: unknown) => commands["Network.takeResponseBodyForInterceptionAsStream"].result.parse(await send("Network.takeResponseBodyForInterceptionAsStream", commands["Network.takeResponseBodyForInterceptionAsStream"].params.parse(params ?? {}))), "Network.takeResponseBodyForInterceptionAsStream", "command"), - replayXHR: withCdpName(async (params?: unknown) => commands["Network.replayXHR"].result.parse(await send("Network.replayXHR", commands["Network.replayXHR"].params.parse(params ?? {}))), "Network.replayXHR", "command"), - searchInResponseBody: withCdpName(async (params?: unknown) => commands["Network.searchInResponseBody"].result.parse(await send("Network.searchInResponseBody", commands["Network.searchInResponseBody"].params.parse(params ?? {}))), "Network.searchInResponseBody", "command"), - setBlockedURLs: withCdpName(async (params?: unknown) => commands["Network.setBlockedURLs"].result.parse(await send("Network.setBlockedURLs", commands["Network.setBlockedURLs"].params.parse(params ?? {}))), "Network.setBlockedURLs", "command"), - setBypassServiceWorker: withCdpName(async (params?: unknown) => commands["Network.setBypassServiceWorker"].result.parse(await send("Network.setBypassServiceWorker", commands["Network.setBypassServiceWorker"].params.parse(params ?? {}))), "Network.setBypassServiceWorker", "command"), - setCacheDisabled: withCdpName(async (params?: unknown) => commands["Network.setCacheDisabled"].result.parse(await send("Network.setCacheDisabled", commands["Network.setCacheDisabled"].params.parse(params ?? {}))), "Network.setCacheDisabled", "command"), - setCookie: withCdpName(async (params?: unknown) => commands["Network.setCookie"].result.parse(await send("Network.setCookie", commands["Network.setCookie"].params.parse(params ?? {}))), "Network.setCookie", "command"), - setCookies: withCdpName(async (params?: unknown) => commands["Network.setCookies"].result.parse(await send("Network.setCookies", commands["Network.setCookies"].params.parse(params ?? {}))), "Network.setCookies", "command"), - setExtraHTTPHeaders: withCdpName(async (params?: unknown) => commands["Network.setExtraHTTPHeaders"].result.parse(await send("Network.setExtraHTTPHeaders", commands["Network.setExtraHTTPHeaders"].params.parse(params ?? {}))), "Network.setExtraHTTPHeaders", "command"), - setAttachDebugStack: withCdpName(async (params?: unknown) => commands["Network.setAttachDebugStack"].result.parse(await send("Network.setAttachDebugStack", commands["Network.setAttachDebugStack"].params.parse(params ?? {}))), "Network.setAttachDebugStack", "command"), - setRequestInterception: withCdpName(async (params?: unknown) => commands["Network.setRequestInterception"].result.parse(await send("Network.setRequestInterception", commands["Network.setRequestInterception"].params.parse(params ?? {}))), "Network.setRequestInterception", "command"), - setUserAgentOverride: withCdpName(async (params?: unknown) => commands["Network.setUserAgentOverride"].result.parse(await send("Network.setUserAgentOverride", commands["Network.setUserAgentOverride"].params.parse(params ?? {}))), "Network.setUserAgentOverride", "command"), - streamResourceContent: withCdpName(async (params?: unknown) => commands["Network.streamResourceContent"].result.parse(await send("Network.streamResourceContent", commands["Network.streamResourceContent"].params.parse(params ?? {}))), "Network.streamResourceContent", "command"), - getSecurityIsolationStatus: withCdpName(async (params?: unknown) => commands["Network.getSecurityIsolationStatus"].result.parse(await send("Network.getSecurityIsolationStatus", commands["Network.getSecurityIsolationStatus"].params.parse(params ?? {}))), "Network.getSecurityIsolationStatus", "command"), - enableReportingApi: withCdpName(async (params?: unknown) => commands["Network.enableReportingApi"].result.parse(await send("Network.enableReportingApi", commands["Network.enableReportingApi"].params.parse(params ?? {}))), "Network.enableReportingApi", "command"), - enableDeviceBoundSessions: withCdpName(async (params?: unknown) => commands["Network.enableDeviceBoundSessions"].result.parse(await send("Network.enableDeviceBoundSessions", commands["Network.enableDeviceBoundSessions"].params.parse(params ?? {}))), "Network.enableDeviceBoundSessions", "command"), - deleteDeviceBoundSession: withCdpName(async (params?: unknown) => commands["Network.deleteDeviceBoundSession"].result.parse(await send("Network.deleteDeviceBoundSession", commands["Network.deleteDeviceBoundSession"].params.parse(params ?? {}))), "Network.deleteDeviceBoundSession", "command"), - fetchSchemefulSite: withCdpName(async (params?: unknown) => commands["Network.fetchSchemefulSite"].result.parse(await send("Network.fetchSchemefulSite", commands["Network.fetchSchemefulSite"].params.parse(params ?? {}))), "Network.fetchSchemefulSite", "command"), - loadNetworkResource: withCdpName(async (params?: unknown) => commands["Network.loadNetworkResource"].result.parse(await send("Network.loadNetworkResource", commands["Network.loadNetworkResource"].params.parse(params ?? {}))), "Network.loadNetworkResource", "command"), - setCookieControls: withCdpName(async (params?: unknown) => commands["Network.setCookieControls"].result.parse(await send("Network.setCookieControls", commands["Network.setCookieControls"].params.parse(params ?? {}))), "Network.setCookieControls", "command"), - dataReceived: events["Network.dataReceived"] as CdpEventAlias, - eventSourceMessageReceived: events["Network.eventSourceMessageReceived"] as CdpEventAlias, - loadingFailed: events["Network.loadingFailed"] as CdpEventAlias, - loadingFinished: events["Network.loadingFinished"] as CdpEventAlias, - requestIntercepted: events["Network.requestIntercepted"] as CdpEventAlias, - requestServedFromCache: events["Network.requestServedFromCache"] as CdpEventAlias, - requestWillBeSent: events["Network.requestWillBeSent"] as CdpEventAlias, - resourceChangedPriority: events["Network.resourceChangedPriority"] as CdpEventAlias, - signedExchangeReceived: events["Network.signedExchangeReceived"] as CdpEventAlias, - responseReceived: events["Network.responseReceived"] as CdpEventAlias, - webSocketClosed: events["Network.webSocketClosed"] as CdpEventAlias, - webSocketCreated: events["Network.webSocketCreated"] as CdpEventAlias, - webSocketFrameError: events["Network.webSocketFrameError"] as CdpEventAlias, - webSocketFrameReceived: events["Network.webSocketFrameReceived"] as CdpEventAlias, - webSocketFrameSent: events["Network.webSocketFrameSent"] as CdpEventAlias, - webSocketHandshakeResponseReceived: events["Network.webSocketHandshakeResponseReceived"] as CdpEventAlias, - webSocketWillSendHandshakeRequest: events["Network.webSocketWillSendHandshakeRequest"] as CdpEventAlias, - webTransportCreated: events["Network.webTransportCreated"] as CdpEventAlias, - webTransportConnectionEstablished: events["Network.webTransportConnectionEstablished"] as CdpEventAlias, - webTransportClosed: events["Network.webTransportClosed"] as CdpEventAlias, - directTCPSocketCreated: events["Network.directTCPSocketCreated"] as CdpEventAlias, - directTCPSocketOpened: events["Network.directTCPSocketOpened"] as CdpEventAlias, - directTCPSocketAborted: events["Network.directTCPSocketAborted"] as CdpEventAlias, - directTCPSocketClosed: events["Network.directTCPSocketClosed"] as CdpEventAlias, - directTCPSocketChunkSent: events["Network.directTCPSocketChunkSent"] as CdpEventAlias, - directTCPSocketChunkReceived: events["Network.directTCPSocketChunkReceived"] as CdpEventAlias, - directUDPSocketJoinedMulticastGroup: events["Network.directUDPSocketJoinedMulticastGroup"] as CdpEventAlias, - directUDPSocketLeftMulticastGroup: events["Network.directUDPSocketLeftMulticastGroup"] as CdpEventAlias, - directUDPSocketCreated: events["Network.directUDPSocketCreated"] as CdpEventAlias, - directUDPSocketOpened: events["Network.directUDPSocketOpened"] as CdpEventAlias, - directUDPSocketAborted: events["Network.directUDPSocketAborted"] as CdpEventAlias, - directUDPSocketClosed: events["Network.directUDPSocketClosed"] as CdpEventAlias, - directUDPSocketChunkSent: events["Network.directUDPSocketChunkSent"] as CdpEventAlias, - directUDPSocketChunkReceived: events["Network.directUDPSocketChunkReceived"] as CdpEventAlias, - requestWillBeSentExtraInfo: events["Network.requestWillBeSentExtraInfo"] as CdpEventAlias, - responseReceivedExtraInfo: events["Network.responseReceivedExtraInfo"] as CdpEventAlias, - responseReceivedEarlyHints: events["Network.responseReceivedEarlyHints"] as CdpEventAlias, - trustTokenOperationDone: events["Network.trustTokenOperationDone"] as CdpEventAlias, - policyUpdated: events["Network.policyUpdated"] as CdpEventAlias, - reportingApiReportAdded: events["Network.reportingApiReportAdded"] as CdpEventAlias, - reportingApiReportUpdated: events["Network.reportingApiReportUpdated"] as CdpEventAlias, - reportingApiEndpointsChangedForOrigin: events["Network.reportingApiEndpointsChangedForOrigin"] as CdpEventAlias, - deviceBoundSessionsAdded: events["Network.deviceBoundSessionsAdded"] as CdpEventAlias, - deviceBoundSessionEventOccurred: events["Network.deviceBoundSessionEventOccurred"] as CdpEventAlias, + setAcceptedEncodings: withCdpName( + async (params?: cdp.types.ts.Network.SetAcceptedEncodingsParams) => + commands["Network.setAcceptedEncodings"].result.parse( + await send( + "Network.setAcceptedEncodings", + commands["Network.setAcceptedEncodings"].params.parse(params ?? {}), + ), + ), + "Network.setAcceptedEncodings", + "command", + ), + clearAcceptedEncodingsOverride: withCdpName( + async (params?: cdp.types.ts.Network.ClearAcceptedEncodingsOverrideParams) => + commands["Network.clearAcceptedEncodingsOverride"].result.parse( + await send( + "Network.clearAcceptedEncodingsOverride", + commands["Network.clearAcceptedEncodingsOverride"].params.parse(params ?? {}), + ), + ), + "Network.clearAcceptedEncodingsOverride", + "command", + ), + canClearBrowserCache: withCdpName( + async (params?: cdp.types.ts.Network.CanClearBrowserCacheParams) => + commands["Network.canClearBrowserCache"].result.parse( + await send( + "Network.canClearBrowserCache", + commands["Network.canClearBrowserCache"].params.parse(params ?? {}), + ), + ), + "Network.canClearBrowserCache", + "command", + ), + canClearBrowserCookies: withCdpName( + async (params?: cdp.types.ts.Network.CanClearBrowserCookiesParams) => + commands["Network.canClearBrowserCookies"].result.parse( + await send( + "Network.canClearBrowserCookies", + commands["Network.canClearBrowserCookies"].params.parse(params ?? {}), + ), + ), + "Network.canClearBrowserCookies", + "command", + ), + canEmulateNetworkConditions: withCdpName( + async (params?: cdp.types.ts.Network.CanEmulateNetworkConditionsParams) => + commands["Network.canEmulateNetworkConditions"].result.parse( + await send( + "Network.canEmulateNetworkConditions", + commands["Network.canEmulateNetworkConditions"].params.parse(params ?? {}), + ), + ), + "Network.canEmulateNetworkConditions", + "command", + ), + clearBrowserCache: withCdpName( + async (params?: cdp.types.ts.Network.ClearBrowserCacheParams) => + commands["Network.clearBrowserCache"].result.parse( + await send("Network.clearBrowserCache", commands["Network.clearBrowserCache"].params.parse(params ?? {})), + ), + "Network.clearBrowserCache", + "command", + ), + clearBrowserCookies: withCdpName( + async (params?: cdp.types.ts.Network.ClearBrowserCookiesParams) => + commands["Network.clearBrowserCookies"].result.parse( + await send( + "Network.clearBrowserCookies", + commands["Network.clearBrowserCookies"].params.parse(params ?? {}), + ), + ), + "Network.clearBrowserCookies", + "command", + ), + continueInterceptedRequest: withCdpName( + async (params?: cdp.types.ts.Network.ContinueInterceptedRequestParams) => + commands["Network.continueInterceptedRequest"].result.parse( + await send( + "Network.continueInterceptedRequest", + commands["Network.continueInterceptedRequest"].params.parse(params ?? {}), + ), + ), + "Network.continueInterceptedRequest", + "command", + ), + deleteCookies: withCdpName( + async (params?: cdp.types.ts.Network.DeleteCookiesParams) => + commands["Network.deleteCookies"].result.parse( + await send("Network.deleteCookies", commands["Network.deleteCookies"].params.parse(params ?? {})), + ), + "Network.deleteCookies", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Network.DisableParams) => + commands["Network.disable"].result.parse( + await send("Network.disable", commands["Network.disable"].params.parse(params ?? {})), + ), + "Network.disable", + "command", + ), + emulateNetworkConditions: withCdpName( + async (params?: cdp.types.ts.Network.EmulateNetworkConditionsParams) => + commands["Network.emulateNetworkConditions"].result.parse( + await send( + "Network.emulateNetworkConditions", + commands["Network.emulateNetworkConditions"].params.parse(params ?? {}), + ), + ), + "Network.emulateNetworkConditions", + "command", + ), + emulateNetworkConditionsByRule: withCdpName( + async (params?: cdp.types.ts.Network.EmulateNetworkConditionsByRuleParams) => + commands["Network.emulateNetworkConditionsByRule"].result.parse( + await send( + "Network.emulateNetworkConditionsByRule", + commands["Network.emulateNetworkConditionsByRule"].params.parse(params ?? {}), + ), + ), + "Network.emulateNetworkConditionsByRule", + "command", + ), + overrideNetworkState: withCdpName( + async (params?: cdp.types.ts.Network.OverrideNetworkStateParams) => + commands["Network.overrideNetworkState"].result.parse( + await send( + "Network.overrideNetworkState", + commands["Network.overrideNetworkState"].params.parse(params ?? {}), + ), + ), + "Network.overrideNetworkState", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Network.EnableParams) => + commands["Network.enable"].result.parse( + await send("Network.enable", commands["Network.enable"].params.parse(params ?? {})), + ), + "Network.enable", + "command", + ), + configureDurableMessages: withCdpName( + async (params?: cdp.types.ts.Network.ConfigureDurableMessagesParams) => + commands["Network.configureDurableMessages"].result.parse( + await send( + "Network.configureDurableMessages", + commands["Network.configureDurableMessages"].params.parse(params ?? {}), + ), + ), + "Network.configureDurableMessages", + "command", + ), + getAllCookies: withCdpName( + async (params?: cdp.types.ts.Network.GetAllCookiesParams) => + commands["Network.getAllCookies"].result.parse( + await send("Network.getAllCookies", commands["Network.getAllCookies"].params.parse(params ?? {})), + ), + "Network.getAllCookies", + "command", + ), + getCertificate: withCdpName( + async (params?: cdp.types.ts.Network.GetCertificateParams) => + commands["Network.getCertificate"].result.parse( + await send("Network.getCertificate", commands["Network.getCertificate"].params.parse(params ?? {})), + ), + "Network.getCertificate", + "command", + ), + getCookies: withCdpName( + async (params?: cdp.types.ts.Network.GetCookiesParams) => + commands["Network.getCookies"].result.parse( + await send("Network.getCookies", commands["Network.getCookies"].params.parse(params ?? {})), + ), + "Network.getCookies", + "command", + ), + getResponseBody: withCdpName( + async (params?: cdp.types.ts.Network.GetResponseBodyParams) => + commands["Network.getResponseBody"].result.parse( + await send("Network.getResponseBody", commands["Network.getResponseBody"].params.parse(params ?? {})), + ), + "Network.getResponseBody", + "command", + ), + getRequestPostData: withCdpName( + async (params?: cdp.types.ts.Network.GetRequestPostDataParams) => + commands["Network.getRequestPostData"].result.parse( + await send("Network.getRequestPostData", commands["Network.getRequestPostData"].params.parse(params ?? {})), + ), + "Network.getRequestPostData", + "command", + ), + getResponseBodyForInterception: withCdpName( + async (params?: cdp.types.ts.Network.GetResponseBodyForInterceptionParams) => + commands["Network.getResponseBodyForInterception"].result.parse( + await send( + "Network.getResponseBodyForInterception", + commands["Network.getResponseBodyForInterception"].params.parse(params ?? {}), + ), + ), + "Network.getResponseBodyForInterception", + "command", + ), + takeResponseBodyForInterceptionAsStream: withCdpName( + async (params?: cdp.types.ts.Network.TakeResponseBodyForInterceptionAsStreamParams) => + commands["Network.takeResponseBodyForInterceptionAsStream"].result.parse( + await send( + "Network.takeResponseBodyForInterceptionAsStream", + commands["Network.takeResponseBodyForInterceptionAsStream"].params.parse(params ?? {}), + ), + ), + "Network.takeResponseBodyForInterceptionAsStream", + "command", + ), + replayXHR: withCdpName( + async (params?: cdp.types.ts.Network.ReplayXHRParams) => + commands["Network.replayXHR"].result.parse( + await send("Network.replayXHR", commands["Network.replayXHR"].params.parse(params ?? {})), + ), + "Network.replayXHR", + "command", + ), + searchInResponseBody: withCdpName( + async (params?: cdp.types.ts.Network.SearchInResponseBodyParams) => + commands["Network.searchInResponseBody"].result.parse( + await send( + "Network.searchInResponseBody", + commands["Network.searchInResponseBody"].params.parse(params ?? {}), + ), + ), + "Network.searchInResponseBody", + "command", + ), + setBlockedURLs: withCdpName( + async (params?: cdp.types.ts.Network.SetBlockedURLsParams) => + commands["Network.setBlockedURLs"].result.parse( + await send("Network.setBlockedURLs", commands["Network.setBlockedURLs"].params.parse(params ?? {})), + ), + "Network.setBlockedURLs", + "command", + ), + setBypassServiceWorker: withCdpName( + async (params?: cdp.types.ts.Network.SetBypassServiceWorkerParams) => + commands["Network.setBypassServiceWorker"].result.parse( + await send( + "Network.setBypassServiceWorker", + commands["Network.setBypassServiceWorker"].params.parse(params ?? {}), + ), + ), + "Network.setBypassServiceWorker", + "command", + ), + setCacheDisabled: withCdpName( + async (params?: cdp.types.ts.Network.SetCacheDisabledParams) => + commands["Network.setCacheDisabled"].result.parse( + await send("Network.setCacheDisabled", commands["Network.setCacheDisabled"].params.parse(params ?? {})), + ), + "Network.setCacheDisabled", + "command", + ), + setCookie: withCdpName( + async (params?: cdp.types.ts.Network.SetCookieParams) => + commands["Network.setCookie"].result.parse( + await send("Network.setCookie", commands["Network.setCookie"].params.parse(params ?? {})), + ), + "Network.setCookie", + "command", + ), + setCookies: withCdpName( + async (params?: cdp.types.ts.Network.SetCookiesParams) => + commands["Network.setCookies"].result.parse( + await send("Network.setCookies", commands["Network.setCookies"].params.parse(params ?? {})), + ), + "Network.setCookies", + "command", + ), + setExtraHTTPHeaders: withCdpName( + async (params?: cdp.types.ts.Network.SetExtraHTTPHeadersParams) => + commands["Network.setExtraHTTPHeaders"].result.parse( + await send( + "Network.setExtraHTTPHeaders", + commands["Network.setExtraHTTPHeaders"].params.parse(params ?? {}), + ), + ), + "Network.setExtraHTTPHeaders", + "command", + ), + setAttachDebugStack: withCdpName( + async (params?: cdp.types.ts.Network.SetAttachDebugStackParams) => + commands["Network.setAttachDebugStack"].result.parse( + await send( + "Network.setAttachDebugStack", + commands["Network.setAttachDebugStack"].params.parse(params ?? {}), + ), + ), + "Network.setAttachDebugStack", + "command", + ), + setRequestInterception: withCdpName( + async (params?: cdp.types.ts.Network.SetRequestInterceptionParams) => + commands["Network.setRequestInterception"].result.parse( + await send( + "Network.setRequestInterception", + commands["Network.setRequestInterception"].params.parse(params ?? {}), + ), + ), + "Network.setRequestInterception", + "command", + ), + setUserAgentOverride: withCdpName( + async (params?: cdp.types.ts.Network.SetUserAgentOverrideParams) => + commands["Network.setUserAgentOverride"].result.parse( + await send( + "Network.setUserAgentOverride", + commands["Network.setUserAgentOverride"].params.parse(params ?? {}), + ), + ), + "Network.setUserAgentOverride", + "command", + ), + streamResourceContent: withCdpName( + async (params?: cdp.types.ts.Network.StreamResourceContentParams) => + commands["Network.streamResourceContent"].result.parse( + await send( + "Network.streamResourceContent", + commands["Network.streamResourceContent"].params.parse(params ?? {}), + ), + ), + "Network.streamResourceContent", + "command", + ), + getSecurityIsolationStatus: withCdpName( + async (params?: cdp.types.ts.Network.GetSecurityIsolationStatusParams) => + commands["Network.getSecurityIsolationStatus"].result.parse( + await send( + "Network.getSecurityIsolationStatus", + commands["Network.getSecurityIsolationStatus"].params.parse(params ?? {}), + ), + ), + "Network.getSecurityIsolationStatus", + "command", + ), + enableReportingApi: withCdpName( + async (params?: cdp.types.ts.Network.EnableReportingApiParams) => + commands["Network.enableReportingApi"].result.parse( + await send("Network.enableReportingApi", commands["Network.enableReportingApi"].params.parse(params ?? {})), + ), + "Network.enableReportingApi", + "command", + ), + enableDeviceBoundSessions: withCdpName( + async (params?: cdp.types.ts.Network.EnableDeviceBoundSessionsParams) => + commands["Network.enableDeviceBoundSessions"].result.parse( + await send( + "Network.enableDeviceBoundSessions", + commands["Network.enableDeviceBoundSessions"].params.parse(params ?? {}), + ), + ), + "Network.enableDeviceBoundSessions", + "command", + ), + deleteDeviceBoundSession: withCdpName( + async (params?: cdp.types.ts.Network.DeleteDeviceBoundSessionParams) => + commands["Network.deleteDeviceBoundSession"].result.parse( + await send( + "Network.deleteDeviceBoundSession", + commands["Network.deleteDeviceBoundSession"].params.parse(params ?? {}), + ), + ), + "Network.deleteDeviceBoundSession", + "command", + ), + fetchSchemefulSite: withCdpName( + async (params?: cdp.types.ts.Network.FetchSchemefulSiteParams) => + commands["Network.fetchSchemefulSite"].result.parse( + await send("Network.fetchSchemefulSite", commands["Network.fetchSchemefulSite"].params.parse(params ?? {})), + ), + "Network.fetchSchemefulSite", + "command", + ), + loadNetworkResource: withCdpName( + async (params?: cdp.types.ts.Network.LoadNetworkResourceParams) => + commands["Network.loadNetworkResource"].result.parse( + await send( + "Network.loadNetworkResource", + commands["Network.loadNetworkResource"].params.parse(params ?? {}), + ), + ), + "Network.loadNetworkResource", + "command", + ), + setCookieControls: withCdpName( + async (params?: cdp.types.ts.Network.SetCookieControlsParams) => + commands["Network.setCookieControls"].result.parse( + await send("Network.setCookieControls", commands["Network.setCookieControls"].params.parse(params ?? {})), + ), + "Network.setCookieControls", + "command", + ), + dataReceived: withCdpName(events["Network.dataReceived"], "Network.dataReceived", "event"), + eventSourceMessageReceived: withCdpName( + events["Network.eventSourceMessageReceived"], + "Network.eventSourceMessageReceived", + "event", + ), + loadingFailed: withCdpName(events["Network.loadingFailed"], "Network.loadingFailed", "event"), + loadingFinished: withCdpName(events["Network.loadingFinished"], "Network.loadingFinished", "event"), + requestIntercepted: withCdpName(events["Network.requestIntercepted"], "Network.requestIntercepted", "event"), + requestServedFromCache: withCdpName( + events["Network.requestServedFromCache"], + "Network.requestServedFromCache", + "event", + ), + requestWillBeSent: withCdpName(events["Network.requestWillBeSent"], "Network.requestWillBeSent", "event"), + resourceChangedPriority: withCdpName( + events["Network.resourceChangedPriority"], + "Network.resourceChangedPriority", + "event", + ), + signedExchangeReceived: withCdpName( + events["Network.signedExchangeReceived"], + "Network.signedExchangeReceived", + "event", + ), + responseReceived: withCdpName(events["Network.responseReceived"], "Network.responseReceived", "event"), + webSocketClosed: withCdpName(events["Network.webSocketClosed"], "Network.webSocketClosed", "event"), + webSocketCreated: withCdpName(events["Network.webSocketCreated"], "Network.webSocketCreated", "event"), + webSocketFrameError: withCdpName(events["Network.webSocketFrameError"], "Network.webSocketFrameError", "event"), + webSocketFrameReceived: withCdpName( + events["Network.webSocketFrameReceived"], + "Network.webSocketFrameReceived", + "event", + ), + webSocketFrameSent: withCdpName(events["Network.webSocketFrameSent"], "Network.webSocketFrameSent", "event"), + webSocketHandshakeResponseReceived: withCdpName( + events["Network.webSocketHandshakeResponseReceived"], + "Network.webSocketHandshakeResponseReceived", + "event", + ), + webSocketWillSendHandshakeRequest: withCdpName( + events["Network.webSocketWillSendHandshakeRequest"], + "Network.webSocketWillSendHandshakeRequest", + "event", + ), + webTransportCreated: withCdpName(events["Network.webTransportCreated"], "Network.webTransportCreated", "event"), + webTransportConnectionEstablished: withCdpName( + events["Network.webTransportConnectionEstablished"], + "Network.webTransportConnectionEstablished", + "event", + ), + webTransportClosed: withCdpName(events["Network.webTransportClosed"], "Network.webTransportClosed", "event"), + directTCPSocketCreated: withCdpName( + events["Network.directTCPSocketCreated"], + "Network.directTCPSocketCreated", + "event", + ), + directTCPSocketOpened: withCdpName( + events["Network.directTCPSocketOpened"], + "Network.directTCPSocketOpened", + "event", + ), + directTCPSocketAborted: withCdpName( + events["Network.directTCPSocketAborted"], + "Network.directTCPSocketAborted", + "event", + ), + directTCPSocketClosed: withCdpName( + events["Network.directTCPSocketClosed"], + "Network.directTCPSocketClosed", + "event", + ), + directTCPSocketChunkSent: withCdpName( + events["Network.directTCPSocketChunkSent"], + "Network.directTCPSocketChunkSent", + "event", + ), + directTCPSocketChunkReceived: withCdpName( + events["Network.directTCPSocketChunkReceived"], + "Network.directTCPSocketChunkReceived", + "event", + ), + directUDPSocketJoinedMulticastGroup: withCdpName( + events["Network.directUDPSocketJoinedMulticastGroup"], + "Network.directUDPSocketJoinedMulticastGroup", + "event", + ), + directUDPSocketLeftMulticastGroup: withCdpName( + events["Network.directUDPSocketLeftMulticastGroup"], + "Network.directUDPSocketLeftMulticastGroup", + "event", + ), + directUDPSocketCreated: withCdpName( + events["Network.directUDPSocketCreated"], + "Network.directUDPSocketCreated", + "event", + ), + directUDPSocketOpened: withCdpName( + events["Network.directUDPSocketOpened"], + "Network.directUDPSocketOpened", + "event", + ), + directUDPSocketAborted: withCdpName( + events["Network.directUDPSocketAborted"], + "Network.directUDPSocketAborted", + "event", + ), + directUDPSocketClosed: withCdpName( + events["Network.directUDPSocketClosed"], + "Network.directUDPSocketClosed", + "event", + ), + directUDPSocketChunkSent: withCdpName( + events["Network.directUDPSocketChunkSent"], + "Network.directUDPSocketChunkSent", + "event", + ), + directUDPSocketChunkReceived: withCdpName( + events["Network.directUDPSocketChunkReceived"], + "Network.directUDPSocketChunkReceived", + "event", + ), + requestWillBeSentExtraInfo: withCdpName( + events["Network.requestWillBeSentExtraInfo"], + "Network.requestWillBeSentExtraInfo", + "event", + ), + responseReceivedExtraInfo: withCdpName( + events["Network.responseReceivedExtraInfo"], + "Network.responseReceivedExtraInfo", + "event", + ), + responseReceivedEarlyHints: withCdpName( + events["Network.responseReceivedEarlyHints"], + "Network.responseReceivedEarlyHints", + "event", + ), + trustTokenOperationDone: withCdpName( + events["Network.trustTokenOperationDone"], + "Network.trustTokenOperationDone", + "event", + ), + policyUpdated: withCdpName(events["Network.policyUpdated"], "Network.policyUpdated", "event"), + reportingApiReportAdded: withCdpName( + events["Network.reportingApiReportAdded"], + "Network.reportingApiReportAdded", + "event", + ), + reportingApiReportUpdated: withCdpName( + events["Network.reportingApiReportUpdated"], + "Network.reportingApiReportUpdated", + "event", + ), + reportingApiEndpointsChangedForOrigin: withCdpName( + events["Network.reportingApiEndpointsChangedForOrigin"], + "Network.reportingApiEndpointsChangedForOrigin", + "event", + ), + deviceBoundSessionsAdded: withCdpName( + events["Network.deviceBoundSessionsAdded"], + "Network.deviceBoundSessionsAdded", + "event", + ), + deviceBoundSessionEventOccurred: withCdpName( + events["Network.deviceBoundSessionEventOccurred"], + "Network.deviceBoundSessionEventOccurred", + "event", + ), }, Overlay: { - disable: withCdpName(async (params?: unknown) => commands["Overlay.disable"].result.parse(await send("Overlay.disable", commands["Overlay.disable"].params.parse(params ?? {}))), "Overlay.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Overlay.enable"].result.parse(await send("Overlay.enable", commands["Overlay.enable"].params.parse(params ?? {}))), "Overlay.enable", "command"), - getHighlightObjectForTest: withCdpName(async (params?: unknown) => commands["Overlay.getHighlightObjectForTest"].result.parse(await send("Overlay.getHighlightObjectForTest", commands["Overlay.getHighlightObjectForTest"].params.parse(params ?? {}))), "Overlay.getHighlightObjectForTest", "command"), - getGridHighlightObjectsForTest: withCdpName(async (params?: unknown) => commands["Overlay.getGridHighlightObjectsForTest"].result.parse(await send("Overlay.getGridHighlightObjectsForTest", commands["Overlay.getGridHighlightObjectsForTest"].params.parse(params ?? {}))), "Overlay.getGridHighlightObjectsForTest", "command"), - getSourceOrderHighlightObjectForTest: withCdpName(async (params?: unknown) => commands["Overlay.getSourceOrderHighlightObjectForTest"].result.parse(await send("Overlay.getSourceOrderHighlightObjectForTest", commands["Overlay.getSourceOrderHighlightObjectForTest"].params.parse(params ?? {}))), "Overlay.getSourceOrderHighlightObjectForTest", "command"), - hideHighlight: withCdpName(async (params?: unknown) => commands["Overlay.hideHighlight"].result.parse(await send("Overlay.hideHighlight", commands["Overlay.hideHighlight"].params.parse(params ?? {}))), "Overlay.hideHighlight", "command"), - highlightFrame: withCdpName(async (params?: unknown) => commands["Overlay.highlightFrame"].result.parse(await send("Overlay.highlightFrame", commands["Overlay.highlightFrame"].params.parse(params ?? {}))), "Overlay.highlightFrame", "command"), - highlightNode: withCdpName(async (params?: unknown) => commands["Overlay.highlightNode"].result.parse(await send("Overlay.highlightNode", commands["Overlay.highlightNode"].params.parse(params ?? {}))), "Overlay.highlightNode", "command"), - highlightQuad: withCdpName(async (params?: unknown) => commands["Overlay.highlightQuad"].result.parse(await send("Overlay.highlightQuad", commands["Overlay.highlightQuad"].params.parse(params ?? {}))), "Overlay.highlightQuad", "command"), - highlightRect: withCdpName(async (params?: unknown) => commands["Overlay.highlightRect"].result.parse(await send("Overlay.highlightRect", commands["Overlay.highlightRect"].params.parse(params ?? {}))), "Overlay.highlightRect", "command"), - highlightSourceOrder: withCdpName(async (params?: unknown) => commands["Overlay.highlightSourceOrder"].result.parse(await send("Overlay.highlightSourceOrder", commands["Overlay.highlightSourceOrder"].params.parse(params ?? {}))), "Overlay.highlightSourceOrder", "command"), - setInspectMode: withCdpName(async (params?: unknown) => commands["Overlay.setInspectMode"].result.parse(await send("Overlay.setInspectMode", commands["Overlay.setInspectMode"].params.parse(params ?? {}))), "Overlay.setInspectMode", "command"), - setShowAdHighlights: withCdpName(async (params?: unknown) => commands["Overlay.setShowAdHighlights"].result.parse(await send("Overlay.setShowAdHighlights", commands["Overlay.setShowAdHighlights"].params.parse(params ?? {}))), "Overlay.setShowAdHighlights", "command"), - setPausedInDebuggerMessage: withCdpName(async (params?: unknown) => commands["Overlay.setPausedInDebuggerMessage"].result.parse(await send("Overlay.setPausedInDebuggerMessage", commands["Overlay.setPausedInDebuggerMessage"].params.parse(params ?? {}))), "Overlay.setPausedInDebuggerMessage", "command"), - setShowDebugBorders: withCdpName(async (params?: unknown) => commands["Overlay.setShowDebugBorders"].result.parse(await send("Overlay.setShowDebugBorders", commands["Overlay.setShowDebugBorders"].params.parse(params ?? {}))), "Overlay.setShowDebugBorders", "command"), - setShowFPSCounter: withCdpName(async (params?: unknown) => commands["Overlay.setShowFPSCounter"].result.parse(await send("Overlay.setShowFPSCounter", commands["Overlay.setShowFPSCounter"].params.parse(params ?? {}))), "Overlay.setShowFPSCounter", "command"), - setShowGridOverlays: withCdpName(async (params?: unknown) => commands["Overlay.setShowGridOverlays"].result.parse(await send("Overlay.setShowGridOverlays", commands["Overlay.setShowGridOverlays"].params.parse(params ?? {}))), "Overlay.setShowGridOverlays", "command"), - setShowFlexOverlays: withCdpName(async (params?: unknown) => commands["Overlay.setShowFlexOverlays"].result.parse(await send("Overlay.setShowFlexOverlays", commands["Overlay.setShowFlexOverlays"].params.parse(params ?? {}))), "Overlay.setShowFlexOverlays", "command"), - setShowScrollSnapOverlays: withCdpName(async (params?: unknown) => commands["Overlay.setShowScrollSnapOverlays"].result.parse(await send("Overlay.setShowScrollSnapOverlays", commands["Overlay.setShowScrollSnapOverlays"].params.parse(params ?? {}))), "Overlay.setShowScrollSnapOverlays", "command"), - setShowContainerQueryOverlays: withCdpName(async (params?: unknown) => commands["Overlay.setShowContainerQueryOverlays"].result.parse(await send("Overlay.setShowContainerQueryOverlays", commands["Overlay.setShowContainerQueryOverlays"].params.parse(params ?? {}))), "Overlay.setShowContainerQueryOverlays", "command"), - setShowInspectedElementAnchor: withCdpName(async (params?: unknown) => commands["Overlay.setShowInspectedElementAnchor"].result.parse(await send("Overlay.setShowInspectedElementAnchor", commands["Overlay.setShowInspectedElementAnchor"].params.parse(params ?? {}))), "Overlay.setShowInspectedElementAnchor", "command"), - setShowPaintRects: withCdpName(async (params?: unknown) => commands["Overlay.setShowPaintRects"].result.parse(await send("Overlay.setShowPaintRects", commands["Overlay.setShowPaintRects"].params.parse(params ?? {}))), "Overlay.setShowPaintRects", "command"), - setShowLayoutShiftRegions: withCdpName(async (params?: unknown) => commands["Overlay.setShowLayoutShiftRegions"].result.parse(await send("Overlay.setShowLayoutShiftRegions", commands["Overlay.setShowLayoutShiftRegions"].params.parse(params ?? {}))), "Overlay.setShowLayoutShiftRegions", "command"), - setShowScrollBottleneckRects: withCdpName(async (params?: unknown) => commands["Overlay.setShowScrollBottleneckRects"].result.parse(await send("Overlay.setShowScrollBottleneckRects", commands["Overlay.setShowScrollBottleneckRects"].params.parse(params ?? {}))), "Overlay.setShowScrollBottleneckRects", "command"), - setShowHitTestBorders: withCdpName(async (params?: unknown) => commands["Overlay.setShowHitTestBorders"].result.parse(await send("Overlay.setShowHitTestBorders", commands["Overlay.setShowHitTestBorders"].params.parse(params ?? {}))), "Overlay.setShowHitTestBorders", "command"), - setShowWebVitals: withCdpName(async (params?: unknown) => commands["Overlay.setShowWebVitals"].result.parse(await send("Overlay.setShowWebVitals", commands["Overlay.setShowWebVitals"].params.parse(params ?? {}))), "Overlay.setShowWebVitals", "command"), - setShowViewportSizeOnResize: withCdpName(async (params?: unknown) => commands["Overlay.setShowViewportSizeOnResize"].result.parse(await send("Overlay.setShowViewportSizeOnResize", commands["Overlay.setShowViewportSizeOnResize"].params.parse(params ?? {}))), "Overlay.setShowViewportSizeOnResize", "command"), - setShowHinge: withCdpName(async (params?: unknown) => commands["Overlay.setShowHinge"].result.parse(await send("Overlay.setShowHinge", commands["Overlay.setShowHinge"].params.parse(params ?? {}))), "Overlay.setShowHinge", "command"), - setShowIsolatedElements: withCdpName(async (params?: unknown) => commands["Overlay.setShowIsolatedElements"].result.parse(await send("Overlay.setShowIsolatedElements", commands["Overlay.setShowIsolatedElements"].params.parse(params ?? {}))), "Overlay.setShowIsolatedElements", "command"), - setShowWindowControlsOverlay: withCdpName(async (params?: unknown) => commands["Overlay.setShowWindowControlsOverlay"].result.parse(await send("Overlay.setShowWindowControlsOverlay", commands["Overlay.setShowWindowControlsOverlay"].params.parse(params ?? {}))), "Overlay.setShowWindowControlsOverlay", "command"), - inspectNodeRequested: events["Overlay.inspectNodeRequested"] as CdpEventAlias, - nodeHighlightRequested: events["Overlay.nodeHighlightRequested"] as CdpEventAlias, - screenshotRequested: events["Overlay.screenshotRequested"] as CdpEventAlias, - inspectPanelShowRequested: events["Overlay.inspectPanelShowRequested"] as CdpEventAlias, - inspectedElementWindowRestored: events["Overlay.inspectedElementWindowRestored"] as CdpEventAlias, - inspectModeCanceled: events["Overlay.inspectModeCanceled"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Overlay.DisableParams) => + commands["Overlay.disable"].result.parse( + await send("Overlay.disable", commands["Overlay.disable"].params.parse(params ?? {})), + ), + "Overlay.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Overlay.EnableParams) => + commands["Overlay.enable"].result.parse( + await send("Overlay.enable", commands["Overlay.enable"].params.parse(params ?? {})), + ), + "Overlay.enable", + "command", + ), + getHighlightObjectForTest: withCdpName( + async (params?: cdp.types.ts.Overlay.GetHighlightObjectForTestParams) => + commands["Overlay.getHighlightObjectForTest"].result.parse( + await send( + "Overlay.getHighlightObjectForTest", + commands["Overlay.getHighlightObjectForTest"].params.parse(params ?? {}), + ), + ), + "Overlay.getHighlightObjectForTest", + "command", + ), + getGridHighlightObjectsForTest: withCdpName( + async (params?: cdp.types.ts.Overlay.GetGridHighlightObjectsForTestParams) => + commands["Overlay.getGridHighlightObjectsForTest"].result.parse( + await send( + "Overlay.getGridHighlightObjectsForTest", + commands["Overlay.getGridHighlightObjectsForTest"].params.parse(params ?? {}), + ), + ), + "Overlay.getGridHighlightObjectsForTest", + "command", + ), + getSourceOrderHighlightObjectForTest: withCdpName( + async (params?: cdp.types.ts.Overlay.GetSourceOrderHighlightObjectForTestParams) => + commands["Overlay.getSourceOrderHighlightObjectForTest"].result.parse( + await send( + "Overlay.getSourceOrderHighlightObjectForTest", + commands["Overlay.getSourceOrderHighlightObjectForTest"].params.parse(params ?? {}), + ), + ), + "Overlay.getSourceOrderHighlightObjectForTest", + "command", + ), + hideHighlight: withCdpName( + async (params?: cdp.types.ts.Overlay.HideHighlightParams) => + commands["Overlay.hideHighlight"].result.parse( + await send("Overlay.hideHighlight", commands["Overlay.hideHighlight"].params.parse(params ?? {})), + ), + "Overlay.hideHighlight", + "command", + ), + highlightFrame: withCdpName( + async (params?: cdp.types.ts.Overlay.HighlightFrameParams) => + commands["Overlay.highlightFrame"].result.parse( + await send("Overlay.highlightFrame", commands["Overlay.highlightFrame"].params.parse(params ?? {})), + ), + "Overlay.highlightFrame", + "command", + ), + highlightNode: withCdpName( + async (params?: cdp.types.ts.Overlay.HighlightNodeParams) => + commands["Overlay.highlightNode"].result.parse( + await send("Overlay.highlightNode", commands["Overlay.highlightNode"].params.parse(params ?? {})), + ), + "Overlay.highlightNode", + "command", + ), + highlightQuad: withCdpName( + async (params?: cdp.types.ts.Overlay.HighlightQuadParams) => + commands["Overlay.highlightQuad"].result.parse( + await send("Overlay.highlightQuad", commands["Overlay.highlightQuad"].params.parse(params ?? {})), + ), + "Overlay.highlightQuad", + "command", + ), + highlightRect: withCdpName( + async (params?: cdp.types.ts.Overlay.HighlightRectParams) => + commands["Overlay.highlightRect"].result.parse( + await send("Overlay.highlightRect", commands["Overlay.highlightRect"].params.parse(params ?? {})), + ), + "Overlay.highlightRect", + "command", + ), + highlightSourceOrder: withCdpName( + async (params?: cdp.types.ts.Overlay.HighlightSourceOrderParams) => + commands["Overlay.highlightSourceOrder"].result.parse( + await send( + "Overlay.highlightSourceOrder", + commands["Overlay.highlightSourceOrder"].params.parse(params ?? {}), + ), + ), + "Overlay.highlightSourceOrder", + "command", + ), + setInspectMode: withCdpName( + async (params?: cdp.types.ts.Overlay.SetInspectModeParams) => + commands["Overlay.setInspectMode"].result.parse( + await send("Overlay.setInspectMode", commands["Overlay.setInspectMode"].params.parse(params ?? {})), + ), + "Overlay.setInspectMode", + "command", + ), + setShowAdHighlights: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowAdHighlightsParams) => + commands["Overlay.setShowAdHighlights"].result.parse( + await send( + "Overlay.setShowAdHighlights", + commands["Overlay.setShowAdHighlights"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowAdHighlights", + "command", + ), + setPausedInDebuggerMessage: withCdpName( + async (params?: cdp.types.ts.Overlay.SetPausedInDebuggerMessageParams) => + commands["Overlay.setPausedInDebuggerMessage"].result.parse( + await send( + "Overlay.setPausedInDebuggerMessage", + commands["Overlay.setPausedInDebuggerMessage"].params.parse(params ?? {}), + ), + ), + "Overlay.setPausedInDebuggerMessage", + "command", + ), + setShowDebugBorders: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowDebugBordersParams) => + commands["Overlay.setShowDebugBorders"].result.parse( + await send( + "Overlay.setShowDebugBorders", + commands["Overlay.setShowDebugBorders"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowDebugBorders", + "command", + ), + setShowFPSCounter: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowFPSCounterParams) => + commands["Overlay.setShowFPSCounter"].result.parse( + await send("Overlay.setShowFPSCounter", commands["Overlay.setShowFPSCounter"].params.parse(params ?? {})), + ), + "Overlay.setShowFPSCounter", + "command", + ), + setShowGridOverlays: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowGridOverlaysParams) => + commands["Overlay.setShowGridOverlays"].result.parse( + await send( + "Overlay.setShowGridOverlays", + commands["Overlay.setShowGridOverlays"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowGridOverlays", + "command", + ), + setShowFlexOverlays: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowFlexOverlaysParams) => + commands["Overlay.setShowFlexOverlays"].result.parse( + await send( + "Overlay.setShowFlexOverlays", + commands["Overlay.setShowFlexOverlays"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowFlexOverlays", + "command", + ), + setShowScrollSnapOverlays: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowScrollSnapOverlaysParams) => + commands["Overlay.setShowScrollSnapOverlays"].result.parse( + await send( + "Overlay.setShowScrollSnapOverlays", + commands["Overlay.setShowScrollSnapOverlays"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowScrollSnapOverlays", + "command", + ), + setShowContainerQueryOverlays: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowContainerQueryOverlaysParams) => + commands["Overlay.setShowContainerQueryOverlays"].result.parse( + await send( + "Overlay.setShowContainerQueryOverlays", + commands["Overlay.setShowContainerQueryOverlays"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowContainerQueryOverlays", + "command", + ), + setShowInspectedElementAnchor: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowInspectedElementAnchorParams) => + commands["Overlay.setShowInspectedElementAnchor"].result.parse( + await send( + "Overlay.setShowInspectedElementAnchor", + commands["Overlay.setShowInspectedElementAnchor"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowInspectedElementAnchor", + "command", + ), + setShowPaintRects: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowPaintRectsParams) => + commands["Overlay.setShowPaintRects"].result.parse( + await send("Overlay.setShowPaintRects", commands["Overlay.setShowPaintRects"].params.parse(params ?? {})), + ), + "Overlay.setShowPaintRects", + "command", + ), + setShowLayoutShiftRegions: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowLayoutShiftRegionsParams) => + commands["Overlay.setShowLayoutShiftRegions"].result.parse( + await send( + "Overlay.setShowLayoutShiftRegions", + commands["Overlay.setShowLayoutShiftRegions"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowLayoutShiftRegions", + "command", + ), + setShowScrollBottleneckRects: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowScrollBottleneckRectsParams) => + commands["Overlay.setShowScrollBottleneckRects"].result.parse( + await send( + "Overlay.setShowScrollBottleneckRects", + commands["Overlay.setShowScrollBottleneckRects"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowScrollBottleneckRects", + "command", + ), + setShowHitTestBorders: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowHitTestBordersParams) => + commands["Overlay.setShowHitTestBorders"].result.parse( + await send( + "Overlay.setShowHitTestBorders", + commands["Overlay.setShowHitTestBorders"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowHitTestBorders", + "command", + ), + setShowWebVitals: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowWebVitalsParams) => + commands["Overlay.setShowWebVitals"].result.parse( + await send("Overlay.setShowWebVitals", commands["Overlay.setShowWebVitals"].params.parse(params ?? {})), + ), + "Overlay.setShowWebVitals", + "command", + ), + setShowViewportSizeOnResize: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowViewportSizeOnResizeParams) => + commands["Overlay.setShowViewportSizeOnResize"].result.parse( + await send( + "Overlay.setShowViewportSizeOnResize", + commands["Overlay.setShowViewportSizeOnResize"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowViewportSizeOnResize", + "command", + ), + setShowHinge: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowHingeParams) => + commands["Overlay.setShowHinge"].result.parse( + await send("Overlay.setShowHinge", commands["Overlay.setShowHinge"].params.parse(params ?? {})), + ), + "Overlay.setShowHinge", + "command", + ), + setShowIsolatedElements: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowIsolatedElementsParams) => + commands["Overlay.setShowIsolatedElements"].result.parse( + await send( + "Overlay.setShowIsolatedElements", + commands["Overlay.setShowIsolatedElements"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowIsolatedElements", + "command", + ), + setShowWindowControlsOverlay: withCdpName( + async (params?: cdp.types.ts.Overlay.SetShowWindowControlsOverlayParams) => + commands["Overlay.setShowWindowControlsOverlay"].result.parse( + await send( + "Overlay.setShowWindowControlsOverlay", + commands["Overlay.setShowWindowControlsOverlay"].params.parse(params ?? {}), + ), + ), + "Overlay.setShowWindowControlsOverlay", + "command", + ), + inspectNodeRequested: withCdpName( + events["Overlay.inspectNodeRequested"], + "Overlay.inspectNodeRequested", + "event", + ), + nodeHighlightRequested: withCdpName( + events["Overlay.nodeHighlightRequested"], + "Overlay.nodeHighlightRequested", + "event", + ), + screenshotRequested: withCdpName(events["Overlay.screenshotRequested"], "Overlay.screenshotRequested", "event"), + inspectPanelShowRequested: withCdpName( + events["Overlay.inspectPanelShowRequested"], + "Overlay.inspectPanelShowRequested", + "event", + ), + inspectedElementWindowRestored: withCdpName( + events["Overlay.inspectedElementWindowRestored"], + "Overlay.inspectedElementWindowRestored", + "event", + ), + inspectModeCanceled: withCdpName(events["Overlay.inspectModeCanceled"], "Overlay.inspectModeCanceled", "event"), }, Page: { - addScriptToEvaluateOnLoad: withCdpName(async (params?: unknown) => commands["Page.addScriptToEvaluateOnLoad"].result.parse(await send("Page.addScriptToEvaluateOnLoad", commands["Page.addScriptToEvaluateOnLoad"].params.parse(params ?? {}))), "Page.addScriptToEvaluateOnLoad", "command"), - addScriptToEvaluateOnNewDocument: withCdpName(async (params?: unknown) => commands["Page.addScriptToEvaluateOnNewDocument"].result.parse(await send("Page.addScriptToEvaluateOnNewDocument", commands["Page.addScriptToEvaluateOnNewDocument"].params.parse(params ?? {}))), "Page.addScriptToEvaluateOnNewDocument", "command"), - bringToFront: withCdpName(async (params?: unknown) => commands["Page.bringToFront"].result.parse(await send("Page.bringToFront", commands["Page.bringToFront"].params.parse(params ?? {}))), "Page.bringToFront", "command"), - captureScreenshot: withCdpName(async (params?: unknown) => commands["Page.captureScreenshot"].result.parse(await send("Page.captureScreenshot", commands["Page.captureScreenshot"].params.parse(params ?? {}))), "Page.captureScreenshot", "command"), - captureSnapshot: withCdpName(async (params?: unknown) => commands["Page.captureSnapshot"].result.parse(await send("Page.captureSnapshot", commands["Page.captureSnapshot"].params.parse(params ?? {}))), "Page.captureSnapshot", "command"), - clearDeviceMetricsOverride: withCdpName(async (params?: unknown) => commands["Page.clearDeviceMetricsOverride"].result.parse(await send("Page.clearDeviceMetricsOverride", commands["Page.clearDeviceMetricsOverride"].params.parse(params ?? {}))), "Page.clearDeviceMetricsOverride", "command"), - clearDeviceOrientationOverride: withCdpName(async (params?: unknown) => commands["Page.clearDeviceOrientationOverride"].result.parse(await send("Page.clearDeviceOrientationOverride", commands["Page.clearDeviceOrientationOverride"].params.parse(params ?? {}))), "Page.clearDeviceOrientationOverride", "command"), - clearGeolocationOverride: withCdpName(async (params?: unknown) => commands["Page.clearGeolocationOverride"].result.parse(await send("Page.clearGeolocationOverride", commands["Page.clearGeolocationOverride"].params.parse(params ?? {}))), "Page.clearGeolocationOverride", "command"), - createIsolatedWorld: withCdpName(async (params?: unknown) => commands["Page.createIsolatedWorld"].result.parse(await send("Page.createIsolatedWorld", commands["Page.createIsolatedWorld"].params.parse(params ?? {}))), "Page.createIsolatedWorld", "command"), - deleteCookie: withCdpName(async (params?: unknown) => commands["Page.deleteCookie"].result.parse(await send("Page.deleteCookie", commands["Page.deleteCookie"].params.parse(params ?? {}))), "Page.deleteCookie", "command"), - disable: withCdpName(async (params?: unknown) => commands["Page.disable"].result.parse(await send("Page.disable", commands["Page.disable"].params.parse(params ?? {}))), "Page.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Page.enable"].result.parse(await send("Page.enable", commands["Page.enable"].params.parse(params ?? {}))), "Page.enable", "command"), - getAppManifest: withCdpName(async (params?: unknown) => commands["Page.getAppManifest"].result.parse(await send("Page.getAppManifest", commands["Page.getAppManifest"].params.parse(params ?? {}))), "Page.getAppManifest", "command"), - getInstallabilityErrors: withCdpName(async (params?: unknown) => commands["Page.getInstallabilityErrors"].result.parse(await send("Page.getInstallabilityErrors", commands["Page.getInstallabilityErrors"].params.parse(params ?? {}))), "Page.getInstallabilityErrors", "command"), - getManifestIcons: withCdpName(async (params?: unknown) => commands["Page.getManifestIcons"].result.parse(await send("Page.getManifestIcons", commands["Page.getManifestIcons"].params.parse(params ?? {}))), "Page.getManifestIcons", "command"), - getAppId: withCdpName(async (params?: unknown) => commands["Page.getAppId"].result.parse(await send("Page.getAppId", commands["Page.getAppId"].params.parse(params ?? {}))), "Page.getAppId", "command"), - getAdScriptAncestry: withCdpName(async (params?: unknown) => commands["Page.getAdScriptAncestry"].result.parse(await send("Page.getAdScriptAncestry", commands["Page.getAdScriptAncestry"].params.parse(params ?? {}))), "Page.getAdScriptAncestry", "command"), - getFrameTree: withCdpName(async (params?: unknown) => commands["Page.getFrameTree"].result.parse(await send("Page.getFrameTree", commands["Page.getFrameTree"].params.parse(params ?? {}))), "Page.getFrameTree", "command"), - getLayoutMetrics: withCdpName(async (params?: unknown) => commands["Page.getLayoutMetrics"].result.parse(await send("Page.getLayoutMetrics", commands["Page.getLayoutMetrics"].params.parse(params ?? {}))), "Page.getLayoutMetrics", "command"), - getNavigationHistory: withCdpName(async (params?: unknown) => commands["Page.getNavigationHistory"].result.parse(await send("Page.getNavigationHistory", commands["Page.getNavigationHistory"].params.parse(params ?? {}))), "Page.getNavigationHistory", "command"), - resetNavigationHistory: withCdpName(async (params?: unknown) => commands["Page.resetNavigationHistory"].result.parse(await send("Page.resetNavigationHistory", commands["Page.resetNavigationHistory"].params.parse(params ?? {}))), "Page.resetNavigationHistory", "command"), - getResourceContent: withCdpName(async (params?: unknown) => commands["Page.getResourceContent"].result.parse(await send("Page.getResourceContent", commands["Page.getResourceContent"].params.parse(params ?? {}))), "Page.getResourceContent", "command"), - getResourceTree: withCdpName(async (params?: unknown) => commands["Page.getResourceTree"].result.parse(await send("Page.getResourceTree", commands["Page.getResourceTree"].params.parse(params ?? {}))), "Page.getResourceTree", "command"), - handleJavaScriptDialog: withCdpName(async (params?: unknown) => commands["Page.handleJavaScriptDialog"].result.parse(await send("Page.handleJavaScriptDialog", commands["Page.handleJavaScriptDialog"].params.parse(params ?? {}))), "Page.handleJavaScriptDialog", "command"), - navigate: withCdpName(async (params?: unknown) => commands["Page.navigate"].result.parse(await send("Page.navigate", commands["Page.navigate"].params.parse(params ?? {}))), "Page.navigate", "command"), - navigateToHistoryEntry: withCdpName(async (params?: unknown) => commands["Page.navigateToHistoryEntry"].result.parse(await send("Page.navigateToHistoryEntry", commands["Page.navigateToHistoryEntry"].params.parse(params ?? {}))), "Page.navigateToHistoryEntry", "command"), - printToPDF: withCdpName(async (params?: unknown) => commands["Page.printToPDF"].result.parse(await send("Page.printToPDF", commands["Page.printToPDF"].params.parse(params ?? {}))), "Page.printToPDF", "command"), - reload: withCdpName(async (params?: unknown) => commands["Page.reload"].result.parse(await send("Page.reload", commands["Page.reload"].params.parse(params ?? {}))), "Page.reload", "command"), - removeScriptToEvaluateOnLoad: withCdpName(async (params?: unknown) => commands["Page.removeScriptToEvaluateOnLoad"].result.parse(await send("Page.removeScriptToEvaluateOnLoad", commands["Page.removeScriptToEvaluateOnLoad"].params.parse(params ?? {}))), "Page.removeScriptToEvaluateOnLoad", "command"), - removeScriptToEvaluateOnNewDocument: withCdpName(async (params?: unknown) => commands["Page.removeScriptToEvaluateOnNewDocument"].result.parse(await send("Page.removeScriptToEvaluateOnNewDocument", commands["Page.removeScriptToEvaluateOnNewDocument"].params.parse(params ?? {}))), "Page.removeScriptToEvaluateOnNewDocument", "command"), - screencastFrameAck: withCdpName(async (params?: unknown) => commands["Page.screencastFrameAck"].result.parse(await send("Page.screencastFrameAck", commands["Page.screencastFrameAck"].params.parse(params ?? {}))), "Page.screencastFrameAck", "command"), - searchInResource: withCdpName(async (params?: unknown) => commands["Page.searchInResource"].result.parse(await send("Page.searchInResource", commands["Page.searchInResource"].params.parse(params ?? {}))), "Page.searchInResource", "command"), - setAdBlockingEnabled: withCdpName(async (params?: unknown) => commands["Page.setAdBlockingEnabled"].result.parse(await send("Page.setAdBlockingEnabled", commands["Page.setAdBlockingEnabled"].params.parse(params ?? {}))), "Page.setAdBlockingEnabled", "command"), - setBypassCSP: withCdpName(async (params?: unknown) => commands["Page.setBypassCSP"].result.parse(await send("Page.setBypassCSP", commands["Page.setBypassCSP"].params.parse(params ?? {}))), "Page.setBypassCSP", "command"), - getPermissionsPolicyState: withCdpName(async (params?: unknown) => commands["Page.getPermissionsPolicyState"].result.parse(await send("Page.getPermissionsPolicyState", commands["Page.getPermissionsPolicyState"].params.parse(params ?? {}))), "Page.getPermissionsPolicyState", "command"), - getOriginTrials: withCdpName(async (params?: unknown) => commands["Page.getOriginTrials"].result.parse(await send("Page.getOriginTrials", commands["Page.getOriginTrials"].params.parse(params ?? {}))), "Page.getOriginTrials", "command"), - setDeviceMetricsOverride: withCdpName(async (params?: unknown) => commands["Page.setDeviceMetricsOverride"].result.parse(await send("Page.setDeviceMetricsOverride", commands["Page.setDeviceMetricsOverride"].params.parse(params ?? {}))), "Page.setDeviceMetricsOverride", "command"), - setDeviceOrientationOverride: withCdpName(async (params?: unknown) => commands["Page.setDeviceOrientationOverride"].result.parse(await send("Page.setDeviceOrientationOverride", commands["Page.setDeviceOrientationOverride"].params.parse(params ?? {}))), "Page.setDeviceOrientationOverride", "command"), - setFontFamilies: withCdpName(async (params?: unknown) => commands["Page.setFontFamilies"].result.parse(await send("Page.setFontFamilies", commands["Page.setFontFamilies"].params.parse(params ?? {}))), "Page.setFontFamilies", "command"), - setFontSizes: withCdpName(async (params?: unknown) => commands["Page.setFontSizes"].result.parse(await send("Page.setFontSizes", commands["Page.setFontSizes"].params.parse(params ?? {}))), "Page.setFontSizes", "command"), - setDocumentContent: withCdpName(async (params?: unknown) => commands["Page.setDocumentContent"].result.parse(await send("Page.setDocumentContent", commands["Page.setDocumentContent"].params.parse(params ?? {}))), "Page.setDocumentContent", "command"), - setDownloadBehavior: withCdpName(async (params?: unknown) => commands["Page.setDownloadBehavior"].result.parse(await send("Page.setDownloadBehavior", commands["Page.setDownloadBehavior"].params.parse(params ?? {}))), "Page.setDownloadBehavior", "command"), - setGeolocationOverride: withCdpName(async (params?: unknown) => commands["Page.setGeolocationOverride"].result.parse(await send("Page.setGeolocationOverride", commands["Page.setGeolocationOverride"].params.parse(params ?? {}))), "Page.setGeolocationOverride", "command"), - setLifecycleEventsEnabled: withCdpName(async (params?: unknown) => commands["Page.setLifecycleEventsEnabled"].result.parse(await send("Page.setLifecycleEventsEnabled", commands["Page.setLifecycleEventsEnabled"].params.parse(params ?? {}))), "Page.setLifecycleEventsEnabled", "command"), - setTouchEmulationEnabled: withCdpName(async (params?: unknown) => commands["Page.setTouchEmulationEnabled"].result.parse(await send("Page.setTouchEmulationEnabled", commands["Page.setTouchEmulationEnabled"].params.parse(params ?? {}))), "Page.setTouchEmulationEnabled", "command"), - startScreencast: withCdpName(async (params?: unknown) => commands["Page.startScreencast"].result.parse(await send("Page.startScreencast", commands["Page.startScreencast"].params.parse(params ?? {}))), "Page.startScreencast", "command"), - stopLoading: withCdpName(async (params?: unknown) => commands["Page.stopLoading"].result.parse(await send("Page.stopLoading", commands["Page.stopLoading"].params.parse(params ?? {}))), "Page.stopLoading", "command"), - crash: withCdpName(async (params?: unknown) => commands["Page.crash"].result.parse(await send("Page.crash", commands["Page.crash"].params.parse(params ?? {}))), "Page.crash", "command"), - close: withCdpName(async (params?: unknown) => commands["Page.close"].result.parse(await send("Page.close", commands["Page.close"].params.parse(params ?? {}))), "Page.close", "command"), - setWebLifecycleState: withCdpName(async (params?: unknown) => commands["Page.setWebLifecycleState"].result.parse(await send("Page.setWebLifecycleState", commands["Page.setWebLifecycleState"].params.parse(params ?? {}))), "Page.setWebLifecycleState", "command"), - stopScreencast: withCdpName(async (params?: unknown) => commands["Page.stopScreencast"].result.parse(await send("Page.stopScreencast", commands["Page.stopScreencast"].params.parse(params ?? {}))), "Page.stopScreencast", "command"), - produceCompilationCache: withCdpName(async (params?: unknown) => commands["Page.produceCompilationCache"].result.parse(await send("Page.produceCompilationCache", commands["Page.produceCompilationCache"].params.parse(params ?? {}))), "Page.produceCompilationCache", "command"), - addCompilationCache: withCdpName(async (params?: unknown) => commands["Page.addCompilationCache"].result.parse(await send("Page.addCompilationCache", commands["Page.addCompilationCache"].params.parse(params ?? {}))), "Page.addCompilationCache", "command"), - clearCompilationCache: withCdpName(async (params?: unknown) => commands["Page.clearCompilationCache"].result.parse(await send("Page.clearCompilationCache", commands["Page.clearCompilationCache"].params.parse(params ?? {}))), "Page.clearCompilationCache", "command"), - setSPCTransactionMode: withCdpName(async (params?: unknown) => commands["Page.setSPCTransactionMode"].result.parse(await send("Page.setSPCTransactionMode", commands["Page.setSPCTransactionMode"].params.parse(params ?? {}))), "Page.setSPCTransactionMode", "command"), - setRPHRegistrationMode: withCdpName(async (params?: unknown) => commands["Page.setRPHRegistrationMode"].result.parse(await send("Page.setRPHRegistrationMode", commands["Page.setRPHRegistrationMode"].params.parse(params ?? {}))), "Page.setRPHRegistrationMode", "command"), - generateTestReport: withCdpName(async (params?: unknown) => commands["Page.generateTestReport"].result.parse(await send("Page.generateTestReport", commands["Page.generateTestReport"].params.parse(params ?? {}))), "Page.generateTestReport", "command"), - waitForDebugger: withCdpName(async (params?: unknown) => commands["Page.waitForDebugger"].result.parse(await send("Page.waitForDebugger", commands["Page.waitForDebugger"].params.parse(params ?? {}))), "Page.waitForDebugger", "command"), - setInterceptFileChooserDialog: withCdpName(async (params?: unknown) => commands["Page.setInterceptFileChooserDialog"].result.parse(await send("Page.setInterceptFileChooserDialog", commands["Page.setInterceptFileChooserDialog"].params.parse(params ?? {}))), "Page.setInterceptFileChooserDialog", "command"), - setPrerenderingAllowed: withCdpName(async (params?: unknown) => commands["Page.setPrerenderingAllowed"].result.parse(await send("Page.setPrerenderingAllowed", commands["Page.setPrerenderingAllowed"].params.parse(params ?? {}))), "Page.setPrerenderingAllowed", "command"), - getAnnotatedPageContent: withCdpName(async (params?: unknown) => commands["Page.getAnnotatedPageContent"].result.parse(await send("Page.getAnnotatedPageContent", commands["Page.getAnnotatedPageContent"].params.parse(params ?? {}))), "Page.getAnnotatedPageContent", "command"), - domContentEventFired: events["Page.domContentEventFired"] as CdpEventAlias, - fileChooserOpened: events["Page.fileChooserOpened"] as CdpEventAlias, - frameAttached: events["Page.frameAttached"] as CdpEventAlias, - frameClearedScheduledNavigation: events["Page.frameClearedScheduledNavigation"] as CdpEventAlias, - frameDetached: events["Page.frameDetached"] as CdpEventAlias, - frameSubtreeWillBeDetached: events["Page.frameSubtreeWillBeDetached"] as CdpEventAlias, - frameNavigated: events["Page.frameNavigated"] as CdpEventAlias, - documentOpened: events["Page.documentOpened"] as CdpEventAlias, - frameResized: events["Page.frameResized"] as CdpEventAlias, - frameStartedNavigating: events["Page.frameStartedNavigating"] as CdpEventAlias, - frameRequestedNavigation: events["Page.frameRequestedNavigation"] as CdpEventAlias, - frameScheduledNavigation: events["Page.frameScheduledNavigation"] as CdpEventAlias, - frameStartedLoading: events["Page.frameStartedLoading"] as CdpEventAlias, - frameStoppedLoading: events["Page.frameStoppedLoading"] as CdpEventAlias, - downloadWillBegin: events["Page.downloadWillBegin"] as CdpEventAlias, - downloadProgress: events["Page.downloadProgress"] as CdpEventAlias, - interstitialHidden: events["Page.interstitialHidden"] as CdpEventAlias, - interstitialShown: events["Page.interstitialShown"] as CdpEventAlias, - javascriptDialogClosed: events["Page.javascriptDialogClosed"] as CdpEventAlias, - javascriptDialogOpening: events["Page.javascriptDialogOpening"] as CdpEventAlias, - lifecycleEvent: events["Page.lifecycleEvent"] as CdpEventAlias, - backForwardCacheNotUsed: events["Page.backForwardCacheNotUsed"] as CdpEventAlias, - loadEventFired: events["Page.loadEventFired"] as CdpEventAlias, - navigatedWithinDocument: events["Page.navigatedWithinDocument"] as CdpEventAlias, - screencastFrame: events["Page.screencastFrame"] as CdpEventAlias, - screencastVisibilityChanged: events["Page.screencastVisibilityChanged"] as CdpEventAlias, - windowOpen: events["Page.windowOpen"] as CdpEventAlias, - compilationCacheProduced: events["Page.compilationCacheProduced"] as CdpEventAlias, + addScriptToEvaluateOnLoad: withCdpName( + async (params?: cdp.types.ts.Page.AddScriptToEvaluateOnLoadParams) => + commands["Page.addScriptToEvaluateOnLoad"].result.parse( + await send( + "Page.addScriptToEvaluateOnLoad", + commands["Page.addScriptToEvaluateOnLoad"].params.parse(params ?? {}), + ), + ), + "Page.addScriptToEvaluateOnLoad", + "command", + ), + addScriptToEvaluateOnNewDocument: withCdpName( + async (params?: cdp.types.ts.Page.AddScriptToEvaluateOnNewDocumentParams) => + commands["Page.addScriptToEvaluateOnNewDocument"].result.parse( + await send( + "Page.addScriptToEvaluateOnNewDocument", + commands["Page.addScriptToEvaluateOnNewDocument"].params.parse(params ?? {}), + ), + ), + "Page.addScriptToEvaluateOnNewDocument", + "command", + ), + bringToFront: withCdpName( + async (params?: cdp.types.ts.Page.BringToFrontParams) => + commands["Page.bringToFront"].result.parse( + await send("Page.bringToFront", commands["Page.bringToFront"].params.parse(params ?? {})), + ), + "Page.bringToFront", + "command", + ), + captureScreenshot: withCdpName( + async (params?: cdp.types.ts.Page.CaptureScreenshotParams) => + commands["Page.captureScreenshot"].result.parse( + await send("Page.captureScreenshot", commands["Page.captureScreenshot"].params.parse(params ?? {})), + ), + "Page.captureScreenshot", + "command", + ), + captureSnapshot: withCdpName( + async (params?: cdp.types.ts.Page.CaptureSnapshotParams) => + commands["Page.captureSnapshot"].result.parse( + await send("Page.captureSnapshot", commands["Page.captureSnapshot"].params.parse(params ?? {})), + ), + "Page.captureSnapshot", + "command", + ), + clearDeviceMetricsOverride: withCdpName( + async (params?: cdp.types.ts.Page.ClearDeviceMetricsOverrideParams) => + commands["Page.clearDeviceMetricsOverride"].result.parse( + await send( + "Page.clearDeviceMetricsOverride", + commands["Page.clearDeviceMetricsOverride"].params.parse(params ?? {}), + ), + ), + "Page.clearDeviceMetricsOverride", + "command", + ), + clearDeviceOrientationOverride: withCdpName( + async (params?: cdp.types.ts.Page.ClearDeviceOrientationOverrideParams) => + commands["Page.clearDeviceOrientationOverride"].result.parse( + await send( + "Page.clearDeviceOrientationOverride", + commands["Page.clearDeviceOrientationOverride"].params.parse(params ?? {}), + ), + ), + "Page.clearDeviceOrientationOverride", + "command", + ), + clearGeolocationOverride: withCdpName( + async (params?: cdp.types.ts.Page.ClearGeolocationOverrideParams) => + commands["Page.clearGeolocationOverride"].result.parse( + await send( + "Page.clearGeolocationOverride", + commands["Page.clearGeolocationOverride"].params.parse(params ?? {}), + ), + ), + "Page.clearGeolocationOverride", + "command", + ), + createIsolatedWorld: withCdpName( + async (params?: cdp.types.ts.Page.CreateIsolatedWorldParams) => + commands["Page.createIsolatedWorld"].result.parse( + await send("Page.createIsolatedWorld", commands["Page.createIsolatedWorld"].params.parse(params ?? {})), + ), + "Page.createIsolatedWorld", + "command", + ), + deleteCookie: withCdpName( + async (params?: cdp.types.ts.Page.DeleteCookieParams) => + commands["Page.deleteCookie"].result.parse( + await send("Page.deleteCookie", commands["Page.deleteCookie"].params.parse(params ?? {})), + ), + "Page.deleteCookie", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Page.DisableParams) => + commands["Page.disable"].result.parse( + await send("Page.disable", commands["Page.disable"].params.parse(params ?? {})), + ), + "Page.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Page.EnableParams) => + commands["Page.enable"].result.parse( + await send("Page.enable", commands["Page.enable"].params.parse(params ?? {})), + ), + "Page.enable", + "command", + ), + getAppManifest: withCdpName( + async (params?: cdp.types.ts.Page.GetAppManifestParams) => + commands["Page.getAppManifest"].result.parse( + await send("Page.getAppManifest", commands["Page.getAppManifest"].params.parse(params ?? {})), + ), + "Page.getAppManifest", + "command", + ), + getInstallabilityErrors: withCdpName( + async (params?: cdp.types.ts.Page.GetInstallabilityErrorsParams) => + commands["Page.getInstallabilityErrors"].result.parse( + await send( + "Page.getInstallabilityErrors", + commands["Page.getInstallabilityErrors"].params.parse(params ?? {}), + ), + ), + "Page.getInstallabilityErrors", + "command", + ), + getManifestIcons: withCdpName( + async (params?: cdp.types.ts.Page.GetManifestIconsParams) => + commands["Page.getManifestIcons"].result.parse( + await send("Page.getManifestIcons", commands["Page.getManifestIcons"].params.parse(params ?? {})), + ), + "Page.getManifestIcons", + "command", + ), + getAppId: withCdpName( + async (params?: cdp.types.ts.Page.GetAppIdParams) => + commands["Page.getAppId"].result.parse( + await send("Page.getAppId", commands["Page.getAppId"].params.parse(params ?? {})), + ), + "Page.getAppId", + "command", + ), + getAdScriptAncestry: withCdpName( + async (params?: cdp.types.ts.Page.GetAdScriptAncestryParams) => + commands["Page.getAdScriptAncestry"].result.parse( + await send("Page.getAdScriptAncestry", commands["Page.getAdScriptAncestry"].params.parse(params ?? {})), + ), + "Page.getAdScriptAncestry", + "command", + ), + getFrameTree: withCdpName( + async (params?: cdp.types.ts.Page.GetFrameTreeParams) => + commands["Page.getFrameTree"].result.parse( + await send("Page.getFrameTree", commands["Page.getFrameTree"].params.parse(params ?? {})), + ), + "Page.getFrameTree", + "command", + ), + getLayoutMetrics: withCdpName( + async (params?: cdp.types.ts.Page.GetLayoutMetricsParams) => + commands["Page.getLayoutMetrics"].result.parse( + await send("Page.getLayoutMetrics", commands["Page.getLayoutMetrics"].params.parse(params ?? {})), + ), + "Page.getLayoutMetrics", + "command", + ), + getNavigationHistory: withCdpName( + async (params?: cdp.types.ts.Page.GetNavigationHistoryParams) => + commands["Page.getNavigationHistory"].result.parse( + await send("Page.getNavigationHistory", commands["Page.getNavigationHistory"].params.parse(params ?? {})), + ), + "Page.getNavigationHistory", + "command", + ), + resetNavigationHistory: withCdpName( + async (params?: cdp.types.ts.Page.ResetNavigationHistoryParams) => + commands["Page.resetNavigationHistory"].result.parse( + await send( + "Page.resetNavigationHistory", + commands["Page.resetNavigationHistory"].params.parse(params ?? {}), + ), + ), + "Page.resetNavigationHistory", + "command", + ), + getResourceContent: withCdpName( + async (params?: cdp.types.ts.Page.GetResourceContentParams) => + commands["Page.getResourceContent"].result.parse( + await send("Page.getResourceContent", commands["Page.getResourceContent"].params.parse(params ?? {})), + ), + "Page.getResourceContent", + "command", + ), + getResourceTree: withCdpName( + async (params?: cdp.types.ts.Page.GetResourceTreeParams) => + commands["Page.getResourceTree"].result.parse( + await send("Page.getResourceTree", commands["Page.getResourceTree"].params.parse(params ?? {})), + ), + "Page.getResourceTree", + "command", + ), + handleJavaScriptDialog: withCdpName( + async (params?: cdp.types.ts.Page.HandleJavaScriptDialogParams) => + commands["Page.handleJavaScriptDialog"].result.parse( + await send( + "Page.handleJavaScriptDialog", + commands["Page.handleJavaScriptDialog"].params.parse(params ?? {}), + ), + ), + "Page.handleJavaScriptDialog", + "command", + ), + navigate: withCdpName( + async (params?: cdp.types.ts.Page.NavigateParams) => + commands["Page.navigate"].result.parse( + await send("Page.navigate", commands["Page.navigate"].params.parse(params ?? {})), + ), + "Page.navigate", + "command", + ), + navigateToHistoryEntry: withCdpName( + async (params?: cdp.types.ts.Page.NavigateToHistoryEntryParams) => + commands["Page.navigateToHistoryEntry"].result.parse( + await send( + "Page.navigateToHistoryEntry", + commands["Page.navigateToHistoryEntry"].params.parse(params ?? {}), + ), + ), + "Page.navigateToHistoryEntry", + "command", + ), + printToPDF: withCdpName( + async (params?: cdp.types.ts.Page.PrintToPDFParams) => + commands["Page.printToPDF"].result.parse( + await send("Page.printToPDF", commands["Page.printToPDF"].params.parse(params ?? {})), + ), + "Page.printToPDF", + "command", + ), + reload: withCdpName( + async (params?: cdp.types.ts.Page.ReloadParams) => + commands["Page.reload"].result.parse( + await send("Page.reload", commands["Page.reload"].params.parse(params ?? {})), + ), + "Page.reload", + "command", + ), + removeScriptToEvaluateOnLoad: withCdpName( + async (params?: cdp.types.ts.Page.RemoveScriptToEvaluateOnLoadParams) => + commands["Page.removeScriptToEvaluateOnLoad"].result.parse( + await send( + "Page.removeScriptToEvaluateOnLoad", + commands["Page.removeScriptToEvaluateOnLoad"].params.parse(params ?? {}), + ), + ), + "Page.removeScriptToEvaluateOnLoad", + "command", + ), + removeScriptToEvaluateOnNewDocument: withCdpName( + async (params?: cdp.types.ts.Page.RemoveScriptToEvaluateOnNewDocumentParams) => + commands["Page.removeScriptToEvaluateOnNewDocument"].result.parse( + await send( + "Page.removeScriptToEvaluateOnNewDocument", + commands["Page.removeScriptToEvaluateOnNewDocument"].params.parse(params ?? {}), + ), + ), + "Page.removeScriptToEvaluateOnNewDocument", + "command", + ), + screencastFrameAck: withCdpName( + async (params?: cdp.types.ts.Page.ScreencastFrameAckParams) => + commands["Page.screencastFrameAck"].result.parse( + await send("Page.screencastFrameAck", commands["Page.screencastFrameAck"].params.parse(params ?? {})), + ), + "Page.screencastFrameAck", + "command", + ), + searchInResource: withCdpName( + async (params?: cdp.types.ts.Page.SearchInResourceParams) => + commands["Page.searchInResource"].result.parse( + await send("Page.searchInResource", commands["Page.searchInResource"].params.parse(params ?? {})), + ), + "Page.searchInResource", + "command", + ), + setAdBlockingEnabled: withCdpName( + async (params?: cdp.types.ts.Page.SetAdBlockingEnabledParams) => + commands["Page.setAdBlockingEnabled"].result.parse( + await send("Page.setAdBlockingEnabled", commands["Page.setAdBlockingEnabled"].params.parse(params ?? {})), + ), + "Page.setAdBlockingEnabled", + "command", + ), + setBypassCSP: withCdpName( + async (params?: cdp.types.ts.Page.SetBypassCSPParams) => + commands["Page.setBypassCSP"].result.parse( + await send("Page.setBypassCSP", commands["Page.setBypassCSP"].params.parse(params ?? {})), + ), + "Page.setBypassCSP", + "command", + ), + getPermissionsPolicyState: withCdpName( + async (params?: cdp.types.ts.Page.GetPermissionsPolicyStateParams) => + commands["Page.getPermissionsPolicyState"].result.parse( + await send( + "Page.getPermissionsPolicyState", + commands["Page.getPermissionsPolicyState"].params.parse(params ?? {}), + ), + ), + "Page.getPermissionsPolicyState", + "command", + ), + getOriginTrials: withCdpName( + async (params?: cdp.types.ts.Page.GetOriginTrialsParams) => + commands["Page.getOriginTrials"].result.parse( + await send("Page.getOriginTrials", commands["Page.getOriginTrials"].params.parse(params ?? {})), + ), + "Page.getOriginTrials", + "command", + ), + setDeviceMetricsOverride: withCdpName( + async (params?: cdp.types.ts.Page.SetDeviceMetricsOverrideParams) => + commands["Page.setDeviceMetricsOverride"].result.parse( + await send( + "Page.setDeviceMetricsOverride", + commands["Page.setDeviceMetricsOverride"].params.parse(params ?? {}), + ), + ), + "Page.setDeviceMetricsOverride", + "command", + ), + setDeviceOrientationOverride: withCdpName( + async (params?: cdp.types.ts.Page.SetDeviceOrientationOverrideParams) => + commands["Page.setDeviceOrientationOverride"].result.parse( + await send( + "Page.setDeviceOrientationOverride", + commands["Page.setDeviceOrientationOverride"].params.parse(params ?? {}), + ), + ), + "Page.setDeviceOrientationOverride", + "command", + ), + setFontFamilies: withCdpName( + async (params?: cdp.types.ts.Page.SetFontFamiliesParams) => + commands["Page.setFontFamilies"].result.parse( + await send("Page.setFontFamilies", commands["Page.setFontFamilies"].params.parse(params ?? {})), + ), + "Page.setFontFamilies", + "command", + ), + setFontSizes: withCdpName( + async (params?: cdp.types.ts.Page.SetFontSizesParams) => + commands["Page.setFontSizes"].result.parse( + await send("Page.setFontSizes", commands["Page.setFontSizes"].params.parse(params ?? {})), + ), + "Page.setFontSizes", + "command", + ), + setDocumentContent: withCdpName( + async (params?: cdp.types.ts.Page.SetDocumentContentParams) => + commands["Page.setDocumentContent"].result.parse( + await send("Page.setDocumentContent", commands["Page.setDocumentContent"].params.parse(params ?? {})), + ), + "Page.setDocumentContent", + "command", + ), + setDownloadBehavior: withCdpName( + async (params?: cdp.types.ts.Page.SetDownloadBehaviorParams) => + commands["Page.setDownloadBehavior"].result.parse( + await send("Page.setDownloadBehavior", commands["Page.setDownloadBehavior"].params.parse(params ?? {})), + ), + "Page.setDownloadBehavior", + "command", + ), + setGeolocationOverride: withCdpName( + async (params?: cdp.types.ts.Page.SetGeolocationOverrideParams) => + commands["Page.setGeolocationOverride"].result.parse( + await send( + "Page.setGeolocationOverride", + commands["Page.setGeolocationOverride"].params.parse(params ?? {}), + ), + ), + "Page.setGeolocationOverride", + "command", + ), + setLifecycleEventsEnabled: withCdpName( + async (params?: cdp.types.ts.Page.SetLifecycleEventsEnabledParams) => + commands["Page.setLifecycleEventsEnabled"].result.parse( + await send( + "Page.setLifecycleEventsEnabled", + commands["Page.setLifecycleEventsEnabled"].params.parse(params ?? {}), + ), + ), + "Page.setLifecycleEventsEnabled", + "command", + ), + setTouchEmulationEnabled: withCdpName( + async (params?: cdp.types.ts.Page.SetTouchEmulationEnabledParams) => + commands["Page.setTouchEmulationEnabled"].result.parse( + await send( + "Page.setTouchEmulationEnabled", + commands["Page.setTouchEmulationEnabled"].params.parse(params ?? {}), + ), + ), + "Page.setTouchEmulationEnabled", + "command", + ), + startScreencast: withCdpName( + async (params?: cdp.types.ts.Page.StartScreencastParams) => + commands["Page.startScreencast"].result.parse( + await send("Page.startScreencast", commands["Page.startScreencast"].params.parse(params ?? {})), + ), + "Page.startScreencast", + "command", + ), + stopLoading: withCdpName( + async (params?: cdp.types.ts.Page.StopLoadingParams) => + commands["Page.stopLoading"].result.parse( + await send("Page.stopLoading", commands["Page.stopLoading"].params.parse(params ?? {})), + ), + "Page.stopLoading", + "command", + ), + crash: withCdpName( + async (params?: cdp.types.ts.Page.CrashParams) => + commands["Page.crash"].result.parse( + await send("Page.crash", commands["Page.crash"].params.parse(params ?? {})), + ), + "Page.crash", + "command", + ), + close: withCdpName( + async (params?: cdp.types.ts.Page.CloseParams) => + commands["Page.close"].result.parse( + await send("Page.close", commands["Page.close"].params.parse(params ?? {})), + ), + "Page.close", + "command", + ), + setWebLifecycleState: withCdpName( + async (params?: cdp.types.ts.Page.SetWebLifecycleStateParams) => + commands["Page.setWebLifecycleState"].result.parse( + await send("Page.setWebLifecycleState", commands["Page.setWebLifecycleState"].params.parse(params ?? {})), + ), + "Page.setWebLifecycleState", + "command", + ), + stopScreencast: withCdpName( + async (params?: cdp.types.ts.Page.StopScreencastParams) => + commands["Page.stopScreencast"].result.parse( + await send("Page.stopScreencast", commands["Page.stopScreencast"].params.parse(params ?? {})), + ), + "Page.stopScreencast", + "command", + ), + produceCompilationCache: withCdpName( + async (params?: cdp.types.ts.Page.ProduceCompilationCacheParams) => + commands["Page.produceCompilationCache"].result.parse( + await send( + "Page.produceCompilationCache", + commands["Page.produceCompilationCache"].params.parse(params ?? {}), + ), + ), + "Page.produceCompilationCache", + "command", + ), + addCompilationCache: withCdpName( + async (params?: cdp.types.ts.Page.AddCompilationCacheParams) => + commands["Page.addCompilationCache"].result.parse( + await send("Page.addCompilationCache", commands["Page.addCompilationCache"].params.parse(params ?? {})), + ), + "Page.addCompilationCache", + "command", + ), + clearCompilationCache: withCdpName( + async (params?: cdp.types.ts.Page.ClearCompilationCacheParams) => + commands["Page.clearCompilationCache"].result.parse( + await send("Page.clearCompilationCache", commands["Page.clearCompilationCache"].params.parse(params ?? {})), + ), + "Page.clearCompilationCache", + "command", + ), + setSPCTransactionMode: withCdpName( + async (params?: cdp.types.ts.Page.SetSPCTransactionModeParams) => + commands["Page.setSPCTransactionMode"].result.parse( + await send("Page.setSPCTransactionMode", commands["Page.setSPCTransactionMode"].params.parse(params ?? {})), + ), + "Page.setSPCTransactionMode", + "command", + ), + setRPHRegistrationMode: withCdpName( + async (params?: cdp.types.ts.Page.SetRPHRegistrationModeParams) => + commands["Page.setRPHRegistrationMode"].result.parse( + await send( + "Page.setRPHRegistrationMode", + commands["Page.setRPHRegistrationMode"].params.parse(params ?? {}), + ), + ), + "Page.setRPHRegistrationMode", + "command", + ), + generateTestReport: withCdpName( + async (params?: cdp.types.ts.Page.GenerateTestReportParams) => + commands["Page.generateTestReport"].result.parse( + await send("Page.generateTestReport", commands["Page.generateTestReport"].params.parse(params ?? {})), + ), + "Page.generateTestReport", + "command", + ), + waitForDebugger: withCdpName( + async (params?: cdp.types.ts.Page.WaitForDebuggerParams) => + commands["Page.waitForDebugger"].result.parse( + await send("Page.waitForDebugger", commands["Page.waitForDebugger"].params.parse(params ?? {})), + ), + "Page.waitForDebugger", + "command", + ), + setInterceptFileChooserDialog: withCdpName( + async (params?: cdp.types.ts.Page.SetInterceptFileChooserDialogParams) => + commands["Page.setInterceptFileChooserDialog"].result.parse( + await send( + "Page.setInterceptFileChooserDialog", + commands["Page.setInterceptFileChooserDialog"].params.parse(params ?? {}), + ), + ), + "Page.setInterceptFileChooserDialog", + "command", + ), + setPrerenderingAllowed: withCdpName( + async (params?: cdp.types.ts.Page.SetPrerenderingAllowedParams) => + commands["Page.setPrerenderingAllowed"].result.parse( + await send( + "Page.setPrerenderingAllowed", + commands["Page.setPrerenderingAllowed"].params.parse(params ?? {}), + ), + ), + "Page.setPrerenderingAllowed", + "command", + ), + getAnnotatedPageContent: withCdpName( + async (params?: cdp.types.ts.Page.GetAnnotatedPageContentParams) => + commands["Page.getAnnotatedPageContent"].result.parse( + await send( + "Page.getAnnotatedPageContent", + commands["Page.getAnnotatedPageContent"].params.parse(params ?? {}), + ), + ), + "Page.getAnnotatedPageContent", + "command", + ), + domContentEventFired: withCdpName(events["Page.domContentEventFired"], "Page.domContentEventFired", "event"), + fileChooserOpened: withCdpName(events["Page.fileChooserOpened"], "Page.fileChooserOpened", "event"), + frameAttached: withCdpName(events["Page.frameAttached"], "Page.frameAttached", "event"), + frameClearedScheduledNavigation: withCdpName( + events["Page.frameClearedScheduledNavigation"], + "Page.frameClearedScheduledNavigation", + "event", + ), + frameDetached: withCdpName(events["Page.frameDetached"], "Page.frameDetached", "event"), + frameSubtreeWillBeDetached: withCdpName( + events["Page.frameSubtreeWillBeDetached"], + "Page.frameSubtreeWillBeDetached", + "event", + ), + frameNavigated: withCdpName(events["Page.frameNavigated"], "Page.frameNavigated", "event"), + documentOpened: withCdpName(events["Page.documentOpened"], "Page.documentOpened", "event"), + frameResized: withCdpName(events["Page.frameResized"], "Page.frameResized", "event"), + frameStartedNavigating: withCdpName( + events["Page.frameStartedNavigating"], + "Page.frameStartedNavigating", + "event", + ), + frameRequestedNavigation: withCdpName( + events["Page.frameRequestedNavigation"], + "Page.frameRequestedNavigation", + "event", + ), + frameScheduledNavigation: withCdpName( + events["Page.frameScheduledNavigation"], + "Page.frameScheduledNavigation", + "event", + ), + frameStartedLoading: withCdpName(events["Page.frameStartedLoading"], "Page.frameStartedLoading", "event"), + frameStoppedLoading: withCdpName(events["Page.frameStoppedLoading"], "Page.frameStoppedLoading", "event"), + downloadWillBegin: withCdpName(events["Page.downloadWillBegin"], "Page.downloadWillBegin", "event"), + downloadProgress: withCdpName(events["Page.downloadProgress"], "Page.downloadProgress", "event"), + interstitialHidden: withCdpName(events["Page.interstitialHidden"], "Page.interstitialHidden", "event"), + interstitialShown: withCdpName(events["Page.interstitialShown"], "Page.interstitialShown", "event"), + javascriptDialogClosed: withCdpName( + events["Page.javascriptDialogClosed"], + "Page.javascriptDialogClosed", + "event", + ), + javascriptDialogOpening: withCdpName( + events["Page.javascriptDialogOpening"], + "Page.javascriptDialogOpening", + "event", + ), + lifecycleEvent: withCdpName(events["Page.lifecycleEvent"], "Page.lifecycleEvent", "event"), + backForwardCacheNotUsed: withCdpName( + events["Page.backForwardCacheNotUsed"], + "Page.backForwardCacheNotUsed", + "event", + ), + loadEventFired: withCdpName(events["Page.loadEventFired"], "Page.loadEventFired", "event"), + navigatedWithinDocument: withCdpName( + events["Page.navigatedWithinDocument"], + "Page.navigatedWithinDocument", + "event", + ), + screencastFrame: withCdpName(events["Page.screencastFrame"], "Page.screencastFrame", "event"), + screencastVisibilityChanged: withCdpName( + events["Page.screencastVisibilityChanged"], + "Page.screencastVisibilityChanged", + "event", + ), + windowOpen: withCdpName(events["Page.windowOpen"], "Page.windowOpen", "event"), + compilationCacheProduced: withCdpName( + events["Page.compilationCacheProduced"], + "Page.compilationCacheProduced", + "event", + ), }, Performance: { - disable: withCdpName(async (params?: unknown) => commands["Performance.disable"].result.parse(await send("Performance.disable", commands["Performance.disable"].params.parse(params ?? {}))), "Performance.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Performance.enable"].result.parse(await send("Performance.enable", commands["Performance.enable"].params.parse(params ?? {}))), "Performance.enable", "command"), - setTimeDomain: withCdpName(async (params?: unknown) => commands["Performance.setTimeDomain"].result.parse(await send("Performance.setTimeDomain", commands["Performance.setTimeDomain"].params.parse(params ?? {}))), "Performance.setTimeDomain", "command"), - getMetrics: withCdpName(async (params?: unknown) => commands["Performance.getMetrics"].result.parse(await send("Performance.getMetrics", commands["Performance.getMetrics"].params.parse(params ?? {}))), "Performance.getMetrics", "command"), - metrics: events["Performance.metrics"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Performance.DisableParams) => + commands["Performance.disable"].result.parse( + await send("Performance.disable", commands["Performance.disable"].params.parse(params ?? {})), + ), + "Performance.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Performance.EnableParams) => + commands["Performance.enable"].result.parse( + await send("Performance.enable", commands["Performance.enable"].params.parse(params ?? {})), + ), + "Performance.enable", + "command", + ), + setTimeDomain: withCdpName( + async (params?: cdp.types.ts.Performance.SetTimeDomainParams) => + commands["Performance.setTimeDomain"].result.parse( + await send("Performance.setTimeDomain", commands["Performance.setTimeDomain"].params.parse(params ?? {})), + ), + "Performance.setTimeDomain", + "command", + ), + getMetrics: withCdpName( + async (params?: cdp.types.ts.Performance.GetMetricsParams) => + commands["Performance.getMetrics"].result.parse( + await send("Performance.getMetrics", commands["Performance.getMetrics"].params.parse(params ?? {})), + ), + "Performance.getMetrics", + "command", + ), + metrics: withCdpName(events["Performance.metrics"], "Performance.metrics", "event"), }, PerformanceTimeline: { - enable: withCdpName(async (params?: unknown) => commands["PerformanceTimeline.enable"].result.parse(await send("PerformanceTimeline.enable", commands["PerformanceTimeline.enable"].params.parse(params ?? {}))), "PerformanceTimeline.enable", "command"), - timelineEventAdded: events["PerformanceTimeline.timelineEventAdded"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.PerformanceTimeline.EnableParams) => + commands["PerformanceTimeline.enable"].result.parse( + await send("PerformanceTimeline.enable", commands["PerformanceTimeline.enable"].params.parse(params ?? {})), + ), + "PerformanceTimeline.enable", + "command", + ), + timelineEventAdded: withCdpName( + events["PerformanceTimeline.timelineEventAdded"], + "PerformanceTimeline.timelineEventAdded", + "event", + ), }, Preload: { - enable: withCdpName(async (params?: unknown) => commands["Preload.enable"].result.parse(await send("Preload.enable", commands["Preload.enable"].params.parse(params ?? {}))), "Preload.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["Preload.disable"].result.parse(await send("Preload.disable", commands["Preload.disable"].params.parse(params ?? {}))), "Preload.disable", "command"), - ruleSetUpdated: events["Preload.ruleSetUpdated"] as CdpEventAlias, - ruleSetRemoved: events["Preload.ruleSetRemoved"] as CdpEventAlias, - preloadEnabledStateUpdated: events["Preload.preloadEnabledStateUpdated"] as CdpEventAlias, - prefetchStatusUpdated: events["Preload.prefetchStatusUpdated"] as CdpEventAlias, - prerenderStatusUpdated: events["Preload.prerenderStatusUpdated"] as CdpEventAlias, - preloadingAttemptSourcesUpdated: events["Preload.preloadingAttemptSourcesUpdated"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.Preload.EnableParams) => + commands["Preload.enable"].result.parse( + await send("Preload.enable", commands["Preload.enable"].params.parse(params ?? {})), + ), + "Preload.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Preload.DisableParams) => + commands["Preload.disable"].result.parse( + await send("Preload.disable", commands["Preload.disable"].params.parse(params ?? {})), + ), + "Preload.disable", + "command", + ), + ruleSetUpdated: withCdpName(events["Preload.ruleSetUpdated"], "Preload.ruleSetUpdated", "event"), + ruleSetRemoved: withCdpName(events["Preload.ruleSetRemoved"], "Preload.ruleSetRemoved", "event"), + preloadEnabledStateUpdated: withCdpName( + events["Preload.preloadEnabledStateUpdated"], + "Preload.preloadEnabledStateUpdated", + "event", + ), + prefetchStatusUpdated: withCdpName( + events["Preload.prefetchStatusUpdated"], + "Preload.prefetchStatusUpdated", + "event", + ), + prerenderStatusUpdated: withCdpName( + events["Preload.prerenderStatusUpdated"], + "Preload.prerenderStatusUpdated", + "event", + ), + preloadingAttemptSourcesUpdated: withCdpName( + events["Preload.preloadingAttemptSourcesUpdated"], + "Preload.preloadingAttemptSourcesUpdated", + "event", + ), }, Profiler: { - disable: withCdpName(async (params?: unknown) => commands["Profiler.disable"].result.parse(await send("Profiler.disable", commands["Profiler.disable"].params.parse(params ?? {}))), "Profiler.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Profiler.enable"].result.parse(await send("Profiler.enable", commands["Profiler.enable"].params.parse(params ?? {}))), "Profiler.enable", "command"), - getBestEffortCoverage: withCdpName(async (params?: unknown) => commands["Profiler.getBestEffortCoverage"].result.parse(await send("Profiler.getBestEffortCoverage", commands["Profiler.getBestEffortCoverage"].params.parse(params ?? {}))), "Profiler.getBestEffortCoverage", "command"), - setSamplingInterval: withCdpName(async (params?: unknown) => commands["Profiler.setSamplingInterval"].result.parse(await send("Profiler.setSamplingInterval", commands["Profiler.setSamplingInterval"].params.parse(params ?? {}))), "Profiler.setSamplingInterval", "command"), - start: withCdpName(async (params?: unknown) => commands["Profiler.start"].result.parse(await send("Profiler.start", commands["Profiler.start"].params.parse(params ?? {}))), "Profiler.start", "command"), - startPreciseCoverage: withCdpName(async (params?: unknown) => commands["Profiler.startPreciseCoverage"].result.parse(await send("Profiler.startPreciseCoverage", commands["Profiler.startPreciseCoverage"].params.parse(params ?? {}))), "Profiler.startPreciseCoverage", "command"), - stop: withCdpName(async (params?: unknown) => commands["Profiler.stop"].result.parse(await send("Profiler.stop", commands["Profiler.stop"].params.parse(params ?? {}))), "Profiler.stop", "command"), - stopPreciseCoverage: withCdpName(async (params?: unknown) => commands["Profiler.stopPreciseCoverage"].result.parse(await send("Profiler.stopPreciseCoverage", commands["Profiler.stopPreciseCoverage"].params.parse(params ?? {}))), "Profiler.stopPreciseCoverage", "command"), - takePreciseCoverage: withCdpName(async (params?: unknown) => commands["Profiler.takePreciseCoverage"].result.parse(await send("Profiler.takePreciseCoverage", commands["Profiler.takePreciseCoverage"].params.parse(params ?? {}))), "Profiler.takePreciseCoverage", "command"), - consoleProfileFinished: events["Profiler.consoleProfileFinished"] as CdpEventAlias, - consoleProfileStarted: events["Profiler.consoleProfileStarted"] as CdpEventAlias, - preciseCoverageDeltaUpdate: events["Profiler.preciseCoverageDeltaUpdate"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Profiler.DisableParams) => + commands["Profiler.disable"].result.parse( + await send("Profiler.disable", commands["Profiler.disable"].params.parse(params ?? {})), + ), + "Profiler.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Profiler.EnableParams) => + commands["Profiler.enable"].result.parse( + await send("Profiler.enable", commands["Profiler.enable"].params.parse(params ?? {})), + ), + "Profiler.enable", + "command", + ), + getBestEffortCoverage: withCdpName( + async (params?: cdp.types.ts.Profiler.GetBestEffortCoverageParams) => + commands["Profiler.getBestEffortCoverage"].result.parse( + await send( + "Profiler.getBestEffortCoverage", + commands["Profiler.getBestEffortCoverage"].params.parse(params ?? {}), + ), + ), + "Profiler.getBestEffortCoverage", + "command", + ), + setSamplingInterval: withCdpName( + async (params?: cdp.types.ts.Profiler.SetSamplingIntervalParams) => + commands["Profiler.setSamplingInterval"].result.parse( + await send( + "Profiler.setSamplingInterval", + commands["Profiler.setSamplingInterval"].params.parse(params ?? {}), + ), + ), + "Profiler.setSamplingInterval", + "command", + ), + start: withCdpName( + async (params?: cdp.types.ts.Profiler.StartParams) => + commands["Profiler.start"].result.parse( + await send("Profiler.start", commands["Profiler.start"].params.parse(params ?? {})), + ), + "Profiler.start", + "command", + ), + startPreciseCoverage: withCdpName( + async (params?: cdp.types.ts.Profiler.StartPreciseCoverageParams) => + commands["Profiler.startPreciseCoverage"].result.parse( + await send( + "Profiler.startPreciseCoverage", + commands["Profiler.startPreciseCoverage"].params.parse(params ?? {}), + ), + ), + "Profiler.startPreciseCoverage", + "command", + ), + stop: withCdpName( + async (params?: cdp.types.ts.Profiler.StopParams) => + commands["Profiler.stop"].result.parse( + await send("Profiler.stop", commands["Profiler.stop"].params.parse(params ?? {})), + ), + "Profiler.stop", + "command", + ), + stopPreciseCoverage: withCdpName( + async (params?: cdp.types.ts.Profiler.StopPreciseCoverageParams) => + commands["Profiler.stopPreciseCoverage"].result.parse( + await send( + "Profiler.stopPreciseCoverage", + commands["Profiler.stopPreciseCoverage"].params.parse(params ?? {}), + ), + ), + "Profiler.stopPreciseCoverage", + "command", + ), + takePreciseCoverage: withCdpName( + async (params?: cdp.types.ts.Profiler.TakePreciseCoverageParams) => + commands["Profiler.takePreciseCoverage"].result.parse( + await send( + "Profiler.takePreciseCoverage", + commands["Profiler.takePreciseCoverage"].params.parse(params ?? {}), + ), + ), + "Profiler.takePreciseCoverage", + "command", + ), + consoleProfileFinished: withCdpName( + events["Profiler.consoleProfileFinished"], + "Profiler.consoleProfileFinished", + "event", + ), + consoleProfileStarted: withCdpName( + events["Profiler.consoleProfileStarted"], + "Profiler.consoleProfileStarted", + "event", + ), + preciseCoverageDeltaUpdate: withCdpName( + events["Profiler.preciseCoverageDeltaUpdate"], + "Profiler.preciseCoverageDeltaUpdate", + "event", + ), }, PWA: { - getOsAppState: withCdpName(async (params?: unknown) => commands["PWA.getOsAppState"].result.parse(await send("PWA.getOsAppState", commands["PWA.getOsAppState"].params.parse(params ?? {}))), "PWA.getOsAppState", "command"), - install: withCdpName(async (params?: unknown) => commands["PWA.install"].result.parse(await send("PWA.install", commands["PWA.install"].params.parse(params ?? {}))), "PWA.install", "command"), - uninstall: withCdpName(async (params?: unknown) => commands["PWA.uninstall"].result.parse(await send("PWA.uninstall", commands["PWA.uninstall"].params.parse(params ?? {}))), "PWA.uninstall", "command"), - launch: withCdpName(async (params?: unknown) => commands["PWA.launch"].result.parse(await send("PWA.launch", commands["PWA.launch"].params.parse(params ?? {}))), "PWA.launch", "command"), - launchFilesInApp: withCdpName(async (params?: unknown) => commands["PWA.launchFilesInApp"].result.parse(await send("PWA.launchFilesInApp", commands["PWA.launchFilesInApp"].params.parse(params ?? {}))), "PWA.launchFilesInApp", "command"), - openCurrentPageInApp: withCdpName(async (params?: unknown) => commands["PWA.openCurrentPageInApp"].result.parse(await send("PWA.openCurrentPageInApp", commands["PWA.openCurrentPageInApp"].params.parse(params ?? {}))), "PWA.openCurrentPageInApp", "command"), - changeAppUserSettings: withCdpName(async (params?: unknown) => commands["PWA.changeAppUserSettings"].result.parse(await send("PWA.changeAppUserSettings", commands["PWA.changeAppUserSettings"].params.parse(params ?? {}))), "PWA.changeAppUserSettings", "command"), + getOsAppState: withCdpName( + async (params?: cdp.types.ts.PWA.GetOsAppStateParams) => + commands["PWA.getOsAppState"].result.parse( + await send("PWA.getOsAppState", commands["PWA.getOsAppState"].params.parse(params ?? {})), + ), + "PWA.getOsAppState", + "command", + ), + install: withCdpName( + async (params?: cdp.types.ts.PWA.InstallParams) => + commands["PWA.install"].result.parse( + await send("PWA.install", commands["PWA.install"].params.parse(params ?? {})), + ), + "PWA.install", + "command", + ), + uninstall: withCdpName( + async (params?: cdp.types.ts.PWA.UninstallParams) => + commands["PWA.uninstall"].result.parse( + await send("PWA.uninstall", commands["PWA.uninstall"].params.parse(params ?? {})), + ), + "PWA.uninstall", + "command", + ), + launch: withCdpName( + async (params?: cdp.types.ts.PWA.LaunchParams) => + commands["PWA.launch"].result.parse( + await send("PWA.launch", commands["PWA.launch"].params.parse(params ?? {})), + ), + "PWA.launch", + "command", + ), + launchFilesInApp: withCdpName( + async (params?: cdp.types.ts.PWA.LaunchFilesInAppParams) => + commands["PWA.launchFilesInApp"].result.parse( + await send("PWA.launchFilesInApp", commands["PWA.launchFilesInApp"].params.parse(params ?? {})), + ), + "PWA.launchFilesInApp", + "command", + ), + openCurrentPageInApp: withCdpName( + async (params?: cdp.types.ts.PWA.OpenCurrentPageInAppParams) => + commands["PWA.openCurrentPageInApp"].result.parse( + await send("PWA.openCurrentPageInApp", commands["PWA.openCurrentPageInApp"].params.parse(params ?? {})), + ), + "PWA.openCurrentPageInApp", + "command", + ), + changeAppUserSettings: withCdpName( + async (params?: cdp.types.ts.PWA.ChangeAppUserSettingsParams) => + commands["PWA.changeAppUserSettings"].result.parse( + await send("PWA.changeAppUserSettings", commands["PWA.changeAppUserSettings"].params.parse(params ?? {})), + ), + "PWA.changeAppUserSettings", + "command", + ), }, Runtime: { - awaitPromise: withCdpName(async (params?: unknown) => commands["Runtime.awaitPromise"].result.parse(await send("Runtime.awaitPromise", commands["Runtime.awaitPromise"].params.parse(params ?? {}))), "Runtime.awaitPromise", "command"), - callFunctionOn: withCdpName(async (params?: unknown) => commands["Runtime.callFunctionOn"].result.parse(await send("Runtime.callFunctionOn", commands["Runtime.callFunctionOn"].params.parse(params ?? {}))), "Runtime.callFunctionOn", "command"), - compileScript: withCdpName(async (params?: unknown) => commands["Runtime.compileScript"].result.parse(await send("Runtime.compileScript", commands["Runtime.compileScript"].params.parse(params ?? {}))), "Runtime.compileScript", "command"), - disable: withCdpName(async (params?: unknown) => commands["Runtime.disable"].result.parse(await send("Runtime.disable", commands["Runtime.disable"].params.parse(params ?? {}))), "Runtime.disable", "command"), - discardConsoleEntries: withCdpName(async (params?: unknown) => commands["Runtime.discardConsoleEntries"].result.parse(await send("Runtime.discardConsoleEntries", commands["Runtime.discardConsoleEntries"].params.parse(params ?? {}))), "Runtime.discardConsoleEntries", "command"), - enable: withCdpName(async (params?: unknown) => commands["Runtime.enable"].result.parse(await send("Runtime.enable", commands["Runtime.enable"].params.parse(params ?? {}))), "Runtime.enable", "command"), - evaluate: withCdpName(async (params?: unknown) => commands["Runtime.evaluate"].result.parse(await send("Runtime.evaluate", commands["Runtime.evaluate"].params.parse(params ?? {}))), "Runtime.evaluate", "command"), - getIsolateId: withCdpName(async (params?: unknown) => commands["Runtime.getIsolateId"].result.parse(await send("Runtime.getIsolateId", commands["Runtime.getIsolateId"].params.parse(params ?? {}))), "Runtime.getIsolateId", "command"), - getHeapUsage: withCdpName(async (params?: unknown) => commands["Runtime.getHeapUsage"].result.parse(await send("Runtime.getHeapUsage", commands["Runtime.getHeapUsage"].params.parse(params ?? {}))), "Runtime.getHeapUsage", "command"), - getProperties: withCdpName(async (params?: unknown) => commands["Runtime.getProperties"].result.parse(await send("Runtime.getProperties", commands["Runtime.getProperties"].params.parse(params ?? {}))), "Runtime.getProperties", "command"), - globalLexicalScopeNames: withCdpName(async (params?: unknown) => commands["Runtime.globalLexicalScopeNames"].result.parse(await send("Runtime.globalLexicalScopeNames", commands["Runtime.globalLexicalScopeNames"].params.parse(params ?? {}))), "Runtime.globalLexicalScopeNames", "command"), - queryObjects: withCdpName(async (params?: unknown) => commands["Runtime.queryObjects"].result.parse(await send("Runtime.queryObjects", commands["Runtime.queryObjects"].params.parse(params ?? {}))), "Runtime.queryObjects", "command"), - releaseObject: withCdpName(async (params?: unknown) => commands["Runtime.releaseObject"].result.parse(await send("Runtime.releaseObject", commands["Runtime.releaseObject"].params.parse(params ?? {}))), "Runtime.releaseObject", "command"), - releaseObjectGroup: withCdpName(async (params?: unknown) => commands["Runtime.releaseObjectGroup"].result.parse(await send("Runtime.releaseObjectGroup", commands["Runtime.releaseObjectGroup"].params.parse(params ?? {}))), "Runtime.releaseObjectGroup", "command"), - runIfWaitingForDebugger: withCdpName(async (params?: unknown) => commands["Runtime.runIfWaitingForDebugger"].result.parse(await send("Runtime.runIfWaitingForDebugger", commands["Runtime.runIfWaitingForDebugger"].params.parse(params ?? {}))), "Runtime.runIfWaitingForDebugger", "command"), - runScript: withCdpName(async (params?: unknown) => commands["Runtime.runScript"].result.parse(await send("Runtime.runScript", commands["Runtime.runScript"].params.parse(params ?? {}))), "Runtime.runScript", "command"), - setAsyncCallStackDepth: withCdpName(async (params?: unknown) => commands["Runtime.setAsyncCallStackDepth"].result.parse(await send("Runtime.setAsyncCallStackDepth", commands["Runtime.setAsyncCallStackDepth"].params.parse(params ?? {}))), "Runtime.setAsyncCallStackDepth", "command"), - setCustomObjectFormatterEnabled: withCdpName(async (params?: unknown) => commands["Runtime.setCustomObjectFormatterEnabled"].result.parse(await send("Runtime.setCustomObjectFormatterEnabled", commands["Runtime.setCustomObjectFormatterEnabled"].params.parse(params ?? {}))), "Runtime.setCustomObjectFormatterEnabled", "command"), - setMaxCallStackSizeToCapture: withCdpName(async (params?: unknown) => commands["Runtime.setMaxCallStackSizeToCapture"].result.parse(await send("Runtime.setMaxCallStackSizeToCapture", commands["Runtime.setMaxCallStackSizeToCapture"].params.parse(params ?? {}))), "Runtime.setMaxCallStackSizeToCapture", "command"), - terminateExecution: withCdpName(async (params?: unknown) => commands["Runtime.terminateExecution"].result.parse(await send("Runtime.terminateExecution", commands["Runtime.terminateExecution"].params.parse(params ?? {}))), "Runtime.terminateExecution", "command"), - addBinding: withCdpName(async (params?: unknown) => commands["Runtime.addBinding"].result.parse(await send("Runtime.addBinding", commands["Runtime.addBinding"].params.parse(params ?? {}))), "Runtime.addBinding", "command"), - removeBinding: withCdpName(async (params?: unknown) => commands["Runtime.removeBinding"].result.parse(await send("Runtime.removeBinding", commands["Runtime.removeBinding"].params.parse(params ?? {}))), "Runtime.removeBinding", "command"), - getExceptionDetails: withCdpName(async (params?: unknown) => commands["Runtime.getExceptionDetails"].result.parse(await send("Runtime.getExceptionDetails", commands["Runtime.getExceptionDetails"].params.parse(params ?? {}))), "Runtime.getExceptionDetails", "command"), - bindingCalled: events["Runtime.bindingCalled"] as CdpEventAlias, - consoleAPICalled: events["Runtime.consoleAPICalled"] as CdpEventAlias, - exceptionRevoked: events["Runtime.exceptionRevoked"] as CdpEventAlias, - exceptionThrown: events["Runtime.exceptionThrown"] as CdpEventAlias, - executionContextCreated: events["Runtime.executionContextCreated"] as CdpEventAlias, - executionContextDestroyed: events["Runtime.executionContextDestroyed"] as CdpEventAlias, - executionContextsCleared: events["Runtime.executionContextsCleared"] as CdpEventAlias, - inspectRequested: events["Runtime.inspectRequested"] as CdpEventAlias, + awaitPromise: withCdpName( + async (params?: cdp.types.ts.Runtime.AwaitPromiseParams) => + commands["Runtime.awaitPromise"].result.parse( + await send("Runtime.awaitPromise", commands["Runtime.awaitPromise"].params.parse(params ?? {})), + ), + "Runtime.awaitPromise", + "command", + ), + callFunctionOn: withCdpName( + async (params?: cdp.types.ts.Runtime.CallFunctionOnParams) => + commands["Runtime.callFunctionOn"].result.parse( + await send("Runtime.callFunctionOn", commands["Runtime.callFunctionOn"].params.parse(params ?? {})), + ), + "Runtime.callFunctionOn", + "command", + ), + compileScript: withCdpName( + async (params?: cdp.types.ts.Runtime.CompileScriptParams) => + commands["Runtime.compileScript"].result.parse( + await send("Runtime.compileScript", commands["Runtime.compileScript"].params.parse(params ?? {})), + ), + "Runtime.compileScript", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.Runtime.DisableParams) => + commands["Runtime.disable"].result.parse( + await send("Runtime.disable", commands["Runtime.disable"].params.parse(params ?? {})), + ), + "Runtime.disable", + "command", + ), + discardConsoleEntries: withCdpName( + async (params?: cdp.types.ts.Runtime.DiscardConsoleEntriesParams) => + commands["Runtime.discardConsoleEntries"].result.parse( + await send( + "Runtime.discardConsoleEntries", + commands["Runtime.discardConsoleEntries"].params.parse(params ?? {}), + ), + ), + "Runtime.discardConsoleEntries", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Runtime.EnableParams) => + commands["Runtime.enable"].result.parse( + await send("Runtime.enable", commands["Runtime.enable"].params.parse(params ?? {})), + ), + "Runtime.enable", + "command", + ), + evaluate: withCdpName( + async (params?: cdp.types.ts.Runtime.EvaluateParams) => + commands["Runtime.evaluate"].result.parse( + await send("Runtime.evaluate", commands["Runtime.evaluate"].params.parse(params ?? {})), + ), + "Runtime.evaluate", + "command", + ), + getIsolateId: withCdpName( + async (params?: cdp.types.ts.Runtime.GetIsolateIdParams) => + commands["Runtime.getIsolateId"].result.parse( + await send("Runtime.getIsolateId", commands["Runtime.getIsolateId"].params.parse(params ?? {})), + ), + "Runtime.getIsolateId", + "command", + ), + getHeapUsage: withCdpName( + async (params?: cdp.types.ts.Runtime.GetHeapUsageParams) => + commands["Runtime.getHeapUsage"].result.parse( + await send("Runtime.getHeapUsage", commands["Runtime.getHeapUsage"].params.parse(params ?? {})), + ), + "Runtime.getHeapUsage", + "command", + ), + getProperties: withCdpName( + async (params?: cdp.types.ts.Runtime.GetPropertiesParams) => + commands["Runtime.getProperties"].result.parse( + await send("Runtime.getProperties", commands["Runtime.getProperties"].params.parse(params ?? {})), + ), + "Runtime.getProperties", + "command", + ), + globalLexicalScopeNames: withCdpName( + async (params?: cdp.types.ts.Runtime.GlobalLexicalScopeNamesParams) => + commands["Runtime.globalLexicalScopeNames"].result.parse( + await send( + "Runtime.globalLexicalScopeNames", + commands["Runtime.globalLexicalScopeNames"].params.parse(params ?? {}), + ), + ), + "Runtime.globalLexicalScopeNames", + "command", + ), + queryObjects: withCdpName( + async (params?: cdp.types.ts.Runtime.QueryObjectsParams) => + commands["Runtime.queryObjects"].result.parse( + await send("Runtime.queryObjects", commands["Runtime.queryObjects"].params.parse(params ?? {})), + ), + "Runtime.queryObjects", + "command", + ), + releaseObject: withCdpName( + async (params?: cdp.types.ts.Runtime.ReleaseObjectParams) => + commands["Runtime.releaseObject"].result.parse( + await send("Runtime.releaseObject", commands["Runtime.releaseObject"].params.parse(params ?? {})), + ), + "Runtime.releaseObject", + "command", + ), + releaseObjectGroup: withCdpName( + async (params?: cdp.types.ts.Runtime.ReleaseObjectGroupParams) => + commands["Runtime.releaseObjectGroup"].result.parse( + await send("Runtime.releaseObjectGroup", commands["Runtime.releaseObjectGroup"].params.parse(params ?? {})), + ), + "Runtime.releaseObjectGroup", + "command", + ), + runIfWaitingForDebugger: withCdpName( + async (params?: cdp.types.ts.Runtime.RunIfWaitingForDebuggerParams) => + commands["Runtime.runIfWaitingForDebugger"].result.parse( + await send( + "Runtime.runIfWaitingForDebugger", + commands["Runtime.runIfWaitingForDebugger"].params.parse(params ?? {}), + ), + ), + "Runtime.runIfWaitingForDebugger", + "command", + ), + runScript: withCdpName( + async (params?: cdp.types.ts.Runtime.RunScriptParams) => + commands["Runtime.runScript"].result.parse( + await send("Runtime.runScript", commands["Runtime.runScript"].params.parse(params ?? {})), + ), + "Runtime.runScript", + "command", + ), + setAsyncCallStackDepth: withCdpName( + async (params?: cdp.types.ts.Runtime.SetAsyncCallStackDepthParams) => + commands["Runtime.setAsyncCallStackDepth"].result.parse( + await send( + "Runtime.setAsyncCallStackDepth", + commands["Runtime.setAsyncCallStackDepth"].params.parse(params ?? {}), + ), + ), + "Runtime.setAsyncCallStackDepth", + "command", + ), + setCustomObjectFormatterEnabled: withCdpName( + async (params?: cdp.types.ts.Runtime.SetCustomObjectFormatterEnabledParams) => + commands["Runtime.setCustomObjectFormatterEnabled"].result.parse( + await send( + "Runtime.setCustomObjectFormatterEnabled", + commands["Runtime.setCustomObjectFormatterEnabled"].params.parse(params ?? {}), + ), + ), + "Runtime.setCustomObjectFormatterEnabled", + "command", + ), + setMaxCallStackSizeToCapture: withCdpName( + async (params?: cdp.types.ts.Runtime.SetMaxCallStackSizeToCaptureParams) => + commands["Runtime.setMaxCallStackSizeToCapture"].result.parse( + await send( + "Runtime.setMaxCallStackSizeToCapture", + commands["Runtime.setMaxCallStackSizeToCapture"].params.parse(params ?? {}), + ), + ), + "Runtime.setMaxCallStackSizeToCapture", + "command", + ), + terminateExecution: withCdpName( + async (params?: cdp.types.ts.Runtime.TerminateExecutionParams) => + commands["Runtime.terminateExecution"].result.parse( + await send("Runtime.terminateExecution", commands["Runtime.terminateExecution"].params.parse(params ?? {})), + ), + "Runtime.terminateExecution", + "command", + ), + addBinding: withCdpName( + async (params?: cdp.types.ts.Runtime.AddBindingParams) => + commands["Runtime.addBinding"].result.parse( + await send("Runtime.addBinding", commands["Runtime.addBinding"].params.parse(params ?? {})), + ), + "Runtime.addBinding", + "command", + ), + removeBinding: withCdpName( + async (params?: cdp.types.ts.Runtime.RemoveBindingParams) => + commands["Runtime.removeBinding"].result.parse( + await send("Runtime.removeBinding", commands["Runtime.removeBinding"].params.parse(params ?? {})), + ), + "Runtime.removeBinding", + "command", + ), + getExceptionDetails: withCdpName( + async (params?: cdp.types.ts.Runtime.GetExceptionDetailsParams) => + commands["Runtime.getExceptionDetails"].result.parse( + await send( + "Runtime.getExceptionDetails", + commands["Runtime.getExceptionDetails"].params.parse(params ?? {}), + ), + ), + "Runtime.getExceptionDetails", + "command", + ), + bindingCalled: withCdpName(events["Runtime.bindingCalled"], "Runtime.bindingCalled", "event"), + consoleAPICalled: withCdpName(events["Runtime.consoleAPICalled"], "Runtime.consoleAPICalled", "event"), + exceptionRevoked: withCdpName(events["Runtime.exceptionRevoked"], "Runtime.exceptionRevoked", "event"), + exceptionThrown: withCdpName(events["Runtime.exceptionThrown"], "Runtime.exceptionThrown", "event"), + executionContextCreated: withCdpName( + events["Runtime.executionContextCreated"], + "Runtime.executionContextCreated", + "event", + ), + executionContextDestroyed: withCdpName( + events["Runtime.executionContextDestroyed"], + "Runtime.executionContextDestroyed", + "event", + ), + executionContextsCleared: withCdpName( + events["Runtime.executionContextsCleared"], + "Runtime.executionContextsCleared", + "event", + ), + inspectRequested: withCdpName(events["Runtime.inspectRequested"], "Runtime.inspectRequested", "event"), }, Schema: { - getDomains: withCdpName(async (params?: unknown) => commands["Schema.getDomains"].result.parse(await send("Schema.getDomains", commands["Schema.getDomains"].params.parse(params ?? {}))), "Schema.getDomains", "command"), + getDomains: withCdpName( + async (params?: cdp.types.ts.Schema.GetDomainsParams) => + commands["Schema.getDomains"].result.parse( + await send("Schema.getDomains", commands["Schema.getDomains"].params.parse(params ?? {})), + ), + "Schema.getDomains", + "command", + ), }, Security: { - disable: withCdpName(async (params?: unknown) => commands["Security.disable"].result.parse(await send("Security.disable", commands["Security.disable"].params.parse(params ?? {}))), "Security.disable", "command"), - enable: withCdpName(async (params?: unknown) => commands["Security.enable"].result.parse(await send("Security.enable", commands["Security.enable"].params.parse(params ?? {}))), "Security.enable", "command"), - setIgnoreCertificateErrors: withCdpName(async (params?: unknown) => commands["Security.setIgnoreCertificateErrors"].result.parse(await send("Security.setIgnoreCertificateErrors", commands["Security.setIgnoreCertificateErrors"].params.parse(params ?? {}))), "Security.setIgnoreCertificateErrors", "command"), - handleCertificateError: withCdpName(async (params?: unknown) => commands["Security.handleCertificateError"].result.parse(await send("Security.handleCertificateError", commands["Security.handleCertificateError"].params.parse(params ?? {}))), "Security.handleCertificateError", "command"), - setOverrideCertificateErrors: withCdpName(async (params?: unknown) => commands["Security.setOverrideCertificateErrors"].result.parse(await send("Security.setOverrideCertificateErrors", commands["Security.setOverrideCertificateErrors"].params.parse(params ?? {}))), "Security.setOverrideCertificateErrors", "command"), - certificateError: events["Security.certificateError"] as CdpEventAlias, - visibleSecurityStateChanged: events["Security.visibleSecurityStateChanged"] as CdpEventAlias, - securityStateChanged: events["Security.securityStateChanged"] as CdpEventAlias, + disable: withCdpName( + async (params?: cdp.types.ts.Security.DisableParams) => + commands["Security.disable"].result.parse( + await send("Security.disable", commands["Security.disable"].params.parse(params ?? {})), + ), + "Security.disable", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.Security.EnableParams) => + commands["Security.enable"].result.parse( + await send("Security.enable", commands["Security.enable"].params.parse(params ?? {})), + ), + "Security.enable", + "command", + ), + setIgnoreCertificateErrors: withCdpName( + async (params?: cdp.types.ts.Security.SetIgnoreCertificateErrorsParams) => + commands["Security.setIgnoreCertificateErrors"].result.parse( + await send( + "Security.setIgnoreCertificateErrors", + commands["Security.setIgnoreCertificateErrors"].params.parse(params ?? {}), + ), + ), + "Security.setIgnoreCertificateErrors", + "command", + ), + handleCertificateError: withCdpName( + async (params?: cdp.types.ts.Security.HandleCertificateErrorParams) => + commands["Security.handleCertificateError"].result.parse( + await send( + "Security.handleCertificateError", + commands["Security.handleCertificateError"].params.parse(params ?? {}), + ), + ), + "Security.handleCertificateError", + "command", + ), + setOverrideCertificateErrors: withCdpName( + async (params?: cdp.types.ts.Security.SetOverrideCertificateErrorsParams) => + commands["Security.setOverrideCertificateErrors"].result.parse( + await send( + "Security.setOverrideCertificateErrors", + commands["Security.setOverrideCertificateErrors"].params.parse(params ?? {}), + ), + ), + "Security.setOverrideCertificateErrors", + "command", + ), + certificateError: withCdpName(events["Security.certificateError"], "Security.certificateError", "event"), + visibleSecurityStateChanged: withCdpName( + events["Security.visibleSecurityStateChanged"], + "Security.visibleSecurityStateChanged", + "event", + ), + securityStateChanged: withCdpName( + events["Security.securityStateChanged"], + "Security.securityStateChanged", + "event", + ), }, ServiceWorker: { - deliverPushMessage: withCdpName(async (params?: unknown) => commands["ServiceWorker.deliverPushMessage"].result.parse(await send("ServiceWorker.deliverPushMessage", commands["ServiceWorker.deliverPushMessage"].params.parse(params ?? {}))), "ServiceWorker.deliverPushMessage", "command"), - disable: withCdpName(async (params?: unknown) => commands["ServiceWorker.disable"].result.parse(await send("ServiceWorker.disable", commands["ServiceWorker.disable"].params.parse(params ?? {}))), "ServiceWorker.disable", "command"), - dispatchSyncEvent: withCdpName(async (params?: unknown) => commands["ServiceWorker.dispatchSyncEvent"].result.parse(await send("ServiceWorker.dispatchSyncEvent", commands["ServiceWorker.dispatchSyncEvent"].params.parse(params ?? {}))), "ServiceWorker.dispatchSyncEvent", "command"), - dispatchPeriodicSyncEvent: withCdpName(async (params?: unknown) => commands["ServiceWorker.dispatchPeriodicSyncEvent"].result.parse(await send("ServiceWorker.dispatchPeriodicSyncEvent", commands["ServiceWorker.dispatchPeriodicSyncEvent"].params.parse(params ?? {}))), "ServiceWorker.dispatchPeriodicSyncEvent", "command"), - enable: withCdpName(async (params?: unknown) => commands["ServiceWorker.enable"].result.parse(await send("ServiceWorker.enable", commands["ServiceWorker.enable"].params.parse(params ?? {}))), "ServiceWorker.enable", "command"), - setForceUpdateOnPageLoad: withCdpName(async (params?: unknown) => commands["ServiceWorker.setForceUpdateOnPageLoad"].result.parse(await send("ServiceWorker.setForceUpdateOnPageLoad", commands["ServiceWorker.setForceUpdateOnPageLoad"].params.parse(params ?? {}))), "ServiceWorker.setForceUpdateOnPageLoad", "command"), - skipWaiting: withCdpName(async (params?: unknown) => commands["ServiceWorker.skipWaiting"].result.parse(await send("ServiceWorker.skipWaiting", commands["ServiceWorker.skipWaiting"].params.parse(params ?? {}))), "ServiceWorker.skipWaiting", "command"), - startWorker: withCdpName(async (params?: unknown) => commands["ServiceWorker.startWorker"].result.parse(await send("ServiceWorker.startWorker", commands["ServiceWorker.startWorker"].params.parse(params ?? {}))), "ServiceWorker.startWorker", "command"), - stopAllWorkers: withCdpName(async (params?: unknown) => commands["ServiceWorker.stopAllWorkers"].result.parse(await send("ServiceWorker.stopAllWorkers", commands["ServiceWorker.stopAllWorkers"].params.parse(params ?? {}))), "ServiceWorker.stopAllWorkers", "command"), - stopWorker: withCdpName(async (params?: unknown) => commands["ServiceWorker.stopWorker"].result.parse(await send("ServiceWorker.stopWorker", commands["ServiceWorker.stopWorker"].params.parse(params ?? {}))), "ServiceWorker.stopWorker", "command"), - unregister: withCdpName(async (params?: unknown) => commands["ServiceWorker.unregister"].result.parse(await send("ServiceWorker.unregister", commands["ServiceWorker.unregister"].params.parse(params ?? {}))), "ServiceWorker.unregister", "command"), - updateRegistration: withCdpName(async (params?: unknown) => commands["ServiceWorker.updateRegistration"].result.parse(await send("ServiceWorker.updateRegistration", commands["ServiceWorker.updateRegistration"].params.parse(params ?? {}))), "ServiceWorker.updateRegistration", "command"), - workerErrorReported: events["ServiceWorker.workerErrorReported"] as CdpEventAlias, - workerRegistrationUpdated: events["ServiceWorker.workerRegistrationUpdated"] as CdpEventAlias, - workerVersionUpdated: events["ServiceWorker.workerVersionUpdated"] as CdpEventAlias, + deliverPushMessage: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.DeliverPushMessageParams) => + commands["ServiceWorker.deliverPushMessage"].result.parse( + await send( + "ServiceWorker.deliverPushMessage", + commands["ServiceWorker.deliverPushMessage"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.deliverPushMessage", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.DisableParams) => + commands["ServiceWorker.disable"].result.parse( + await send("ServiceWorker.disable", commands["ServiceWorker.disable"].params.parse(params ?? {})), + ), + "ServiceWorker.disable", + "command", + ), + dispatchSyncEvent: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.DispatchSyncEventParams) => + commands["ServiceWorker.dispatchSyncEvent"].result.parse( + await send( + "ServiceWorker.dispatchSyncEvent", + commands["ServiceWorker.dispatchSyncEvent"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.dispatchSyncEvent", + "command", + ), + dispatchPeriodicSyncEvent: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.DispatchPeriodicSyncEventParams) => + commands["ServiceWorker.dispatchPeriodicSyncEvent"].result.parse( + await send( + "ServiceWorker.dispatchPeriodicSyncEvent", + commands["ServiceWorker.dispatchPeriodicSyncEvent"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.dispatchPeriodicSyncEvent", + "command", + ), + enable: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.EnableParams) => + commands["ServiceWorker.enable"].result.parse( + await send("ServiceWorker.enable", commands["ServiceWorker.enable"].params.parse(params ?? {})), + ), + "ServiceWorker.enable", + "command", + ), + setForceUpdateOnPageLoad: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.SetForceUpdateOnPageLoadParams) => + commands["ServiceWorker.setForceUpdateOnPageLoad"].result.parse( + await send( + "ServiceWorker.setForceUpdateOnPageLoad", + commands["ServiceWorker.setForceUpdateOnPageLoad"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.setForceUpdateOnPageLoad", + "command", + ), + skipWaiting: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.SkipWaitingParams) => + commands["ServiceWorker.skipWaiting"].result.parse( + await send("ServiceWorker.skipWaiting", commands["ServiceWorker.skipWaiting"].params.parse(params ?? {})), + ), + "ServiceWorker.skipWaiting", + "command", + ), + startWorker: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.StartWorkerParams) => + commands["ServiceWorker.startWorker"].result.parse( + await send("ServiceWorker.startWorker", commands["ServiceWorker.startWorker"].params.parse(params ?? {})), + ), + "ServiceWorker.startWorker", + "command", + ), + stopAllWorkers: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.StopAllWorkersParams) => + commands["ServiceWorker.stopAllWorkers"].result.parse( + await send( + "ServiceWorker.stopAllWorkers", + commands["ServiceWorker.stopAllWorkers"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.stopAllWorkers", + "command", + ), + stopWorker: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.StopWorkerParams) => + commands["ServiceWorker.stopWorker"].result.parse( + await send("ServiceWorker.stopWorker", commands["ServiceWorker.stopWorker"].params.parse(params ?? {})), + ), + "ServiceWorker.stopWorker", + "command", + ), + unregister: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.UnregisterParams) => + commands["ServiceWorker.unregister"].result.parse( + await send("ServiceWorker.unregister", commands["ServiceWorker.unregister"].params.parse(params ?? {})), + ), + "ServiceWorker.unregister", + "command", + ), + updateRegistration: withCdpName( + async (params?: cdp.types.ts.ServiceWorker.UpdateRegistrationParams) => + commands["ServiceWorker.updateRegistration"].result.parse( + await send( + "ServiceWorker.updateRegistration", + commands["ServiceWorker.updateRegistration"].params.parse(params ?? {}), + ), + ), + "ServiceWorker.updateRegistration", + "command", + ), + workerErrorReported: withCdpName( + events["ServiceWorker.workerErrorReported"], + "ServiceWorker.workerErrorReported", + "event", + ), + workerRegistrationUpdated: withCdpName( + events["ServiceWorker.workerRegistrationUpdated"], + "ServiceWorker.workerRegistrationUpdated", + "event", + ), + workerVersionUpdated: withCdpName( + events["ServiceWorker.workerVersionUpdated"], + "ServiceWorker.workerVersionUpdated", + "event", + ), }, SmartCardEmulation: { - enable: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.enable"].result.parse(await send("SmartCardEmulation.enable", commands["SmartCardEmulation.enable"].params.parse(params ?? {}))), "SmartCardEmulation.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.disable"].result.parse(await send("SmartCardEmulation.disable", commands["SmartCardEmulation.disable"].params.parse(params ?? {}))), "SmartCardEmulation.disable", "command"), - reportEstablishContextResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportEstablishContextResult"].result.parse(await send("SmartCardEmulation.reportEstablishContextResult", commands["SmartCardEmulation.reportEstablishContextResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportEstablishContextResult", "command"), - reportReleaseContextResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportReleaseContextResult"].result.parse(await send("SmartCardEmulation.reportReleaseContextResult", commands["SmartCardEmulation.reportReleaseContextResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportReleaseContextResult", "command"), - reportListReadersResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportListReadersResult"].result.parse(await send("SmartCardEmulation.reportListReadersResult", commands["SmartCardEmulation.reportListReadersResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportListReadersResult", "command"), - reportGetStatusChangeResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportGetStatusChangeResult"].result.parse(await send("SmartCardEmulation.reportGetStatusChangeResult", commands["SmartCardEmulation.reportGetStatusChangeResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportGetStatusChangeResult", "command"), - reportBeginTransactionResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportBeginTransactionResult"].result.parse(await send("SmartCardEmulation.reportBeginTransactionResult", commands["SmartCardEmulation.reportBeginTransactionResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportBeginTransactionResult", "command"), - reportPlainResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportPlainResult"].result.parse(await send("SmartCardEmulation.reportPlainResult", commands["SmartCardEmulation.reportPlainResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportPlainResult", "command"), - reportConnectResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportConnectResult"].result.parse(await send("SmartCardEmulation.reportConnectResult", commands["SmartCardEmulation.reportConnectResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportConnectResult", "command"), - reportDataResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportDataResult"].result.parse(await send("SmartCardEmulation.reportDataResult", commands["SmartCardEmulation.reportDataResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportDataResult", "command"), - reportStatusResult: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportStatusResult"].result.parse(await send("SmartCardEmulation.reportStatusResult", commands["SmartCardEmulation.reportStatusResult"].params.parse(params ?? {}))), "SmartCardEmulation.reportStatusResult", "command"), - reportError: withCdpName(async (params?: unknown) => commands["SmartCardEmulation.reportError"].result.parse(await send("SmartCardEmulation.reportError", commands["SmartCardEmulation.reportError"].params.parse(params ?? {}))), "SmartCardEmulation.reportError", "command"), - establishContextRequested: events["SmartCardEmulation.establishContextRequested"] as CdpEventAlias, - releaseContextRequested: events["SmartCardEmulation.releaseContextRequested"] as CdpEventAlias, - listReadersRequested: events["SmartCardEmulation.listReadersRequested"] as CdpEventAlias, - getStatusChangeRequested: events["SmartCardEmulation.getStatusChangeRequested"] as CdpEventAlias, - cancelRequested: events["SmartCardEmulation.cancelRequested"] as CdpEventAlias, - connectRequested: events["SmartCardEmulation.connectRequested"] as CdpEventAlias, - disconnectRequested: events["SmartCardEmulation.disconnectRequested"] as CdpEventAlias, - transmitRequested: events["SmartCardEmulation.transmitRequested"] as CdpEventAlias, - controlRequested: events["SmartCardEmulation.controlRequested"] as CdpEventAlias, - getAttribRequested: events["SmartCardEmulation.getAttribRequested"] as CdpEventAlias, - setAttribRequested: events["SmartCardEmulation.setAttribRequested"] as CdpEventAlias, - statusRequested: events["SmartCardEmulation.statusRequested"] as CdpEventAlias, - beginTransactionRequested: events["SmartCardEmulation.beginTransactionRequested"] as CdpEventAlias, - endTransactionRequested: events["SmartCardEmulation.endTransactionRequested"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.EnableParams) => + commands["SmartCardEmulation.enable"].result.parse( + await send("SmartCardEmulation.enable", commands["SmartCardEmulation.enable"].params.parse(params ?? {})), + ), + "SmartCardEmulation.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.DisableParams) => + commands["SmartCardEmulation.disable"].result.parse( + await send("SmartCardEmulation.disable", commands["SmartCardEmulation.disable"].params.parse(params ?? {})), + ), + "SmartCardEmulation.disable", + "command", + ), + reportEstablishContextResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportEstablishContextResultParams) => + commands["SmartCardEmulation.reportEstablishContextResult"].result.parse( + await send( + "SmartCardEmulation.reportEstablishContextResult", + commands["SmartCardEmulation.reportEstablishContextResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportEstablishContextResult", + "command", + ), + reportReleaseContextResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportReleaseContextResultParams) => + commands["SmartCardEmulation.reportReleaseContextResult"].result.parse( + await send( + "SmartCardEmulation.reportReleaseContextResult", + commands["SmartCardEmulation.reportReleaseContextResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportReleaseContextResult", + "command", + ), + reportListReadersResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportListReadersResultParams) => + commands["SmartCardEmulation.reportListReadersResult"].result.parse( + await send( + "SmartCardEmulation.reportListReadersResult", + commands["SmartCardEmulation.reportListReadersResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportListReadersResult", + "command", + ), + reportGetStatusChangeResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportGetStatusChangeResultParams) => + commands["SmartCardEmulation.reportGetStatusChangeResult"].result.parse( + await send( + "SmartCardEmulation.reportGetStatusChangeResult", + commands["SmartCardEmulation.reportGetStatusChangeResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportGetStatusChangeResult", + "command", + ), + reportBeginTransactionResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportBeginTransactionResultParams) => + commands["SmartCardEmulation.reportBeginTransactionResult"].result.parse( + await send( + "SmartCardEmulation.reportBeginTransactionResult", + commands["SmartCardEmulation.reportBeginTransactionResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportBeginTransactionResult", + "command", + ), + reportPlainResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportPlainResultParams) => + commands["SmartCardEmulation.reportPlainResult"].result.parse( + await send( + "SmartCardEmulation.reportPlainResult", + commands["SmartCardEmulation.reportPlainResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportPlainResult", + "command", + ), + reportConnectResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportConnectResultParams) => + commands["SmartCardEmulation.reportConnectResult"].result.parse( + await send( + "SmartCardEmulation.reportConnectResult", + commands["SmartCardEmulation.reportConnectResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportConnectResult", + "command", + ), + reportDataResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportDataResultParams) => + commands["SmartCardEmulation.reportDataResult"].result.parse( + await send( + "SmartCardEmulation.reportDataResult", + commands["SmartCardEmulation.reportDataResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportDataResult", + "command", + ), + reportStatusResult: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportStatusResultParams) => + commands["SmartCardEmulation.reportStatusResult"].result.parse( + await send( + "SmartCardEmulation.reportStatusResult", + commands["SmartCardEmulation.reportStatusResult"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportStatusResult", + "command", + ), + reportError: withCdpName( + async (params?: cdp.types.ts.SmartCardEmulation.ReportErrorParams) => + commands["SmartCardEmulation.reportError"].result.parse( + await send( + "SmartCardEmulation.reportError", + commands["SmartCardEmulation.reportError"].params.parse(params ?? {}), + ), + ), + "SmartCardEmulation.reportError", + "command", + ), + establishContextRequested: withCdpName( + events["SmartCardEmulation.establishContextRequested"], + "SmartCardEmulation.establishContextRequested", + "event", + ), + releaseContextRequested: withCdpName( + events["SmartCardEmulation.releaseContextRequested"], + "SmartCardEmulation.releaseContextRequested", + "event", + ), + listReadersRequested: withCdpName( + events["SmartCardEmulation.listReadersRequested"], + "SmartCardEmulation.listReadersRequested", + "event", + ), + getStatusChangeRequested: withCdpName( + events["SmartCardEmulation.getStatusChangeRequested"], + "SmartCardEmulation.getStatusChangeRequested", + "event", + ), + cancelRequested: withCdpName( + events["SmartCardEmulation.cancelRequested"], + "SmartCardEmulation.cancelRequested", + "event", + ), + connectRequested: withCdpName( + events["SmartCardEmulation.connectRequested"], + "SmartCardEmulation.connectRequested", + "event", + ), + disconnectRequested: withCdpName( + events["SmartCardEmulation.disconnectRequested"], + "SmartCardEmulation.disconnectRequested", + "event", + ), + transmitRequested: withCdpName( + events["SmartCardEmulation.transmitRequested"], + "SmartCardEmulation.transmitRequested", + "event", + ), + controlRequested: withCdpName( + events["SmartCardEmulation.controlRequested"], + "SmartCardEmulation.controlRequested", + "event", + ), + getAttribRequested: withCdpName( + events["SmartCardEmulation.getAttribRequested"], + "SmartCardEmulation.getAttribRequested", + "event", + ), + setAttribRequested: withCdpName( + events["SmartCardEmulation.setAttribRequested"], + "SmartCardEmulation.setAttribRequested", + "event", + ), + statusRequested: withCdpName( + events["SmartCardEmulation.statusRequested"], + "SmartCardEmulation.statusRequested", + "event", + ), + beginTransactionRequested: withCdpName( + events["SmartCardEmulation.beginTransactionRequested"], + "SmartCardEmulation.beginTransactionRequested", + "event", + ), + endTransactionRequested: withCdpName( + events["SmartCardEmulation.endTransactionRequested"], + "SmartCardEmulation.endTransactionRequested", + "event", + ), }, Storage: { - getStorageKeyForFrame: withCdpName(async (params?: unknown) => commands["Storage.getStorageKeyForFrame"].result.parse(await send("Storage.getStorageKeyForFrame", commands["Storage.getStorageKeyForFrame"].params.parse(params ?? {}))), "Storage.getStorageKeyForFrame", "command"), - getStorageKey: withCdpName(async (params?: unknown) => commands["Storage.getStorageKey"].result.parse(await send("Storage.getStorageKey", commands["Storage.getStorageKey"].params.parse(params ?? {}))), "Storage.getStorageKey", "command"), - clearDataForOrigin: withCdpName(async (params?: unknown) => commands["Storage.clearDataForOrigin"].result.parse(await send("Storage.clearDataForOrigin", commands["Storage.clearDataForOrigin"].params.parse(params ?? {}))), "Storage.clearDataForOrigin", "command"), - clearDataForStorageKey: withCdpName(async (params?: unknown) => commands["Storage.clearDataForStorageKey"].result.parse(await send("Storage.clearDataForStorageKey", commands["Storage.clearDataForStorageKey"].params.parse(params ?? {}))), "Storage.clearDataForStorageKey", "command"), - getCookies: withCdpName(async (params?: unknown) => commands["Storage.getCookies"].result.parse(await send("Storage.getCookies", commands["Storage.getCookies"].params.parse(params ?? {}))), "Storage.getCookies", "command"), - setCookies: withCdpName(async (params?: unknown) => commands["Storage.setCookies"].result.parse(await send("Storage.setCookies", commands["Storage.setCookies"].params.parse(params ?? {}))), "Storage.setCookies", "command"), - clearCookies: withCdpName(async (params?: unknown) => commands["Storage.clearCookies"].result.parse(await send("Storage.clearCookies", commands["Storage.clearCookies"].params.parse(params ?? {}))), "Storage.clearCookies", "command"), - getUsageAndQuota: withCdpName(async (params?: unknown) => commands["Storage.getUsageAndQuota"].result.parse(await send("Storage.getUsageAndQuota", commands["Storage.getUsageAndQuota"].params.parse(params ?? {}))), "Storage.getUsageAndQuota", "command"), - overrideQuotaForOrigin: withCdpName(async (params?: unknown) => commands["Storage.overrideQuotaForOrigin"].result.parse(await send("Storage.overrideQuotaForOrigin", commands["Storage.overrideQuotaForOrigin"].params.parse(params ?? {}))), "Storage.overrideQuotaForOrigin", "command"), - trackCacheStorageForOrigin: withCdpName(async (params?: unknown) => commands["Storage.trackCacheStorageForOrigin"].result.parse(await send("Storage.trackCacheStorageForOrigin", commands["Storage.trackCacheStorageForOrigin"].params.parse(params ?? {}))), "Storage.trackCacheStorageForOrigin", "command"), - trackCacheStorageForStorageKey: withCdpName(async (params?: unknown) => commands["Storage.trackCacheStorageForStorageKey"].result.parse(await send("Storage.trackCacheStorageForStorageKey", commands["Storage.trackCacheStorageForStorageKey"].params.parse(params ?? {}))), "Storage.trackCacheStorageForStorageKey", "command"), - trackIndexedDBForOrigin: withCdpName(async (params?: unknown) => commands["Storage.trackIndexedDBForOrigin"].result.parse(await send("Storage.trackIndexedDBForOrigin", commands["Storage.trackIndexedDBForOrigin"].params.parse(params ?? {}))), "Storage.trackIndexedDBForOrigin", "command"), - trackIndexedDBForStorageKey: withCdpName(async (params?: unknown) => commands["Storage.trackIndexedDBForStorageKey"].result.parse(await send("Storage.trackIndexedDBForStorageKey", commands["Storage.trackIndexedDBForStorageKey"].params.parse(params ?? {}))), "Storage.trackIndexedDBForStorageKey", "command"), - untrackCacheStorageForOrigin: withCdpName(async (params?: unknown) => commands["Storage.untrackCacheStorageForOrigin"].result.parse(await send("Storage.untrackCacheStorageForOrigin", commands["Storage.untrackCacheStorageForOrigin"].params.parse(params ?? {}))), "Storage.untrackCacheStorageForOrigin", "command"), - untrackCacheStorageForStorageKey: withCdpName(async (params?: unknown) => commands["Storage.untrackCacheStorageForStorageKey"].result.parse(await send("Storage.untrackCacheStorageForStorageKey", commands["Storage.untrackCacheStorageForStorageKey"].params.parse(params ?? {}))), "Storage.untrackCacheStorageForStorageKey", "command"), - untrackIndexedDBForOrigin: withCdpName(async (params?: unknown) => commands["Storage.untrackIndexedDBForOrigin"].result.parse(await send("Storage.untrackIndexedDBForOrigin", commands["Storage.untrackIndexedDBForOrigin"].params.parse(params ?? {}))), "Storage.untrackIndexedDBForOrigin", "command"), - untrackIndexedDBForStorageKey: withCdpName(async (params?: unknown) => commands["Storage.untrackIndexedDBForStorageKey"].result.parse(await send("Storage.untrackIndexedDBForStorageKey", commands["Storage.untrackIndexedDBForStorageKey"].params.parse(params ?? {}))), "Storage.untrackIndexedDBForStorageKey", "command"), - getTrustTokens: withCdpName(async (params?: unknown) => commands["Storage.getTrustTokens"].result.parse(await send("Storage.getTrustTokens", commands["Storage.getTrustTokens"].params.parse(params ?? {}))), "Storage.getTrustTokens", "command"), - clearTrustTokens: withCdpName(async (params?: unknown) => commands["Storage.clearTrustTokens"].result.parse(await send("Storage.clearTrustTokens", commands["Storage.clearTrustTokens"].params.parse(params ?? {}))), "Storage.clearTrustTokens", "command"), - getInterestGroupDetails: withCdpName(async (params?: unknown) => commands["Storage.getInterestGroupDetails"].result.parse(await send("Storage.getInterestGroupDetails", commands["Storage.getInterestGroupDetails"].params.parse(params ?? {}))), "Storage.getInterestGroupDetails", "command"), - setInterestGroupTracking: withCdpName(async (params?: unknown) => commands["Storage.setInterestGroupTracking"].result.parse(await send("Storage.setInterestGroupTracking", commands["Storage.setInterestGroupTracking"].params.parse(params ?? {}))), "Storage.setInterestGroupTracking", "command"), - setInterestGroupAuctionTracking: withCdpName(async (params?: unknown) => commands["Storage.setInterestGroupAuctionTracking"].result.parse(await send("Storage.setInterestGroupAuctionTracking", commands["Storage.setInterestGroupAuctionTracking"].params.parse(params ?? {}))), "Storage.setInterestGroupAuctionTracking", "command"), - getSharedStorageMetadata: withCdpName(async (params?: unknown) => commands["Storage.getSharedStorageMetadata"].result.parse(await send("Storage.getSharedStorageMetadata", commands["Storage.getSharedStorageMetadata"].params.parse(params ?? {}))), "Storage.getSharedStorageMetadata", "command"), - getSharedStorageEntries: withCdpName(async (params?: unknown) => commands["Storage.getSharedStorageEntries"].result.parse(await send("Storage.getSharedStorageEntries", commands["Storage.getSharedStorageEntries"].params.parse(params ?? {}))), "Storage.getSharedStorageEntries", "command"), - setSharedStorageEntry: withCdpName(async (params?: unknown) => commands["Storage.setSharedStorageEntry"].result.parse(await send("Storage.setSharedStorageEntry", commands["Storage.setSharedStorageEntry"].params.parse(params ?? {}))), "Storage.setSharedStorageEntry", "command"), - deleteSharedStorageEntry: withCdpName(async (params?: unknown) => commands["Storage.deleteSharedStorageEntry"].result.parse(await send("Storage.deleteSharedStorageEntry", commands["Storage.deleteSharedStorageEntry"].params.parse(params ?? {}))), "Storage.deleteSharedStorageEntry", "command"), - clearSharedStorageEntries: withCdpName(async (params?: unknown) => commands["Storage.clearSharedStorageEntries"].result.parse(await send("Storage.clearSharedStorageEntries", commands["Storage.clearSharedStorageEntries"].params.parse(params ?? {}))), "Storage.clearSharedStorageEntries", "command"), - resetSharedStorageBudget: withCdpName(async (params?: unknown) => commands["Storage.resetSharedStorageBudget"].result.parse(await send("Storage.resetSharedStorageBudget", commands["Storage.resetSharedStorageBudget"].params.parse(params ?? {}))), "Storage.resetSharedStorageBudget", "command"), - setSharedStorageTracking: withCdpName(async (params?: unknown) => commands["Storage.setSharedStorageTracking"].result.parse(await send("Storage.setSharedStorageTracking", commands["Storage.setSharedStorageTracking"].params.parse(params ?? {}))), "Storage.setSharedStorageTracking", "command"), - setStorageBucketTracking: withCdpName(async (params?: unknown) => commands["Storage.setStorageBucketTracking"].result.parse(await send("Storage.setStorageBucketTracking", commands["Storage.setStorageBucketTracking"].params.parse(params ?? {}))), "Storage.setStorageBucketTracking", "command"), - deleteStorageBucket: withCdpName(async (params?: unknown) => commands["Storage.deleteStorageBucket"].result.parse(await send("Storage.deleteStorageBucket", commands["Storage.deleteStorageBucket"].params.parse(params ?? {}))), "Storage.deleteStorageBucket", "command"), - runBounceTrackingMitigations: withCdpName(async (params?: unknown) => commands["Storage.runBounceTrackingMitigations"].result.parse(await send("Storage.runBounceTrackingMitigations", commands["Storage.runBounceTrackingMitigations"].params.parse(params ?? {}))), "Storage.runBounceTrackingMitigations", "command"), - getRelatedWebsiteSets: withCdpName(async (params?: unknown) => commands["Storage.getRelatedWebsiteSets"].result.parse(await send("Storage.getRelatedWebsiteSets", commands["Storage.getRelatedWebsiteSets"].params.parse(params ?? {}))), "Storage.getRelatedWebsiteSets", "command"), - setProtectedAudienceKAnonymity: withCdpName(async (params?: unknown) => commands["Storage.setProtectedAudienceKAnonymity"].result.parse(await send("Storage.setProtectedAudienceKAnonymity", commands["Storage.setProtectedAudienceKAnonymity"].params.parse(params ?? {}))), "Storage.setProtectedAudienceKAnonymity", "command"), - cacheStorageContentUpdated: events["Storage.cacheStorageContentUpdated"] as CdpEventAlias, - cacheStorageListUpdated: events["Storage.cacheStorageListUpdated"] as CdpEventAlias, - indexedDBContentUpdated: events["Storage.indexedDBContentUpdated"] as CdpEventAlias, - indexedDBListUpdated: events["Storage.indexedDBListUpdated"] as CdpEventAlias, - interestGroupAccessed: events["Storage.interestGroupAccessed"] as CdpEventAlias, - interestGroupAuctionEventOccurred: events["Storage.interestGroupAuctionEventOccurred"] as CdpEventAlias, - interestGroupAuctionNetworkRequestCreated: events["Storage.interestGroupAuctionNetworkRequestCreated"] as CdpEventAlias, - sharedStorageAccessed: events["Storage.sharedStorageAccessed"] as CdpEventAlias, - sharedStorageWorkletOperationExecutionFinished: events["Storage.sharedStorageWorkletOperationExecutionFinished"] as CdpEventAlias, - storageBucketCreatedOrUpdated: events["Storage.storageBucketCreatedOrUpdated"] as CdpEventAlias, - storageBucketDeleted: events["Storage.storageBucketDeleted"] as CdpEventAlias, + getStorageKeyForFrame: withCdpName( + async (params?: cdp.types.ts.Storage.GetStorageKeyForFrameParams) => + commands["Storage.getStorageKeyForFrame"].result.parse( + await send( + "Storage.getStorageKeyForFrame", + commands["Storage.getStorageKeyForFrame"].params.parse(params ?? {}), + ), + ), + "Storage.getStorageKeyForFrame", + "command", + ), + getStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.GetStorageKeyParams) => + commands["Storage.getStorageKey"].result.parse( + await send("Storage.getStorageKey", commands["Storage.getStorageKey"].params.parse(params ?? {})), + ), + "Storage.getStorageKey", + "command", + ), + clearDataForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.ClearDataForOriginParams) => + commands["Storage.clearDataForOrigin"].result.parse( + await send("Storage.clearDataForOrigin", commands["Storage.clearDataForOrigin"].params.parse(params ?? {})), + ), + "Storage.clearDataForOrigin", + "command", + ), + clearDataForStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.ClearDataForStorageKeyParams) => + commands["Storage.clearDataForStorageKey"].result.parse( + await send( + "Storage.clearDataForStorageKey", + commands["Storage.clearDataForStorageKey"].params.parse(params ?? {}), + ), + ), + "Storage.clearDataForStorageKey", + "command", + ), + getCookies: withCdpName( + async (params?: cdp.types.ts.Storage.GetCookiesParams) => + commands["Storage.getCookies"].result.parse( + await send("Storage.getCookies", commands["Storage.getCookies"].params.parse(params ?? {})), + ), + "Storage.getCookies", + "command", + ), + setCookies: withCdpName( + async (params?: cdp.types.ts.Storage.SetCookiesParams) => + commands["Storage.setCookies"].result.parse( + await send("Storage.setCookies", commands["Storage.setCookies"].params.parse(params ?? {})), + ), + "Storage.setCookies", + "command", + ), + clearCookies: withCdpName( + async (params?: cdp.types.ts.Storage.ClearCookiesParams) => + commands["Storage.clearCookies"].result.parse( + await send("Storage.clearCookies", commands["Storage.clearCookies"].params.parse(params ?? {})), + ), + "Storage.clearCookies", + "command", + ), + getUsageAndQuota: withCdpName( + async (params?: cdp.types.ts.Storage.GetUsageAndQuotaParams) => + commands["Storage.getUsageAndQuota"].result.parse( + await send("Storage.getUsageAndQuota", commands["Storage.getUsageAndQuota"].params.parse(params ?? {})), + ), + "Storage.getUsageAndQuota", + "command", + ), + overrideQuotaForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.OverrideQuotaForOriginParams) => + commands["Storage.overrideQuotaForOrigin"].result.parse( + await send( + "Storage.overrideQuotaForOrigin", + commands["Storage.overrideQuotaForOrigin"].params.parse(params ?? {}), + ), + ), + "Storage.overrideQuotaForOrigin", + "command", + ), + trackCacheStorageForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.TrackCacheStorageForOriginParams) => + commands["Storage.trackCacheStorageForOrigin"].result.parse( + await send( + "Storage.trackCacheStorageForOrigin", + commands["Storage.trackCacheStorageForOrigin"].params.parse(params ?? {}), + ), + ), + "Storage.trackCacheStorageForOrigin", + "command", + ), + trackCacheStorageForStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.TrackCacheStorageForStorageKeyParams) => + commands["Storage.trackCacheStorageForStorageKey"].result.parse( + await send( + "Storage.trackCacheStorageForStorageKey", + commands["Storage.trackCacheStorageForStorageKey"].params.parse(params ?? {}), + ), + ), + "Storage.trackCacheStorageForStorageKey", + "command", + ), + trackIndexedDBForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.TrackIndexedDBForOriginParams) => + commands["Storage.trackIndexedDBForOrigin"].result.parse( + await send( + "Storage.trackIndexedDBForOrigin", + commands["Storage.trackIndexedDBForOrigin"].params.parse(params ?? {}), + ), + ), + "Storage.trackIndexedDBForOrigin", + "command", + ), + trackIndexedDBForStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.TrackIndexedDBForStorageKeyParams) => + commands["Storage.trackIndexedDBForStorageKey"].result.parse( + await send( + "Storage.trackIndexedDBForStorageKey", + commands["Storage.trackIndexedDBForStorageKey"].params.parse(params ?? {}), + ), + ), + "Storage.trackIndexedDBForStorageKey", + "command", + ), + untrackCacheStorageForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.UntrackCacheStorageForOriginParams) => + commands["Storage.untrackCacheStorageForOrigin"].result.parse( + await send( + "Storage.untrackCacheStorageForOrigin", + commands["Storage.untrackCacheStorageForOrigin"].params.parse(params ?? {}), + ), + ), + "Storage.untrackCacheStorageForOrigin", + "command", + ), + untrackCacheStorageForStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.UntrackCacheStorageForStorageKeyParams) => + commands["Storage.untrackCacheStorageForStorageKey"].result.parse( + await send( + "Storage.untrackCacheStorageForStorageKey", + commands["Storage.untrackCacheStorageForStorageKey"].params.parse(params ?? {}), + ), + ), + "Storage.untrackCacheStorageForStorageKey", + "command", + ), + untrackIndexedDBForOrigin: withCdpName( + async (params?: cdp.types.ts.Storage.UntrackIndexedDBForOriginParams) => + commands["Storage.untrackIndexedDBForOrigin"].result.parse( + await send( + "Storage.untrackIndexedDBForOrigin", + commands["Storage.untrackIndexedDBForOrigin"].params.parse(params ?? {}), + ), + ), + "Storage.untrackIndexedDBForOrigin", + "command", + ), + untrackIndexedDBForStorageKey: withCdpName( + async (params?: cdp.types.ts.Storage.UntrackIndexedDBForStorageKeyParams) => + commands["Storage.untrackIndexedDBForStorageKey"].result.parse( + await send( + "Storage.untrackIndexedDBForStorageKey", + commands["Storage.untrackIndexedDBForStorageKey"].params.parse(params ?? {}), + ), + ), + "Storage.untrackIndexedDBForStorageKey", + "command", + ), + getTrustTokens: withCdpName( + async (params?: cdp.types.ts.Storage.GetTrustTokensParams) => + commands["Storage.getTrustTokens"].result.parse( + await send("Storage.getTrustTokens", commands["Storage.getTrustTokens"].params.parse(params ?? {})), + ), + "Storage.getTrustTokens", + "command", + ), + clearTrustTokens: withCdpName( + async (params?: cdp.types.ts.Storage.ClearTrustTokensParams) => + commands["Storage.clearTrustTokens"].result.parse( + await send("Storage.clearTrustTokens", commands["Storage.clearTrustTokens"].params.parse(params ?? {})), + ), + "Storage.clearTrustTokens", + "command", + ), + getInterestGroupDetails: withCdpName( + async (params?: cdp.types.ts.Storage.GetInterestGroupDetailsParams) => + commands["Storage.getInterestGroupDetails"].result.parse( + await send( + "Storage.getInterestGroupDetails", + commands["Storage.getInterestGroupDetails"].params.parse(params ?? {}), + ), + ), + "Storage.getInterestGroupDetails", + "command", + ), + setInterestGroupTracking: withCdpName( + async (params?: cdp.types.ts.Storage.SetInterestGroupTrackingParams) => + commands["Storage.setInterestGroupTracking"].result.parse( + await send( + "Storage.setInterestGroupTracking", + commands["Storage.setInterestGroupTracking"].params.parse(params ?? {}), + ), + ), + "Storage.setInterestGroupTracking", + "command", + ), + setInterestGroupAuctionTracking: withCdpName( + async (params?: cdp.types.ts.Storage.SetInterestGroupAuctionTrackingParams) => + commands["Storage.setInterestGroupAuctionTracking"].result.parse( + await send( + "Storage.setInterestGroupAuctionTracking", + commands["Storage.setInterestGroupAuctionTracking"].params.parse(params ?? {}), + ), + ), + "Storage.setInterestGroupAuctionTracking", + "command", + ), + getSharedStorageMetadata: withCdpName( + async (params?: cdp.types.ts.Storage.GetSharedStorageMetadataParams) => + commands["Storage.getSharedStorageMetadata"].result.parse( + await send( + "Storage.getSharedStorageMetadata", + commands["Storage.getSharedStorageMetadata"].params.parse(params ?? {}), + ), + ), + "Storage.getSharedStorageMetadata", + "command", + ), + getSharedStorageEntries: withCdpName( + async (params?: cdp.types.ts.Storage.GetSharedStorageEntriesParams) => + commands["Storage.getSharedStorageEntries"].result.parse( + await send( + "Storage.getSharedStorageEntries", + commands["Storage.getSharedStorageEntries"].params.parse(params ?? {}), + ), + ), + "Storage.getSharedStorageEntries", + "command", + ), + setSharedStorageEntry: withCdpName( + async (params?: cdp.types.ts.Storage.SetSharedStorageEntryParams) => + commands["Storage.setSharedStorageEntry"].result.parse( + await send( + "Storage.setSharedStorageEntry", + commands["Storage.setSharedStorageEntry"].params.parse(params ?? {}), + ), + ), + "Storage.setSharedStorageEntry", + "command", + ), + deleteSharedStorageEntry: withCdpName( + async (params?: cdp.types.ts.Storage.DeleteSharedStorageEntryParams) => + commands["Storage.deleteSharedStorageEntry"].result.parse( + await send( + "Storage.deleteSharedStorageEntry", + commands["Storage.deleteSharedStorageEntry"].params.parse(params ?? {}), + ), + ), + "Storage.deleteSharedStorageEntry", + "command", + ), + clearSharedStorageEntries: withCdpName( + async (params?: cdp.types.ts.Storage.ClearSharedStorageEntriesParams) => + commands["Storage.clearSharedStorageEntries"].result.parse( + await send( + "Storage.clearSharedStorageEntries", + commands["Storage.clearSharedStorageEntries"].params.parse(params ?? {}), + ), + ), + "Storage.clearSharedStorageEntries", + "command", + ), + resetSharedStorageBudget: withCdpName( + async (params?: cdp.types.ts.Storage.ResetSharedStorageBudgetParams) => + commands["Storage.resetSharedStorageBudget"].result.parse( + await send( + "Storage.resetSharedStorageBudget", + commands["Storage.resetSharedStorageBudget"].params.parse(params ?? {}), + ), + ), + "Storage.resetSharedStorageBudget", + "command", + ), + setSharedStorageTracking: withCdpName( + async (params?: cdp.types.ts.Storage.SetSharedStorageTrackingParams) => + commands["Storage.setSharedStorageTracking"].result.parse( + await send( + "Storage.setSharedStorageTracking", + commands["Storage.setSharedStorageTracking"].params.parse(params ?? {}), + ), + ), + "Storage.setSharedStorageTracking", + "command", + ), + setStorageBucketTracking: withCdpName( + async (params?: cdp.types.ts.Storage.SetStorageBucketTrackingParams) => + commands["Storage.setStorageBucketTracking"].result.parse( + await send( + "Storage.setStorageBucketTracking", + commands["Storage.setStorageBucketTracking"].params.parse(params ?? {}), + ), + ), + "Storage.setStorageBucketTracking", + "command", + ), + deleteStorageBucket: withCdpName( + async (params?: cdp.types.ts.Storage.DeleteStorageBucketParams) => + commands["Storage.deleteStorageBucket"].result.parse( + await send( + "Storage.deleteStorageBucket", + commands["Storage.deleteStorageBucket"].params.parse(params ?? {}), + ), + ), + "Storage.deleteStorageBucket", + "command", + ), + runBounceTrackingMitigations: withCdpName( + async (params?: cdp.types.ts.Storage.RunBounceTrackingMitigationsParams) => + commands["Storage.runBounceTrackingMitigations"].result.parse( + await send( + "Storage.runBounceTrackingMitigations", + commands["Storage.runBounceTrackingMitigations"].params.parse(params ?? {}), + ), + ), + "Storage.runBounceTrackingMitigations", + "command", + ), + getRelatedWebsiteSets: withCdpName( + async (params?: cdp.types.ts.Storage.GetRelatedWebsiteSetsParams) => + commands["Storage.getRelatedWebsiteSets"].result.parse( + await send( + "Storage.getRelatedWebsiteSets", + commands["Storage.getRelatedWebsiteSets"].params.parse(params ?? {}), + ), + ), + "Storage.getRelatedWebsiteSets", + "command", + ), + setProtectedAudienceKAnonymity: withCdpName( + async (params?: cdp.types.ts.Storage.SetProtectedAudienceKAnonymityParams) => + commands["Storage.setProtectedAudienceKAnonymity"].result.parse( + await send( + "Storage.setProtectedAudienceKAnonymity", + commands["Storage.setProtectedAudienceKAnonymity"].params.parse(params ?? {}), + ), + ), + "Storage.setProtectedAudienceKAnonymity", + "command", + ), + cacheStorageContentUpdated: withCdpName( + events["Storage.cacheStorageContentUpdated"], + "Storage.cacheStorageContentUpdated", + "event", + ), + cacheStorageListUpdated: withCdpName( + events["Storage.cacheStorageListUpdated"], + "Storage.cacheStorageListUpdated", + "event", + ), + indexedDBContentUpdated: withCdpName( + events["Storage.indexedDBContentUpdated"], + "Storage.indexedDBContentUpdated", + "event", + ), + indexedDBListUpdated: withCdpName( + events["Storage.indexedDBListUpdated"], + "Storage.indexedDBListUpdated", + "event", + ), + interestGroupAccessed: withCdpName( + events["Storage.interestGroupAccessed"], + "Storage.interestGroupAccessed", + "event", + ), + interestGroupAuctionEventOccurred: withCdpName( + events["Storage.interestGroupAuctionEventOccurred"], + "Storage.interestGroupAuctionEventOccurred", + "event", + ), + interestGroupAuctionNetworkRequestCreated: withCdpName( + events["Storage.interestGroupAuctionNetworkRequestCreated"], + "Storage.interestGroupAuctionNetworkRequestCreated", + "event", + ), + sharedStorageAccessed: withCdpName( + events["Storage.sharedStorageAccessed"], + "Storage.sharedStorageAccessed", + "event", + ), + sharedStorageWorkletOperationExecutionFinished: withCdpName( + events["Storage.sharedStorageWorkletOperationExecutionFinished"], + "Storage.sharedStorageWorkletOperationExecutionFinished", + "event", + ), + storageBucketCreatedOrUpdated: withCdpName( + events["Storage.storageBucketCreatedOrUpdated"], + "Storage.storageBucketCreatedOrUpdated", + "event", + ), + storageBucketDeleted: withCdpName( + events["Storage.storageBucketDeleted"], + "Storage.storageBucketDeleted", + "event", + ), }, SystemInfo: { - getInfo: withCdpName(async (params?: unknown) => commands["SystemInfo.getInfo"].result.parse(await send("SystemInfo.getInfo", commands["SystemInfo.getInfo"].params.parse(params ?? {}))), "SystemInfo.getInfo", "command"), - getFeatureState: withCdpName(async (params?: unknown) => commands["SystemInfo.getFeatureState"].result.parse(await send("SystemInfo.getFeatureState", commands["SystemInfo.getFeatureState"].params.parse(params ?? {}))), "SystemInfo.getFeatureState", "command"), - getProcessInfo: withCdpName(async (params?: unknown) => commands["SystemInfo.getProcessInfo"].result.parse(await send("SystemInfo.getProcessInfo", commands["SystemInfo.getProcessInfo"].params.parse(params ?? {}))), "SystemInfo.getProcessInfo", "command"), + getInfo: withCdpName( + async (params?: cdp.types.ts.SystemInfo.GetInfoParams) => + commands["SystemInfo.getInfo"].result.parse( + await send("SystemInfo.getInfo", commands["SystemInfo.getInfo"].params.parse(params ?? {})), + ), + "SystemInfo.getInfo", + "command", + ), + getFeatureState: withCdpName( + async (params?: cdp.types.ts.SystemInfo.GetFeatureStateParams) => + commands["SystemInfo.getFeatureState"].result.parse( + await send("SystemInfo.getFeatureState", commands["SystemInfo.getFeatureState"].params.parse(params ?? {})), + ), + "SystemInfo.getFeatureState", + "command", + ), + getProcessInfo: withCdpName( + async (params?: cdp.types.ts.SystemInfo.GetProcessInfoParams) => + commands["SystemInfo.getProcessInfo"].result.parse( + await send("SystemInfo.getProcessInfo", commands["SystemInfo.getProcessInfo"].params.parse(params ?? {})), + ), + "SystemInfo.getProcessInfo", + "command", + ), }, Target: { - activateTarget: withCdpName(async (params?: unknown) => commands["Target.activateTarget"].result.parse(await send("Target.activateTarget", commands["Target.activateTarget"].params.parse(params ?? {}))), "Target.activateTarget", "command"), - attachToTarget: withCdpName(async (params?: unknown) => commands["Target.attachToTarget"].result.parse(await send("Target.attachToTarget", commands["Target.attachToTarget"].params.parse(params ?? {}))), "Target.attachToTarget", "command"), - attachToBrowserTarget: withCdpName(async (params?: unknown) => commands["Target.attachToBrowserTarget"].result.parse(await send("Target.attachToBrowserTarget", commands["Target.attachToBrowserTarget"].params.parse(params ?? {}))), "Target.attachToBrowserTarget", "command"), - closeTarget: withCdpName(async (params?: unknown) => commands["Target.closeTarget"].result.parse(await send("Target.closeTarget", commands["Target.closeTarget"].params.parse(params ?? {}))), "Target.closeTarget", "command"), - exposeDevToolsProtocol: withCdpName(async (params?: unknown) => commands["Target.exposeDevToolsProtocol"].result.parse(await send("Target.exposeDevToolsProtocol", commands["Target.exposeDevToolsProtocol"].params.parse(params ?? {}))), "Target.exposeDevToolsProtocol", "command"), - createBrowserContext: withCdpName(async (params?: unknown) => commands["Target.createBrowserContext"].result.parse(await send("Target.createBrowserContext", commands["Target.createBrowserContext"].params.parse(params ?? {}))), "Target.createBrowserContext", "command"), - getBrowserContexts: withCdpName(async (params?: unknown) => commands["Target.getBrowserContexts"].result.parse(await send("Target.getBrowserContexts", commands["Target.getBrowserContexts"].params.parse(params ?? {}))), "Target.getBrowserContexts", "command"), - createTarget: withCdpName(async (params?: unknown) => commands["Target.createTarget"].result.parse(await send("Target.createTarget", commands["Target.createTarget"].params.parse(params ?? {}))), "Target.createTarget", "command"), - detachFromTarget: withCdpName(async (params?: unknown) => commands["Target.detachFromTarget"].result.parse(await send("Target.detachFromTarget", commands["Target.detachFromTarget"].params.parse(params ?? {}))), "Target.detachFromTarget", "command"), - disposeBrowserContext: withCdpName(async (params?: unknown) => commands["Target.disposeBrowserContext"].result.parse(await send("Target.disposeBrowserContext", commands["Target.disposeBrowserContext"].params.parse(params ?? {}))), "Target.disposeBrowserContext", "command"), - getTargetInfo: withCdpName(async (params?: unknown) => commands["Target.getTargetInfo"].result.parse(await send("Target.getTargetInfo", commands["Target.getTargetInfo"].params.parse(params ?? {}))), "Target.getTargetInfo", "command"), - getTargets: withCdpName(async (params?: unknown) => commands["Target.getTargets"].result.parse(await send("Target.getTargets", commands["Target.getTargets"].params.parse(params ?? {}))), "Target.getTargets", "command"), - sendMessageToTarget: withCdpName(async (params?: unknown) => commands["Target.sendMessageToTarget"].result.parse(await send("Target.sendMessageToTarget", commands["Target.sendMessageToTarget"].params.parse(params ?? {}))), "Target.sendMessageToTarget", "command"), - setAutoAttach: withCdpName(async (params?: unknown) => commands["Target.setAutoAttach"].result.parse(await send("Target.setAutoAttach", commands["Target.setAutoAttach"].params.parse(params ?? {}))), "Target.setAutoAttach", "command"), - autoAttachRelated: withCdpName(async (params?: unknown) => commands["Target.autoAttachRelated"].result.parse(await send("Target.autoAttachRelated", commands["Target.autoAttachRelated"].params.parse(params ?? {}))), "Target.autoAttachRelated", "command"), - setDiscoverTargets: withCdpName(async (params?: unknown) => commands["Target.setDiscoverTargets"].result.parse(await send("Target.setDiscoverTargets", commands["Target.setDiscoverTargets"].params.parse(params ?? {}))), "Target.setDiscoverTargets", "command"), - setRemoteLocations: withCdpName(async (params?: unknown) => commands["Target.setRemoteLocations"].result.parse(await send("Target.setRemoteLocations", commands["Target.setRemoteLocations"].params.parse(params ?? {}))), "Target.setRemoteLocations", "command"), - getDevToolsTarget: withCdpName(async (params?: unknown) => commands["Target.getDevToolsTarget"].result.parse(await send("Target.getDevToolsTarget", commands["Target.getDevToolsTarget"].params.parse(params ?? {}))), "Target.getDevToolsTarget", "command"), - openDevTools: withCdpName(async (params?: unknown) => commands["Target.openDevTools"].result.parse(await send("Target.openDevTools", commands["Target.openDevTools"].params.parse(params ?? {}))), "Target.openDevTools", "command"), - attachedToTarget: events["Target.attachedToTarget"] as CdpEventAlias, - detachedFromTarget: events["Target.detachedFromTarget"] as CdpEventAlias, - receivedMessageFromTarget: events["Target.receivedMessageFromTarget"] as CdpEventAlias, - targetCreated: events["Target.targetCreated"] as CdpEventAlias, - targetDestroyed: events["Target.targetDestroyed"] as CdpEventAlias, - targetCrashed: events["Target.targetCrashed"] as CdpEventAlias, - targetInfoChanged: events["Target.targetInfoChanged"] as CdpEventAlias, + activateTarget: withCdpName( + async (params?: cdp.types.ts.Target.ActivateTargetParams) => + commands["Target.activateTarget"].result.parse( + await send("Target.activateTarget", commands["Target.activateTarget"].params.parse(params ?? {})), + ), + "Target.activateTarget", + "command", + ), + attachToTarget: withCdpName( + async (params?: cdp.types.ts.Target.AttachToTargetParams) => + commands["Target.attachToTarget"].result.parse( + await send("Target.attachToTarget", commands["Target.attachToTarget"].params.parse(params ?? {})), + ), + "Target.attachToTarget", + "command", + ), + attachToBrowserTarget: withCdpName( + async (params?: cdp.types.ts.Target.AttachToBrowserTargetParams) => + commands["Target.attachToBrowserTarget"].result.parse( + await send( + "Target.attachToBrowserTarget", + commands["Target.attachToBrowserTarget"].params.parse(params ?? {}), + ), + ), + "Target.attachToBrowserTarget", + "command", + ), + closeTarget: withCdpName( + async (params?: cdp.types.ts.Target.CloseTargetParams) => + commands["Target.closeTarget"].result.parse( + await send("Target.closeTarget", commands["Target.closeTarget"].params.parse(params ?? {})), + ), + "Target.closeTarget", + "command", + ), + exposeDevToolsProtocol: withCdpName( + async (params?: cdp.types.ts.Target.ExposeDevToolsProtocolParams) => + commands["Target.exposeDevToolsProtocol"].result.parse( + await send( + "Target.exposeDevToolsProtocol", + commands["Target.exposeDevToolsProtocol"].params.parse(params ?? {}), + ), + ), + "Target.exposeDevToolsProtocol", + "command", + ), + createBrowserContext: withCdpName( + async (params?: cdp.types.ts.Target.CreateBrowserContextParams) => + commands["Target.createBrowserContext"].result.parse( + await send( + "Target.createBrowserContext", + commands["Target.createBrowserContext"].params.parse(params ?? {}), + ), + ), + "Target.createBrowserContext", + "command", + ), + getBrowserContexts: withCdpName( + async (params?: cdp.types.ts.Target.GetBrowserContextsParams) => + commands["Target.getBrowserContexts"].result.parse( + await send("Target.getBrowserContexts", commands["Target.getBrowserContexts"].params.parse(params ?? {})), + ), + "Target.getBrowserContexts", + "command", + ), + createTarget: withCdpName( + async (params?: cdp.types.ts.Target.CreateTargetParams) => + commands["Target.createTarget"].result.parse( + await send("Target.createTarget", commands["Target.createTarget"].params.parse(params ?? {})), + ), + "Target.createTarget", + "command", + ), + detachFromTarget: withCdpName( + async (params?: cdp.types.ts.Target.DetachFromTargetParams) => + commands["Target.detachFromTarget"].result.parse( + await send("Target.detachFromTarget", commands["Target.detachFromTarget"].params.parse(params ?? {})), + ), + "Target.detachFromTarget", + "command", + ), + disposeBrowserContext: withCdpName( + async (params?: cdp.types.ts.Target.DisposeBrowserContextParams) => + commands["Target.disposeBrowserContext"].result.parse( + await send( + "Target.disposeBrowserContext", + commands["Target.disposeBrowserContext"].params.parse(params ?? {}), + ), + ), + "Target.disposeBrowserContext", + "command", + ), + getTargetInfo: withCdpName( + async (params?: cdp.types.ts.Target.GetTargetInfoParams) => + commands["Target.getTargetInfo"].result.parse( + await send("Target.getTargetInfo", commands["Target.getTargetInfo"].params.parse(params ?? {})), + ), + "Target.getTargetInfo", + "command", + ), + getTargets: withCdpName( + async (params?: cdp.types.ts.Target.GetTargetsParams) => + commands["Target.getTargets"].result.parse( + await send("Target.getTargets", commands["Target.getTargets"].params.parse(params ?? {})), + ), + "Target.getTargets", + "command", + ), + sendMessageToTarget: withCdpName( + async (params?: cdp.types.ts.Target.SendMessageToTargetParams) => + commands["Target.sendMessageToTarget"].result.parse( + await send("Target.sendMessageToTarget", commands["Target.sendMessageToTarget"].params.parse(params ?? {})), + ), + "Target.sendMessageToTarget", + "command", + ), + setAutoAttach: withCdpName( + async (params?: cdp.types.ts.Target.SetAutoAttachParams) => + commands["Target.setAutoAttach"].result.parse( + await send("Target.setAutoAttach", commands["Target.setAutoAttach"].params.parse(params ?? {})), + ), + "Target.setAutoAttach", + "command", + ), + autoAttachRelated: withCdpName( + async (params?: cdp.types.ts.Target.AutoAttachRelatedParams) => + commands["Target.autoAttachRelated"].result.parse( + await send("Target.autoAttachRelated", commands["Target.autoAttachRelated"].params.parse(params ?? {})), + ), + "Target.autoAttachRelated", + "command", + ), + setDiscoverTargets: withCdpName( + async (params?: cdp.types.ts.Target.SetDiscoverTargetsParams) => + commands["Target.setDiscoverTargets"].result.parse( + await send("Target.setDiscoverTargets", commands["Target.setDiscoverTargets"].params.parse(params ?? {})), + ), + "Target.setDiscoverTargets", + "command", + ), + setRemoteLocations: withCdpName( + async (params?: cdp.types.ts.Target.SetRemoteLocationsParams) => + commands["Target.setRemoteLocations"].result.parse( + await send("Target.setRemoteLocations", commands["Target.setRemoteLocations"].params.parse(params ?? {})), + ), + "Target.setRemoteLocations", + "command", + ), + getDevToolsTarget: withCdpName( + async (params?: cdp.types.ts.Target.GetDevToolsTargetParams) => + commands["Target.getDevToolsTarget"].result.parse( + await send("Target.getDevToolsTarget", commands["Target.getDevToolsTarget"].params.parse(params ?? {})), + ), + "Target.getDevToolsTarget", + "command", + ), + openDevTools: withCdpName( + async (params?: cdp.types.ts.Target.OpenDevToolsParams) => + commands["Target.openDevTools"].result.parse( + await send("Target.openDevTools", commands["Target.openDevTools"].params.parse(params ?? {})), + ), + "Target.openDevTools", + "command", + ), + attachedToTarget: withCdpName(events["Target.attachedToTarget"], "Target.attachedToTarget", "event"), + detachedFromTarget: withCdpName(events["Target.detachedFromTarget"], "Target.detachedFromTarget", "event"), + receivedMessageFromTarget: withCdpName( + events["Target.receivedMessageFromTarget"], + "Target.receivedMessageFromTarget", + "event", + ), + targetCreated: withCdpName(events["Target.targetCreated"], "Target.targetCreated", "event"), + targetDestroyed: withCdpName(events["Target.targetDestroyed"], "Target.targetDestroyed", "event"), + targetCrashed: withCdpName(events["Target.targetCrashed"], "Target.targetCrashed", "event"), + targetInfoChanged: withCdpName(events["Target.targetInfoChanged"], "Target.targetInfoChanged", "event"), }, Tethering: { - bind: withCdpName(async (params?: unknown) => commands["Tethering.bind"].result.parse(await send("Tethering.bind", commands["Tethering.bind"].params.parse(params ?? {}))), "Tethering.bind", "command"), - unbind: withCdpName(async (params?: unknown) => commands["Tethering.unbind"].result.parse(await send("Tethering.unbind", commands["Tethering.unbind"].params.parse(params ?? {}))), "Tethering.unbind", "command"), - accepted: events["Tethering.accepted"] as CdpEventAlias, + bind: withCdpName( + async (params?: cdp.types.ts.Tethering.BindParams) => + commands["Tethering.bind"].result.parse( + await send("Tethering.bind", commands["Tethering.bind"].params.parse(params ?? {})), + ), + "Tethering.bind", + "command", + ), + unbind: withCdpName( + async (params?: cdp.types.ts.Tethering.UnbindParams) => + commands["Tethering.unbind"].result.parse( + await send("Tethering.unbind", commands["Tethering.unbind"].params.parse(params ?? {})), + ), + "Tethering.unbind", + "command", + ), + accepted: withCdpName(events["Tethering.accepted"], "Tethering.accepted", "event"), }, Tracing: { - end: withCdpName(async (params?: unknown) => commands["Tracing.end"].result.parse(await send("Tracing.end", commands["Tracing.end"].params.parse(params ?? {}))), "Tracing.end", "command"), - getCategories: withCdpName(async (params?: unknown) => commands["Tracing.getCategories"].result.parse(await send("Tracing.getCategories", commands["Tracing.getCategories"].params.parse(params ?? {}))), "Tracing.getCategories", "command"), - getTrackEventDescriptor: withCdpName(async (params?: unknown) => commands["Tracing.getTrackEventDescriptor"].result.parse(await send("Tracing.getTrackEventDescriptor", commands["Tracing.getTrackEventDescriptor"].params.parse(params ?? {}))), "Tracing.getTrackEventDescriptor", "command"), - recordClockSyncMarker: withCdpName(async (params?: unknown) => commands["Tracing.recordClockSyncMarker"].result.parse(await send("Tracing.recordClockSyncMarker", commands["Tracing.recordClockSyncMarker"].params.parse(params ?? {}))), "Tracing.recordClockSyncMarker", "command"), - requestMemoryDump: withCdpName(async (params?: unknown) => commands["Tracing.requestMemoryDump"].result.parse(await send("Tracing.requestMemoryDump", commands["Tracing.requestMemoryDump"].params.parse(params ?? {}))), "Tracing.requestMemoryDump", "command"), - start: withCdpName(async (params?: unknown) => commands["Tracing.start"].result.parse(await send("Tracing.start", commands["Tracing.start"].params.parse(params ?? {}))), "Tracing.start", "command"), - bufferUsage: events["Tracing.bufferUsage"] as CdpEventAlias, - dataCollected: events["Tracing.dataCollected"] as CdpEventAlias, - tracingComplete: events["Tracing.tracingComplete"] as CdpEventAlias, + end: withCdpName( + async (params?: cdp.types.ts.Tracing.EndParams) => + commands["Tracing.end"].result.parse( + await send("Tracing.end", commands["Tracing.end"].params.parse(params ?? {})), + ), + "Tracing.end", + "command", + ), + getCategories: withCdpName( + async (params?: cdp.types.ts.Tracing.GetCategoriesParams) => + commands["Tracing.getCategories"].result.parse( + await send("Tracing.getCategories", commands["Tracing.getCategories"].params.parse(params ?? {})), + ), + "Tracing.getCategories", + "command", + ), + getTrackEventDescriptor: withCdpName( + async (params?: cdp.types.ts.Tracing.GetTrackEventDescriptorParams) => + commands["Tracing.getTrackEventDescriptor"].result.parse( + await send( + "Tracing.getTrackEventDescriptor", + commands["Tracing.getTrackEventDescriptor"].params.parse(params ?? {}), + ), + ), + "Tracing.getTrackEventDescriptor", + "command", + ), + recordClockSyncMarker: withCdpName( + async (params?: cdp.types.ts.Tracing.RecordClockSyncMarkerParams) => + commands["Tracing.recordClockSyncMarker"].result.parse( + await send( + "Tracing.recordClockSyncMarker", + commands["Tracing.recordClockSyncMarker"].params.parse(params ?? {}), + ), + ), + "Tracing.recordClockSyncMarker", + "command", + ), + requestMemoryDump: withCdpName( + async (params?: cdp.types.ts.Tracing.RequestMemoryDumpParams) => + commands["Tracing.requestMemoryDump"].result.parse( + await send("Tracing.requestMemoryDump", commands["Tracing.requestMemoryDump"].params.parse(params ?? {})), + ), + "Tracing.requestMemoryDump", + "command", + ), + start: withCdpName( + async (params?: cdp.types.ts.Tracing.StartParams) => + commands["Tracing.start"].result.parse( + await send("Tracing.start", commands["Tracing.start"].params.parse(params ?? {})), + ), + "Tracing.start", + "command", + ), + bufferUsage: withCdpName(events["Tracing.bufferUsage"], "Tracing.bufferUsage", "event"), + dataCollected: withCdpName(events["Tracing.dataCollected"], "Tracing.dataCollected", "event"), + tracingComplete: withCdpName(events["Tracing.tracingComplete"], "Tracing.tracingComplete", "event"), }, WebAudio: { - enable: withCdpName(async (params?: unknown) => commands["WebAudio.enable"].result.parse(await send("WebAudio.enable", commands["WebAudio.enable"].params.parse(params ?? {}))), "WebAudio.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["WebAudio.disable"].result.parse(await send("WebAudio.disable", commands["WebAudio.disable"].params.parse(params ?? {}))), "WebAudio.disable", "command"), - getRealtimeData: withCdpName(async (params?: unknown) => commands["WebAudio.getRealtimeData"].result.parse(await send("WebAudio.getRealtimeData", commands["WebAudio.getRealtimeData"].params.parse(params ?? {}))), "WebAudio.getRealtimeData", "command"), - contextCreated: events["WebAudio.contextCreated"] as CdpEventAlias, - contextWillBeDestroyed: events["WebAudio.contextWillBeDestroyed"] as CdpEventAlias, - contextChanged: events["WebAudio.contextChanged"] as CdpEventAlias, - audioListenerCreated: events["WebAudio.audioListenerCreated"] as CdpEventAlias, - audioListenerWillBeDestroyed: events["WebAudio.audioListenerWillBeDestroyed"] as CdpEventAlias, - audioNodeCreated: events["WebAudio.audioNodeCreated"] as CdpEventAlias, - audioNodeWillBeDestroyed: events["WebAudio.audioNodeWillBeDestroyed"] as CdpEventAlias, - audioParamCreated: events["WebAudio.audioParamCreated"] as CdpEventAlias, - audioParamWillBeDestroyed: events["WebAudio.audioParamWillBeDestroyed"] as CdpEventAlias, - nodesConnected: events["WebAudio.nodesConnected"] as CdpEventAlias, - nodesDisconnected: events["WebAudio.nodesDisconnected"] as CdpEventAlias, - nodeParamConnected: events["WebAudio.nodeParamConnected"] as CdpEventAlias, - nodeParamDisconnected: events["WebAudio.nodeParamDisconnected"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.WebAudio.EnableParams) => + commands["WebAudio.enable"].result.parse( + await send("WebAudio.enable", commands["WebAudio.enable"].params.parse(params ?? {})), + ), + "WebAudio.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.WebAudio.DisableParams) => + commands["WebAudio.disable"].result.parse( + await send("WebAudio.disable", commands["WebAudio.disable"].params.parse(params ?? {})), + ), + "WebAudio.disable", + "command", + ), + getRealtimeData: withCdpName( + async (params?: cdp.types.ts.WebAudio.GetRealtimeDataParams) => + commands["WebAudio.getRealtimeData"].result.parse( + await send("WebAudio.getRealtimeData", commands["WebAudio.getRealtimeData"].params.parse(params ?? {})), + ), + "WebAudio.getRealtimeData", + "command", + ), + contextCreated: withCdpName(events["WebAudio.contextCreated"], "WebAudio.contextCreated", "event"), + contextWillBeDestroyed: withCdpName( + events["WebAudio.contextWillBeDestroyed"], + "WebAudio.contextWillBeDestroyed", + "event", + ), + contextChanged: withCdpName(events["WebAudio.contextChanged"], "WebAudio.contextChanged", "event"), + audioListenerCreated: withCdpName( + events["WebAudio.audioListenerCreated"], + "WebAudio.audioListenerCreated", + "event", + ), + audioListenerWillBeDestroyed: withCdpName( + events["WebAudio.audioListenerWillBeDestroyed"], + "WebAudio.audioListenerWillBeDestroyed", + "event", + ), + audioNodeCreated: withCdpName(events["WebAudio.audioNodeCreated"], "WebAudio.audioNodeCreated", "event"), + audioNodeWillBeDestroyed: withCdpName( + events["WebAudio.audioNodeWillBeDestroyed"], + "WebAudio.audioNodeWillBeDestroyed", + "event", + ), + audioParamCreated: withCdpName(events["WebAudio.audioParamCreated"], "WebAudio.audioParamCreated", "event"), + audioParamWillBeDestroyed: withCdpName( + events["WebAudio.audioParamWillBeDestroyed"], + "WebAudio.audioParamWillBeDestroyed", + "event", + ), + nodesConnected: withCdpName(events["WebAudio.nodesConnected"], "WebAudio.nodesConnected", "event"), + nodesDisconnected: withCdpName(events["WebAudio.nodesDisconnected"], "WebAudio.nodesDisconnected", "event"), + nodeParamConnected: withCdpName(events["WebAudio.nodeParamConnected"], "WebAudio.nodeParamConnected", "event"), + nodeParamDisconnected: withCdpName( + events["WebAudio.nodeParamDisconnected"], + "WebAudio.nodeParamDisconnected", + "event", + ), }, WebAuthn: { - enable: withCdpName(async (params?: unknown) => commands["WebAuthn.enable"].result.parse(await send("WebAuthn.enable", commands["WebAuthn.enable"].params.parse(params ?? {}))), "WebAuthn.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["WebAuthn.disable"].result.parse(await send("WebAuthn.disable", commands["WebAuthn.disable"].params.parse(params ?? {}))), "WebAuthn.disable", "command"), - addVirtualAuthenticator: withCdpName(async (params?: unknown) => commands["WebAuthn.addVirtualAuthenticator"].result.parse(await send("WebAuthn.addVirtualAuthenticator", commands["WebAuthn.addVirtualAuthenticator"].params.parse(params ?? {}))), "WebAuthn.addVirtualAuthenticator", "command"), - setResponseOverrideBits: withCdpName(async (params?: unknown) => commands["WebAuthn.setResponseOverrideBits"].result.parse(await send("WebAuthn.setResponseOverrideBits", commands["WebAuthn.setResponseOverrideBits"].params.parse(params ?? {}))), "WebAuthn.setResponseOverrideBits", "command"), - removeVirtualAuthenticator: withCdpName(async (params?: unknown) => commands["WebAuthn.removeVirtualAuthenticator"].result.parse(await send("WebAuthn.removeVirtualAuthenticator", commands["WebAuthn.removeVirtualAuthenticator"].params.parse(params ?? {}))), "WebAuthn.removeVirtualAuthenticator", "command"), - addCredential: withCdpName(async (params?: unknown) => commands["WebAuthn.addCredential"].result.parse(await send("WebAuthn.addCredential", commands["WebAuthn.addCredential"].params.parse(params ?? {}))), "WebAuthn.addCredential", "command"), - getCredential: withCdpName(async (params?: unknown) => commands["WebAuthn.getCredential"].result.parse(await send("WebAuthn.getCredential", commands["WebAuthn.getCredential"].params.parse(params ?? {}))), "WebAuthn.getCredential", "command"), - getCredentials: withCdpName(async (params?: unknown) => commands["WebAuthn.getCredentials"].result.parse(await send("WebAuthn.getCredentials", commands["WebAuthn.getCredentials"].params.parse(params ?? {}))), "WebAuthn.getCredentials", "command"), - removeCredential: withCdpName(async (params?: unknown) => commands["WebAuthn.removeCredential"].result.parse(await send("WebAuthn.removeCredential", commands["WebAuthn.removeCredential"].params.parse(params ?? {}))), "WebAuthn.removeCredential", "command"), - clearCredentials: withCdpName(async (params?: unknown) => commands["WebAuthn.clearCredentials"].result.parse(await send("WebAuthn.clearCredentials", commands["WebAuthn.clearCredentials"].params.parse(params ?? {}))), "WebAuthn.clearCredentials", "command"), - setUserVerified: withCdpName(async (params?: unknown) => commands["WebAuthn.setUserVerified"].result.parse(await send("WebAuthn.setUserVerified", commands["WebAuthn.setUserVerified"].params.parse(params ?? {}))), "WebAuthn.setUserVerified", "command"), - setAutomaticPresenceSimulation: withCdpName(async (params?: unknown) => commands["WebAuthn.setAutomaticPresenceSimulation"].result.parse(await send("WebAuthn.setAutomaticPresenceSimulation", commands["WebAuthn.setAutomaticPresenceSimulation"].params.parse(params ?? {}))), "WebAuthn.setAutomaticPresenceSimulation", "command"), - setCredentialProperties: withCdpName(async (params?: unknown) => commands["WebAuthn.setCredentialProperties"].result.parse(await send("WebAuthn.setCredentialProperties", commands["WebAuthn.setCredentialProperties"].params.parse(params ?? {}))), "WebAuthn.setCredentialProperties", "command"), - credentialAdded: events["WebAuthn.credentialAdded"] as CdpEventAlias, - credentialDeleted: events["WebAuthn.credentialDeleted"] as CdpEventAlias, - credentialUpdated: events["WebAuthn.credentialUpdated"] as CdpEventAlias, - credentialAsserted: events["WebAuthn.credentialAsserted"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.WebAuthn.EnableParams) => + commands["WebAuthn.enable"].result.parse( + await send("WebAuthn.enable", commands["WebAuthn.enable"].params.parse(params ?? {})), + ), + "WebAuthn.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.WebAuthn.DisableParams) => + commands["WebAuthn.disable"].result.parse( + await send("WebAuthn.disable", commands["WebAuthn.disable"].params.parse(params ?? {})), + ), + "WebAuthn.disable", + "command", + ), + addVirtualAuthenticator: withCdpName( + async (params?: cdp.types.ts.WebAuthn.AddVirtualAuthenticatorParams) => + commands["WebAuthn.addVirtualAuthenticator"].result.parse( + await send( + "WebAuthn.addVirtualAuthenticator", + commands["WebAuthn.addVirtualAuthenticator"].params.parse(params ?? {}), + ), + ), + "WebAuthn.addVirtualAuthenticator", + "command", + ), + setResponseOverrideBits: withCdpName( + async (params?: cdp.types.ts.WebAuthn.SetResponseOverrideBitsParams) => + commands["WebAuthn.setResponseOverrideBits"].result.parse( + await send( + "WebAuthn.setResponseOverrideBits", + commands["WebAuthn.setResponseOverrideBits"].params.parse(params ?? {}), + ), + ), + "WebAuthn.setResponseOverrideBits", + "command", + ), + removeVirtualAuthenticator: withCdpName( + async (params?: cdp.types.ts.WebAuthn.RemoveVirtualAuthenticatorParams) => + commands["WebAuthn.removeVirtualAuthenticator"].result.parse( + await send( + "WebAuthn.removeVirtualAuthenticator", + commands["WebAuthn.removeVirtualAuthenticator"].params.parse(params ?? {}), + ), + ), + "WebAuthn.removeVirtualAuthenticator", + "command", + ), + addCredential: withCdpName( + async (params?: cdp.types.ts.WebAuthn.AddCredentialParams) => + commands["WebAuthn.addCredential"].result.parse( + await send("WebAuthn.addCredential", commands["WebAuthn.addCredential"].params.parse(params ?? {})), + ), + "WebAuthn.addCredential", + "command", + ), + getCredential: withCdpName( + async (params?: cdp.types.ts.WebAuthn.GetCredentialParams) => + commands["WebAuthn.getCredential"].result.parse( + await send("WebAuthn.getCredential", commands["WebAuthn.getCredential"].params.parse(params ?? {})), + ), + "WebAuthn.getCredential", + "command", + ), + getCredentials: withCdpName( + async (params?: cdp.types.ts.WebAuthn.GetCredentialsParams) => + commands["WebAuthn.getCredentials"].result.parse( + await send("WebAuthn.getCredentials", commands["WebAuthn.getCredentials"].params.parse(params ?? {})), + ), + "WebAuthn.getCredentials", + "command", + ), + removeCredential: withCdpName( + async (params?: cdp.types.ts.WebAuthn.RemoveCredentialParams) => + commands["WebAuthn.removeCredential"].result.parse( + await send("WebAuthn.removeCredential", commands["WebAuthn.removeCredential"].params.parse(params ?? {})), + ), + "WebAuthn.removeCredential", + "command", + ), + clearCredentials: withCdpName( + async (params?: cdp.types.ts.WebAuthn.ClearCredentialsParams) => + commands["WebAuthn.clearCredentials"].result.parse( + await send("WebAuthn.clearCredentials", commands["WebAuthn.clearCredentials"].params.parse(params ?? {})), + ), + "WebAuthn.clearCredentials", + "command", + ), + setUserVerified: withCdpName( + async (params?: cdp.types.ts.WebAuthn.SetUserVerifiedParams) => + commands["WebAuthn.setUserVerified"].result.parse( + await send("WebAuthn.setUserVerified", commands["WebAuthn.setUserVerified"].params.parse(params ?? {})), + ), + "WebAuthn.setUserVerified", + "command", + ), + setAutomaticPresenceSimulation: withCdpName( + async (params?: cdp.types.ts.WebAuthn.SetAutomaticPresenceSimulationParams) => + commands["WebAuthn.setAutomaticPresenceSimulation"].result.parse( + await send( + "WebAuthn.setAutomaticPresenceSimulation", + commands["WebAuthn.setAutomaticPresenceSimulation"].params.parse(params ?? {}), + ), + ), + "WebAuthn.setAutomaticPresenceSimulation", + "command", + ), + setCredentialProperties: withCdpName( + async (params?: cdp.types.ts.WebAuthn.SetCredentialPropertiesParams) => + commands["WebAuthn.setCredentialProperties"].result.parse( + await send( + "WebAuthn.setCredentialProperties", + commands["WebAuthn.setCredentialProperties"].params.parse(params ?? {}), + ), + ), + "WebAuthn.setCredentialProperties", + "command", + ), + credentialAdded: withCdpName(events["WebAuthn.credentialAdded"], "WebAuthn.credentialAdded", "event"), + credentialDeleted: withCdpName(events["WebAuthn.credentialDeleted"], "WebAuthn.credentialDeleted", "event"), + credentialUpdated: withCdpName(events["WebAuthn.credentialUpdated"], "WebAuthn.credentialUpdated", "event"), + credentialAsserted: withCdpName(events["WebAuthn.credentialAsserted"], "WebAuthn.credentialAsserted", "event"), }, WebMCP: { - enable: withCdpName(async (params?: unknown) => commands["WebMCP.enable"].result.parse(await send("WebMCP.enable", commands["WebMCP.enable"].params.parse(params ?? {}))), "WebMCP.enable", "command"), - disable: withCdpName(async (params?: unknown) => commands["WebMCP.disable"].result.parse(await send("WebMCP.disable", commands["WebMCP.disable"].params.parse(params ?? {}))), "WebMCP.disable", "command"), - invokeTool: withCdpName(async (params?: unknown) => commands["WebMCP.invokeTool"].result.parse(await send("WebMCP.invokeTool", commands["WebMCP.invokeTool"].params.parse(params ?? {}))), "WebMCP.invokeTool", "command"), - cancelInvocation: withCdpName(async (params?: unknown) => commands["WebMCP.cancelInvocation"].result.parse(await send("WebMCP.cancelInvocation", commands["WebMCP.cancelInvocation"].params.parse(params ?? {}))), "WebMCP.cancelInvocation", "command"), - toolsAdded: events["WebMCP.toolsAdded"] as CdpEventAlias, - toolsRemoved: events["WebMCP.toolsRemoved"] as CdpEventAlias, - toolInvoked: events["WebMCP.toolInvoked"] as CdpEventAlias, - toolResponded: events["WebMCP.toolResponded"] as CdpEventAlias, + enable: withCdpName( + async (params?: cdp.types.ts.WebMCP.EnableParams) => + commands["WebMCP.enable"].result.parse( + await send("WebMCP.enable", commands["WebMCP.enable"].params.parse(params ?? {})), + ), + "WebMCP.enable", + "command", + ), + disable: withCdpName( + async (params?: cdp.types.ts.WebMCP.DisableParams) => + commands["WebMCP.disable"].result.parse( + await send("WebMCP.disable", commands["WebMCP.disable"].params.parse(params ?? {})), + ), + "WebMCP.disable", + "command", + ), + invokeTool: withCdpName( + async (params?: cdp.types.ts.WebMCP.InvokeToolParams) => + commands["WebMCP.invokeTool"].result.parse( + await send("WebMCP.invokeTool", commands["WebMCP.invokeTool"].params.parse(params ?? {})), + ), + "WebMCP.invokeTool", + "command", + ), + cancelInvocation: withCdpName( + async (params?: cdp.types.ts.WebMCP.CancelInvocationParams) => + commands["WebMCP.cancelInvocation"].result.parse( + await send("WebMCP.cancelInvocation", commands["WebMCP.cancelInvocation"].params.parse(params ?? {})), + ), + "WebMCP.cancelInvocation", + "command", + ), + toolsAdded: withCdpName(events["WebMCP.toolsAdded"], "WebMCP.toolsAdded", "event"), + toolsRemoved: withCdpName(events["WebMCP.toolsRemoved"], "WebMCP.toolsRemoved", "event"), + toolInvoked: withCdpName(events["WebMCP.toolInvoked"], "WebMCP.toolInvoked", "event"), + toolResponded: withCdpName(events["WebMCP.toolResponded"], "WebMCP.toolResponded", "event"), }, Mod: { - evaluate: async (params?: unknown) => { - const parsed = Mod.EvaluateParams.parse(params ?? {}); - return Mod.EvaluateResponse.parse(await send("Mod.evaluate", parsed)); - }, - addCustomCommand: async (params_or_name?: unknown, options: Record = {}) => { - const input = typeof params_or_name === "string" - ? { - name: params_or_name, - expression: options.expression ?? null, - params_schema: options.params_schema ?? null, - result_schema: options.result_schema ?? null, - } - : params_or_name; - const parsed = Mod.AddCustomCommandParams.parse(input ?? {}); - const name = normalizeModCDPName(parsed.name); - const params_schema = normalizeModCDPPayloadSchema(parsed.params_schema); - const result_schema = normalizeModCDPPayloadSchema(parsed.result_schema); - const response = Mod.AddCustomCommandResponse.parse(await send("Mod.addCustomCommand", { ...parsed, name, params_schema: null, result_schema: null })); - hooks.onCustomCommand?.(name, params_schema, result_schema); - return response; - }, - addCustomEvent: async (params_or_name?: unknown, options: Record = {}) => { - const input = typeof params_or_name === "string" - ? { name: params_or_name, event_schema: options.event_schema ?? null } - : params_or_name; - const parsed = Mod.AddCustomEventParams.parse(input ?? {}); - const directSchema = Mod.ZodType.safeParse(parsed); - if (directSchema.success) { - const name = normalizeModCDPName(directSchema.data); - const event_schema = normalizeModCDPPayloadSchema(directSchema.data); - const response = Mod.AddCustomEventResponse.parse(await send("Mod.addCustomEvent", { name, event_schema: null })); + evaluate: withCdpName( + async (params?: cdp.types.ts.Mod.EvaluateParams) => { + const parsed = Mod.EvaluateParams.parse(params ?? {}); + return Mod.EvaluateResponse.parse(await send("Mod.evaluate", parsed)); + }, + "Mod.evaluate", + "command", + ), + addCustomCommand: withCdpName( + async ( + params_or_name?: cdp.types.ts.Mod.AddCustomCommandParams | string, + config: ModCustomCommandConfig = {}, + ) => { + const input = + typeof params_or_name === "string" + ? { + name: params_or_name, + expression: config.expression ?? null, + params_schema: config.params_schema ?? null, + result_schema: config.result_schema ?? null, + } + : params_or_name; + const parsed = Mod.AddCustomCommandParams.parse(input ?? {}); + const name = normalizeModCDPName(parsed.name); + const params_schema = validateZodSchema(parsed.params_schema); + const result_schema = validateZodSchema(parsed.result_schema); + const response = Mod.AddCustomCommandResponse.parse(await send("Mod.addCustomCommand", { ...parsed, name })); + hooks.onCustomCommand?.(name, params_schema, result_schema); + return response; + }, + "Mod.addCustomCommand", + "command", + ), + addCustomEvent: withCdpName( + async ( + params_or_name?: cdp.types.ts.Mod.AddCustomEventParams | string, + config: { event_schema?: z.ZodType | null } = {}, + ) => { + const input = + typeof params_or_name === "string" + ? { name: params_or_name, event_schema: config.event_schema ?? null } + : params_or_name; + const parsed = Mod.AddCustomEventParams.parse(input ?? {}); + const direct_schema = Mod.ZodType.safeParse(parsed); + if (direct_schema.success) { + const name = normalizeModCDPName(direct_schema.data); + const event_schema = validateZodSchema(direct_schema.data); + const response = Mod.AddCustomEventResponse.parse(await send("Mod.addCustomEvent", { name, event_schema })); + hooks.onCustomEvent?.(name, event_schema); + return response; + } + const object_params = Mod.AddCustomEventObjectParams.parse(parsed); + const name = normalizeModCDPName(object_params.name); + const event_schema = validateZodSchema(object_params.event_schema); + const response = Mod.AddCustomEventResponse.parse( + await send("Mod.addCustomEvent", { ...object_params, name, event_schema }), + ); hooks.onCustomEvent?.(name, event_schema); return response; - } - const objectParams = Mod.AddCustomEventObjectParams.parse(parsed); - const name = normalizeModCDPName(objectParams.name); - const event_schema = normalizeModCDPPayloadSchema(objectParams.event_schema); - const response = Mod.AddCustomEventResponse.parse(await send("Mod.addCustomEvent", { ...objectParams, name, event_schema: null })); - hooks.onCustomEvent?.(name, event_schema); - return response; - }, - addMiddleware: async (params?: unknown) => { - const parsed = Mod.AddMiddlewareParams.parse(params ?? {}); - const name = parsed.name == null ? undefined : normalizeModCDPName(parsed.name); - return Mod.AddMiddlewareResponse.parse(await send("Mod.addMiddleware", { ...parsed, name })); - }, - configure: async (params?: unknown) => { - const parsed = Mod.ConfigureParams.parse(params ?? {}); - return Mod.ConfigureResponse.parse(await send("Mod.configure", parsed)); - }, - ping: async (params?: unknown) => { - const parsed = Mod.PingParams.parse(params ?? {}); - return Mod.PingResponse.parse(await send("Mod.ping", parsed)); - }, + }, + "Mod.addCustomEvent", + "command", + ), + addMiddleware: withCdpName( + async (params?: cdp.types.ts.Mod.AddMiddlewareParams) => { + const parsed = Mod.AddMiddlewareParams.parse(params ?? {}); + const name = parsed.name == null ? undefined : normalizeModCDPName(parsed.name); + return Mod.AddMiddlewareResponse.parse(await send("Mod.addMiddleware", { ...parsed, name })); + }, + "Mod.addMiddleware", + "command", + ), + configure: withCdpName( + async (params?: cdp.types.ts.Mod.ConfigureParams) => { + const parsed = Mod.ConfigureParams.parse(params ?? {}); + return Mod.ConfigureResponse.parse(await send("Mod.configure", parsed)); + }, + "Mod.configure", + "command", + ), + ping: withCdpName( + async (params?: cdp.types.ts.Mod.PingParams) => { + const parsed = Mod.PingParams.parse(params ?? {}); + return Mod.PingResponse.parse(await send("Mod.ping", parsed)); + }, + "Mod.ping", + "command", + ), + getTopology: withCdpName( + async (params?: cdp.types.ts.Mod.GetTopologyParams) => { + const parsed = Mod.GetTopologyParams.parse(params ?? {}); + return Mod.GetTopologyResponse.parse(await send("Mod.getTopology", parsed)); + }, + "Mod.getTopology", + "command", + ), + pong: withCdpName(Mod.PongEvent, "Mod.pong", "event"), }, }; } diff --git a/js/src/types/generated/cdp.ts b/js/src/types/generated/cdp.ts index 5b536abb..6469cc2d 100644 --- a/js/src/types/generated/cdp.ts +++ b/js/src/types/generated/cdp.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. import { zod, commands, events, types as runtimeTypes } from "./zod.js"; import type { z } from "zod"; -import type { ModCDPRoutes, ModCDPCustomPayload, ModCDPNamedValue, ModCDPName, ModCDPZodType, ModCDPPayloadShape, ModCDPPayloadSchemaSpec, ModCDPEvaluateParams, ModCDPAddCustomCommandParams, ModCDPAddCustomEventObjectParams, ModCDPAddCustomEventParams, ModCDPAddMiddlewareParams, ModCDPConfigureParams, ModCDPPingParams, ModCDPPongEvent, ModCDPPingLatency, ModCDPCommandParams, ModCDPCommandResult, ModCDPEvaluateResponse, ModCDPAddCustomCommandResponse, ModCDPAddCustomEventResponse, ModCDPAddMiddlewareResponse, ModCDPConfigureResponse, ModCDPPingResponse, ModCDPBindingPayload, ModCDPCustomCommandRegistration, ModCDPCustomEventRegistration, ModCDPMiddlewareRegistration } from "../modcdp.js"; +import type { ModCDPRoutes, ModCDPRouterConfig, ModCDPCustomPayload, ModCDPNamedValue, ModCDPName, ModCDPZodType, ModCDPPayloadShape, ModCDPPayloadSchemaSpec, ModCDPEvaluateParams, ModCDPAddCustomCommandParams, ModCDPAddCustomEventObjectParams, ModCDPAddCustomEventParams, ModCDPAddMiddlewareParams, ModCDPLauncherConfig, ModCDPUpstreamConfig, ModCDPClientConfig, ModCDPDownstreamConfig, ModCDPServerConfig, ModCDPConfigureParams, ModCDPPingParams, ModCDPPongEvent, ModCDPPingLatency, ModCDPGetTopologyParams, ModCDPTopologyFrame, ModCDPTopologyDomRoot, ModCDPTopologyTarget, ModCDPTopologyExecutionContext, ModCDPTopology, ModCDPCommandParams, ModCDPCommandResult, ModCDPEvaluateResponse, ModCDPGetTopologyResponse, ModCDPAddCustomCommandResponse, ModCDPAddCustomEventResponse, ModCDPAddMiddlewareResponse, ModCDPConfigureResponse, ModCDPPingResponse, ModCDPBindingPayload, ModCDPCustomCommandRegistration, ModCDPCustomEventRegistration, ModCDPMiddlewareRegistration } from "../modcdp.js"; export const REQUEST = "request" as const; export const RESPONSE = "response" as const; @@ -24,6 +24,7 @@ export namespace cdp { export namespace ts { export namespace Mod { export type Routes = ModCDPRoutes; + export type RouterConfig = ModCDPRouterConfig; export type CustomPayload = ModCDPCustomPayload; export type NamedValue = ModCDPNamedValue; export type Name = ModCDPName; @@ -35,13 +36,25 @@ export namespace cdp { export type AddCustomEventObjectParams = ModCDPAddCustomEventObjectParams; export type AddCustomEventParams = ModCDPAddCustomEventParams; export type AddMiddlewareParams = ModCDPAddMiddlewareParams; + export type LauncherConfig = ModCDPLauncherConfig; + export type UpstreamConfig = ModCDPUpstreamConfig; + export type ClientConfig = ModCDPClientConfig; + export type DownstreamConfig = ModCDPDownstreamConfig; + export type ServerConfig = ModCDPServerConfig; export type ConfigureParams = ModCDPConfigureParams; export type PingParams = ModCDPPingParams; export type PongEvent = ModCDPPongEvent; export type PingLatency = ModCDPPingLatency; + export type GetTopologyParams = ModCDPGetTopologyParams; + export type TopologyFrame = ModCDPTopologyFrame; + export type TopologyDomRoot = ModCDPTopologyDomRoot; + export type TopologyTarget = ModCDPTopologyTarget; + export type TopologyExecutionContext = ModCDPTopologyExecutionContext; + export type Topology = ModCDPTopology; export type CommandParams = ModCDPCommandParams; export type CommandResult = ModCDPCommandResult; export type EvaluateResponse = ModCDPEvaluateResponse; + export type GetTopologyResponse = ModCDPGetTopologyResponse; export type AddCustomCommandResponse = ModCDPAddCustomCommandResponse; export type AddCustomEventResponse = ModCDPAddCustomEventResponse; export type AddMiddlewareResponse = ModCDPAddMiddlewareResponse; diff --git a/js/src/types/generated/zod/Accessibility.ts b/js/src/types/generated/zod/Accessibility.ts index 37f3806b..9a92d903 100644 --- a/js/src/types/generated/zod/Accessibility.ts +++ b/js/src/types/generated/zod/Accessibility.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Page from "./Page.js"; import * as Runtime from "./Runtime.js"; @@ -18,20 +18,28 @@ export const AXPropertyName = withCdpMeta(z.enum(["actions", "busy", "disabled", export const AXNode = withCdpMeta(z.object({ "nodeId": z.lazy(() => AXNodeId), "ignored": z.boolean(), "ignoredReasons": z.array(z.lazy(() => AXProperty)).optional(), "role": z.lazy(() => AXValue).optional(), "chromeRole": z.lazy(() => AXValue).optional(), "name": z.lazy(() => AXValue).optional(), "description": z.lazy(() => AXValue).optional(), "value": z.lazy(() => AXValue).optional(), "properties": z.array(z.lazy(() => AXProperty)).optional(), "parentId": z.lazy(() => AXNodeId).optional(), "childIds": z.array(z.lazy(() => AXNodeId)).optional(), "backendDOMNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Accessibility.AXNode", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Accessibility.disable.params", "commandParams", { method: "Accessibility.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Accessibility.disable.result", "commandResult", { method: "Accessibility.disable" }); +export const DisableCommand = withCdpCommand("Accessibility.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Accessibility.enable.params", "commandParams", { method: "Accessibility.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Accessibility.enable.result", "commandResult", { method: "Accessibility.enable" }); +export const EnableCommand = withCdpCommand("Accessibility.enable", EnableParams, EnableResult); export const GetPartialAXTreeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "fetchRelatives": z.boolean().optional() }).passthrough(), "Accessibility.getPartialAXTree.params", "commandParams", { method: "Accessibility.getPartialAXTree" }); export const GetPartialAXTreeResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.getPartialAXTree.result", "commandResult", { method: "Accessibility.getPartialAXTree" }); +export const GetPartialAXTreeCommand = withCdpCommand("Accessibility.getPartialAXTree", GetPartialAXTreeParams, GetPartialAXTreeResult); export const GetFullAXTreeParams = withCdpMeta(z.object({ "depth": z.number().int().optional(), "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Accessibility.getFullAXTree.params", "commandParams", { method: "Accessibility.getFullAXTree" }); export const GetFullAXTreeResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.getFullAXTree.result", "commandResult", { method: "Accessibility.getFullAXTree" }); +export const GetFullAXTreeCommand = withCdpCommand("Accessibility.getFullAXTree", GetFullAXTreeParams, GetFullAXTreeResult); export const GetRootAXNodeParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Accessibility.getRootAXNode.params", "commandParams", { method: "Accessibility.getRootAXNode" }); export const GetRootAXNodeResult = withCdpMeta(z.object({ "node": z.lazy(() => AXNode) }).passthrough(), "Accessibility.getRootAXNode.result", "commandResult", { method: "Accessibility.getRootAXNode" }); +export const GetRootAXNodeCommand = withCdpCommand("Accessibility.getRootAXNode", GetRootAXNodeParams, GetRootAXNodeResult); export const GetAXNodeAndAncestorsParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "Accessibility.getAXNodeAndAncestors.params", "commandParams", { method: "Accessibility.getAXNodeAndAncestors" }); export const GetAXNodeAndAncestorsResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.getAXNodeAndAncestors.result", "commandResult", { method: "Accessibility.getAXNodeAndAncestors" }); +export const GetAXNodeAndAncestorsCommand = withCdpCommand("Accessibility.getAXNodeAndAncestors", GetAXNodeAndAncestorsParams, GetAXNodeAndAncestorsResult); export const GetChildAXNodesParams = withCdpMeta(z.object({ "id": z.lazy(() => AXNodeId), "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Accessibility.getChildAXNodes.params", "commandParams", { method: "Accessibility.getChildAXNodes" }); export const GetChildAXNodesResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.getChildAXNodes.result", "commandResult", { method: "Accessibility.getChildAXNodes" }); +export const GetChildAXNodesCommand = withCdpCommand("Accessibility.getChildAXNodes", GetChildAXNodesParams, GetChildAXNodesResult); export const QueryAXTreeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "accessibleName": z.string().optional(), "role": z.string().optional() }).passthrough(), "Accessibility.queryAXTree.params", "commandParams", { method: "Accessibility.queryAXTree" }); export const QueryAXTreeResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.queryAXTree.result", "commandResult", { method: "Accessibility.queryAXTree" }); +export const QueryAXTreeCommand = withCdpCommand("Accessibility.queryAXTree", QueryAXTreeParams, QueryAXTreeResult); export const LoadCompleteEvent = withCdpMeta(z.object({ "root": z.lazy(() => AXNode) }).passthrough(), "Accessibility.loadComplete", "event", { phase: "event" }); export const NodesUpdatedEvent = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => AXNode)) }).passthrough(), "Accessibility.nodesUpdated", "event", { phase: "event" }); @@ -66,14 +74,14 @@ export const zod = { NodesUpdatedEvent: NodesUpdatedEvent, } as const; export const commands = { - "Accessibility.disable": { params: DisableParams, result: DisableResult }, - "Accessibility.enable": { params: EnableParams, result: EnableResult }, - "Accessibility.getPartialAXTree": { params: GetPartialAXTreeParams, result: GetPartialAXTreeResult }, - "Accessibility.getFullAXTree": { params: GetFullAXTreeParams, result: GetFullAXTreeResult }, - "Accessibility.getRootAXNode": { params: GetRootAXNodeParams, result: GetRootAXNodeResult }, - "Accessibility.getAXNodeAndAncestors": { params: GetAXNodeAndAncestorsParams, result: GetAXNodeAndAncestorsResult }, - "Accessibility.getChildAXNodes": { params: GetChildAXNodesParams, result: GetChildAXNodesResult }, - "Accessibility.queryAXTree": { params: QueryAXTreeParams, result: QueryAXTreeResult }, + "Accessibility.disable": DisableCommand, + "Accessibility.enable": EnableCommand, + "Accessibility.getPartialAXTree": GetPartialAXTreeCommand, + "Accessibility.getFullAXTree": GetFullAXTreeCommand, + "Accessibility.getRootAXNode": GetRootAXNodeCommand, + "Accessibility.getAXNodeAndAncestors": GetAXNodeAndAncestorsCommand, + "Accessibility.getChildAXNodes": GetChildAXNodesCommand, + "Accessibility.queryAXTree": QueryAXTreeCommand, } as const; export const events = { "Accessibility.loadComplete": LoadCompleteEvent, diff --git a/js/src/types/generated/zod/Animation.ts b/js/src/types/generated/zod/Animation.ts index b1726d75..e3af7a3b 100644 --- a/js/src/types/generated/zod/Animation.ts +++ b/js/src/types/generated/zod/Animation.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Runtime from "./Runtime.js"; @@ -12,24 +12,34 @@ export const KeyframesRule = withCdpMeta(z.object({ "name": z.string().optional( export const KeyframeStyle = withCdpMeta(z.object({ "offset": z.string(), "easing": z.string() }).passthrough(), "Animation.KeyframeStyle", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Animation.disable.params", "commandParams", { method: "Animation.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Animation.disable.result", "commandResult", { method: "Animation.disable" }); +export const DisableCommand = withCdpCommand("Animation.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Animation.enable.params", "commandParams", { method: "Animation.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Animation.enable.result", "commandResult", { method: "Animation.enable" }); +export const EnableCommand = withCdpCommand("Animation.enable", EnableParams, EnableResult); export const GetCurrentTimeParams = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Animation.getCurrentTime.params", "commandParams", { method: "Animation.getCurrentTime" }); export const GetCurrentTimeResult = withCdpMeta(z.object({ "currentTime": z.number() }).passthrough(), "Animation.getCurrentTime.result", "commandResult", { method: "Animation.getCurrentTime" }); +export const GetCurrentTimeCommand = withCdpCommand("Animation.getCurrentTime", GetCurrentTimeParams, GetCurrentTimeResult); export const GetPlaybackRateParams = withCdpMeta(z.object({ }).passthrough(), "Animation.getPlaybackRate.params", "commandParams", { method: "Animation.getPlaybackRate" }); export const GetPlaybackRateResult = withCdpMeta(z.object({ "playbackRate": z.number() }).passthrough(), "Animation.getPlaybackRate.result", "commandResult", { method: "Animation.getPlaybackRate" }); +export const GetPlaybackRateCommand = withCdpCommand("Animation.getPlaybackRate", GetPlaybackRateParams, GetPlaybackRateResult); export const ReleaseAnimationsParams = withCdpMeta(z.object({ "animations": z.array(z.string()) }).passthrough(), "Animation.releaseAnimations.params", "commandParams", { method: "Animation.releaseAnimations" }); export const ReleaseAnimationsResult = withCdpMeta(z.object({ }).passthrough(), "Animation.releaseAnimations.result", "commandResult", { method: "Animation.releaseAnimations" }); +export const ReleaseAnimationsCommand = withCdpCommand("Animation.releaseAnimations", ReleaseAnimationsParams, ReleaseAnimationsResult); export const ResolveAnimationParams = withCdpMeta(z.object({ "animationId": z.string() }).passthrough(), "Animation.resolveAnimation.params", "commandParams", { method: "Animation.resolveAnimation" }); export const ResolveAnimationResult = withCdpMeta(z.object({ "remoteObject": z.lazy(() => Runtime.RemoteObject) }).passthrough(), "Animation.resolveAnimation.result", "commandResult", { method: "Animation.resolveAnimation" }); +export const ResolveAnimationCommand = withCdpCommand("Animation.resolveAnimation", ResolveAnimationParams, ResolveAnimationResult); export const SeekAnimationsParams = withCdpMeta(z.object({ "animations": z.array(z.string()), "currentTime": z.number() }).passthrough(), "Animation.seekAnimations.params", "commandParams", { method: "Animation.seekAnimations" }); export const SeekAnimationsResult = withCdpMeta(z.object({ }).passthrough(), "Animation.seekAnimations.result", "commandResult", { method: "Animation.seekAnimations" }); +export const SeekAnimationsCommand = withCdpCommand("Animation.seekAnimations", SeekAnimationsParams, SeekAnimationsResult); export const SetPausedParams = withCdpMeta(z.object({ "animations": z.array(z.string()), "paused": z.boolean() }).passthrough(), "Animation.setPaused.params", "commandParams", { method: "Animation.setPaused" }); export const SetPausedResult = withCdpMeta(z.object({ }).passthrough(), "Animation.setPaused.result", "commandResult", { method: "Animation.setPaused" }); +export const SetPausedCommand = withCdpCommand("Animation.setPaused", SetPausedParams, SetPausedResult); export const SetPlaybackRateParams = withCdpMeta(z.object({ "playbackRate": z.number() }).passthrough(), "Animation.setPlaybackRate.params", "commandParams", { method: "Animation.setPlaybackRate" }); export const SetPlaybackRateResult = withCdpMeta(z.object({ }).passthrough(), "Animation.setPlaybackRate.result", "commandResult", { method: "Animation.setPlaybackRate" }); +export const SetPlaybackRateCommand = withCdpCommand("Animation.setPlaybackRate", SetPlaybackRateParams, SetPlaybackRateResult); export const SetTimingParams = withCdpMeta(z.object({ "animationId": z.string(), "duration": z.number(), "delay": z.number() }).passthrough(), "Animation.setTiming.params", "commandParams", { method: "Animation.setTiming" }); export const SetTimingResult = withCdpMeta(z.object({ }).passthrough(), "Animation.setTiming.result", "commandResult", { method: "Animation.setTiming" }); +export const SetTimingCommand = withCdpCommand("Animation.setTiming", SetTimingParams, SetTimingResult); export const AnimationCanceledEvent = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Animation.animationCanceled", "event", { phase: "event" }); export const AnimationCreatedEvent = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Animation.animationCreated", "event", { phase: "event" }); export const AnimationStartedEvent = withCdpMeta(z.object({ "animation": z.lazy(() => Animation) }).passthrough(), "Animation.animationStarted", "event", { phase: "event" }); @@ -67,16 +77,16 @@ export const zod = { AnimationUpdatedEvent: AnimationUpdatedEvent, } as const; export const commands = { - "Animation.disable": { params: DisableParams, result: DisableResult }, - "Animation.enable": { params: EnableParams, result: EnableResult }, - "Animation.getCurrentTime": { params: GetCurrentTimeParams, result: GetCurrentTimeResult }, - "Animation.getPlaybackRate": { params: GetPlaybackRateParams, result: GetPlaybackRateResult }, - "Animation.releaseAnimations": { params: ReleaseAnimationsParams, result: ReleaseAnimationsResult }, - "Animation.resolveAnimation": { params: ResolveAnimationParams, result: ResolveAnimationResult }, - "Animation.seekAnimations": { params: SeekAnimationsParams, result: SeekAnimationsResult }, - "Animation.setPaused": { params: SetPausedParams, result: SetPausedResult }, - "Animation.setPlaybackRate": { params: SetPlaybackRateParams, result: SetPlaybackRateResult }, - "Animation.setTiming": { params: SetTimingParams, result: SetTimingResult }, + "Animation.disable": DisableCommand, + "Animation.enable": EnableCommand, + "Animation.getCurrentTime": GetCurrentTimeCommand, + "Animation.getPlaybackRate": GetPlaybackRateCommand, + "Animation.releaseAnimations": ReleaseAnimationsCommand, + "Animation.resolveAnimation": ResolveAnimationCommand, + "Animation.seekAnimations": SeekAnimationsCommand, + "Animation.setPaused": SetPausedCommand, + "Animation.setPlaybackRate": SetPlaybackRateCommand, + "Animation.setTiming": SetTimingCommand, } as const; export const events = { "Animation.animationCanceled": AnimationCanceledEvent, diff --git a/js/src/types/generated/zod/Audits.ts b/js/src/types/generated/zod/Audits.ts index 7e41056d..df57a821 100644 --- a/js/src/types/generated/zod/Audits.ts +++ b/js/src/types/generated/zod/Audits.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -75,12 +75,16 @@ export const IssueId = withCdpMeta(z.string(), "Audits.IssueId", "type"); export const InspectorIssue = withCdpMeta(z.object({ "code": z.lazy(() => InspectorIssueCode), "details": z.lazy(() => InspectorIssueDetails), "issueId": z.lazy(() => IssueId).optional() }).passthrough(), "Audits.InspectorIssue", "type"); export const GetEncodedResponseParams = withCdpMeta(z.object({ "requestId": z.lazy(() => Network.RequestId), "encoding": z.enum(["webp", "jpeg", "png"]), "quality": z.number().optional(), "sizeOnly": z.boolean().optional() }).passthrough(), "Audits.getEncodedResponse.params", "commandParams", { method: "Audits.getEncodedResponse" }); export const GetEncodedResponseResult = withCdpMeta(z.object({ "body": z.string().optional(), "originalSize": z.number().int(), "encodedSize": z.number().int() }).passthrough(), "Audits.getEncodedResponse.result", "commandResult", { method: "Audits.getEncodedResponse" }); +export const GetEncodedResponseCommand = withCdpCommand("Audits.getEncodedResponse", GetEncodedResponseParams, GetEncodedResponseResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Audits.disable.params", "commandParams", { method: "Audits.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Audits.disable.result", "commandResult", { method: "Audits.disable" }); +export const DisableCommand = withCdpCommand("Audits.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Audits.enable.params", "commandParams", { method: "Audits.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Audits.enable.result", "commandResult", { method: "Audits.enable" }); +export const EnableCommand = withCdpCommand("Audits.enable", EnableParams, EnableResult); export const CheckFormsIssuesParams = withCdpMeta(z.object({ }).passthrough(), "Audits.checkFormsIssues.params", "commandParams", { method: "Audits.checkFormsIssues" }); export const CheckFormsIssuesResult = withCdpMeta(z.object({ "formIssues": z.array(z.lazy(() => GenericIssueDetails)) }).passthrough(), "Audits.checkFormsIssues.result", "commandResult", { method: "Audits.checkFormsIssues" }); +export const CheckFormsIssuesCommand = withCdpCommand("Audits.checkFormsIssues", CheckFormsIssuesParams, CheckFormsIssuesResult); export const IssueAddedEvent = withCdpMeta(z.object({ "issue": z.lazy(() => InspectorIssue) }).passthrough(), "Audits.issueAdded", "event", { phase: "event" }); export const zod = { @@ -161,10 +165,10 @@ export const zod = { IssueAddedEvent: IssueAddedEvent, } as const; export const commands = { - "Audits.getEncodedResponse": { params: GetEncodedResponseParams, result: GetEncodedResponseResult }, - "Audits.disable": { params: DisableParams, result: DisableResult }, - "Audits.enable": { params: EnableParams, result: EnableResult }, - "Audits.checkFormsIssues": { params: CheckFormsIssuesParams, result: CheckFormsIssuesResult }, + "Audits.getEncodedResponse": GetEncodedResponseCommand, + "Audits.disable": DisableCommand, + "Audits.enable": EnableCommand, + "Audits.checkFormsIssues": CheckFormsIssuesCommand, } as const; export const events = { "Audits.issueAdded": IssueAddedEvent, diff --git a/js/src/types/generated/zod/Autofill.ts b/js/src/types/generated/zod/Autofill.ts index 63d0d893..5de650ed 100644 --- a/js/src/types/generated/zod/Autofill.ts +++ b/js/src/types/generated/zod/Autofill.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Page from "./Page.js"; @@ -14,12 +14,16 @@ export const FillingStrategy = withCdpMeta(z.enum(["autocompleteAttribute", "aut export const FilledField = withCdpMeta(z.object({ "htmlType": z.string(), "id": z.string(), "name": z.string(), "value": z.string(), "autofillType": z.string(), "fillingStrategy": z.lazy(() => FillingStrategy), "frameId": z.lazy(() => Page.FrameId), "fieldId": z.lazy(() => DOM.BackendNodeId) }).passthrough(), "Autofill.FilledField", "type"); export const TriggerParams = withCdpMeta(z.object({ "fieldId": z.lazy(() => DOM.BackendNodeId), "frameId": z.lazy(() => Page.FrameId).optional(), "card": z.lazy(() => CreditCard).optional(), "address": z.lazy(() => Address).optional() }).passthrough(), "Autofill.trigger.params", "commandParams", { method: "Autofill.trigger" }); export const TriggerResult = withCdpMeta(z.object({ }).passthrough(), "Autofill.trigger.result", "commandResult", { method: "Autofill.trigger" }); +export const TriggerCommand = withCdpCommand("Autofill.trigger", TriggerParams, TriggerResult); export const SetAddressesParams = withCdpMeta(z.object({ "addresses": z.array(z.lazy(() => Address)) }).passthrough(), "Autofill.setAddresses.params", "commandParams", { method: "Autofill.setAddresses" }); export const SetAddressesResult = withCdpMeta(z.object({ }).passthrough(), "Autofill.setAddresses.result", "commandResult", { method: "Autofill.setAddresses" }); +export const SetAddressesCommand = withCdpCommand("Autofill.setAddresses", SetAddressesParams, SetAddressesResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Autofill.disable.params", "commandParams", { method: "Autofill.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Autofill.disable.result", "commandResult", { method: "Autofill.disable" }); +export const DisableCommand = withCdpCommand("Autofill.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Autofill.enable.params", "commandParams", { method: "Autofill.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Autofill.enable.result", "commandResult", { method: "Autofill.enable" }); +export const EnableCommand = withCdpCommand("Autofill.enable", EnableParams, EnableResult); export const AddressFormFilledEvent = withCdpMeta(z.object({ "filledFields": z.array(z.lazy(() => FilledField)), "addressUi": z.lazy(() => AddressUI) }).passthrough(), "Autofill.addressFormFilled", "event", { phase: "event" }); export const zod = { @@ -41,10 +45,10 @@ export const zod = { AddressFormFilledEvent: AddressFormFilledEvent, } as const; export const commands = { - "Autofill.trigger": { params: TriggerParams, result: TriggerResult }, - "Autofill.setAddresses": { params: SetAddressesParams, result: SetAddressesResult }, - "Autofill.disable": { params: DisableParams, result: DisableResult }, - "Autofill.enable": { params: EnableParams, result: EnableResult }, + "Autofill.trigger": TriggerCommand, + "Autofill.setAddresses": SetAddressesCommand, + "Autofill.disable": DisableCommand, + "Autofill.enable": EnableCommand, } as const; export const events = { "Autofill.addressFormFilled": AddressFormFilledEvent, diff --git a/js/src/types/generated/zod/BackgroundService.ts b/js/src/types/generated/zod/BackgroundService.ts index 1991f650..34659f4e 100644 --- a/js/src/types/generated/zod/BackgroundService.ts +++ b/js/src/types/generated/zod/BackgroundService.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Network from "./Network.js"; import * as ServiceWorker from "./ServiceWorker.js"; @@ -10,12 +10,16 @@ export const EventMetadata = withCdpMeta(z.object({ "key": z.string(), "value": export const BackgroundServiceEvent = withCdpMeta(z.object({ "timestamp": z.lazy(() => Network.TimeSinceEpoch), "origin": z.string(), "serviceWorkerRegistrationId": z.lazy(() => ServiceWorker.RegistrationID), "service": z.lazy(() => ServiceName), "eventName": z.string(), "instanceId": z.string(), "eventMetadata": z.array(z.lazy(() => EventMetadata)), "storageKey": z.string() }).passthrough(), "BackgroundService.BackgroundServiceEvent", "type"); export const StartObservingParams = withCdpMeta(z.object({ "service": z.lazy(() => ServiceName) }).passthrough(), "BackgroundService.startObserving.params", "commandParams", { method: "BackgroundService.startObserving" }); export const StartObservingResult = withCdpMeta(z.object({ }).passthrough(), "BackgroundService.startObserving.result", "commandResult", { method: "BackgroundService.startObserving" }); +export const StartObservingCommand = withCdpCommand("BackgroundService.startObserving", StartObservingParams, StartObservingResult); export const StopObservingParams = withCdpMeta(z.object({ "service": z.lazy(() => ServiceName) }).passthrough(), "BackgroundService.stopObserving.params", "commandParams", { method: "BackgroundService.stopObserving" }); export const StopObservingResult = withCdpMeta(z.object({ }).passthrough(), "BackgroundService.stopObserving.result", "commandResult", { method: "BackgroundService.stopObserving" }); +export const StopObservingCommand = withCdpCommand("BackgroundService.stopObserving", StopObservingParams, StopObservingResult); export const SetRecordingParams = withCdpMeta(z.object({ "shouldRecord": z.boolean(), "service": z.lazy(() => ServiceName) }).passthrough(), "BackgroundService.setRecording.params", "commandParams", { method: "BackgroundService.setRecording" }); export const SetRecordingResult = withCdpMeta(z.object({ }).passthrough(), "BackgroundService.setRecording.result", "commandResult", { method: "BackgroundService.setRecording" }); +export const SetRecordingCommand = withCdpCommand("BackgroundService.setRecording", SetRecordingParams, SetRecordingResult); export const ClearEventsParams = withCdpMeta(z.object({ "service": z.lazy(() => ServiceName) }).passthrough(), "BackgroundService.clearEvents.params", "commandParams", { method: "BackgroundService.clearEvents" }); export const ClearEventsResult = withCdpMeta(z.object({ }).passthrough(), "BackgroundService.clearEvents.result", "commandResult", { method: "BackgroundService.clearEvents" }); +export const ClearEventsCommand = withCdpCommand("BackgroundService.clearEvents", ClearEventsParams, ClearEventsResult); export const RecordingStateChangedEvent = withCdpMeta(z.object({ "isRecording": z.boolean(), "service": z.lazy(() => ServiceName) }).passthrough(), "BackgroundService.recordingStateChanged", "event", { phase: "event" }); export const BackgroundServiceEventReceivedEvent = withCdpMeta(z.object({ "backgroundServiceEvent": z.lazy(() => BackgroundServiceEvent) }).passthrough(), "BackgroundService.backgroundServiceEventReceived", "event", { phase: "event" }); @@ -35,10 +39,10 @@ export const zod = { BackgroundServiceEventReceivedEvent: BackgroundServiceEventReceivedEvent, } as const; export const commands = { - "BackgroundService.startObserving": { params: StartObservingParams, result: StartObservingResult }, - "BackgroundService.stopObserving": { params: StopObservingParams, result: StopObservingResult }, - "BackgroundService.setRecording": { params: SetRecordingParams, result: SetRecordingResult }, - "BackgroundService.clearEvents": { params: ClearEventsParams, result: ClearEventsResult }, + "BackgroundService.startObserving": StartObservingCommand, + "BackgroundService.stopObserving": StopObservingCommand, + "BackgroundService.setRecording": SetRecordingCommand, + "BackgroundService.clearEvents": ClearEventsCommand, } as const; export const events = { "BackgroundService.recordingStateChanged": RecordingStateChangedEvent, diff --git a/js/src/types/generated/zod/BluetoothEmulation.ts b/js/src/types/generated/zod/BluetoothEmulation.ts index 53c06249..55fda5ac 100644 --- a/js/src/types/generated/zod/BluetoothEmulation.ts +++ b/js/src/types/generated/zod/BluetoothEmulation.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const CentralState = withCdpMeta(z.enum(["absent", "powered-off", "powered-on"]), "BluetoothEmulation.CentralState", "type"); export const GATTOperationType = withCdpMeta(z.enum(["connection", "discovery"]), "BluetoothEmulation.GATTOperationType", "type"); @@ -14,34 +14,49 @@ export const ScanEntry = withCdpMeta(z.object({ "deviceAddress": z.string(), "rs export const CharacteristicProperties = withCdpMeta(z.object({ "broadcast": z.boolean().optional(), "read": z.boolean().optional(), "writeWithoutResponse": z.boolean().optional(), "write": z.boolean().optional(), "notify": z.boolean().optional(), "indicate": z.boolean().optional(), "authenticatedSignedWrites": z.boolean().optional(), "extendedProperties": z.boolean().optional() }).passthrough(), "BluetoothEmulation.CharacteristicProperties", "type"); export const EnableParams = withCdpMeta(z.object({ "state": z.lazy(() => CentralState), "leSupported": z.boolean() }).passthrough(), "BluetoothEmulation.enable.params", "commandParams", { method: "BluetoothEmulation.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.enable.result", "commandResult", { method: "BluetoothEmulation.enable" }); +export const EnableCommand = withCdpCommand("BluetoothEmulation.enable", EnableParams, EnableResult); export const SetSimulatedCentralStateParams = withCdpMeta(z.object({ "state": z.lazy(() => CentralState) }).passthrough(), "BluetoothEmulation.setSimulatedCentralState.params", "commandParams", { method: "BluetoothEmulation.setSimulatedCentralState" }); export const SetSimulatedCentralStateResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.setSimulatedCentralState.result", "commandResult", { method: "BluetoothEmulation.setSimulatedCentralState" }); +export const SetSimulatedCentralStateCommand = withCdpCommand("BluetoothEmulation.setSimulatedCentralState", SetSimulatedCentralStateParams, SetSimulatedCentralStateResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.disable.params", "commandParams", { method: "BluetoothEmulation.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.disable.result", "commandResult", { method: "BluetoothEmulation.disable" }); +export const DisableCommand = withCdpCommand("BluetoothEmulation.disable", DisableParams, DisableResult); export const SimulatePreconnectedPeripheralParams = withCdpMeta(z.object({ "address": z.string(), "name": z.string(), "manufacturerData": z.array(z.lazy(() => ManufacturerData)), "knownServiceUuids": z.array(z.string()) }).passthrough(), "BluetoothEmulation.simulatePreconnectedPeripheral.params", "commandParams", { method: "BluetoothEmulation.simulatePreconnectedPeripheral" }); export const SimulatePreconnectedPeripheralResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulatePreconnectedPeripheral.result", "commandResult", { method: "BluetoothEmulation.simulatePreconnectedPeripheral" }); +export const SimulatePreconnectedPeripheralCommand = withCdpCommand("BluetoothEmulation.simulatePreconnectedPeripheral", SimulatePreconnectedPeripheralParams, SimulatePreconnectedPeripheralResult); export const SimulateAdvertisementParams = withCdpMeta(z.object({ "entry": z.lazy(() => ScanEntry) }).passthrough(), "BluetoothEmulation.simulateAdvertisement.params", "commandParams", { method: "BluetoothEmulation.simulateAdvertisement" }); export const SimulateAdvertisementResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulateAdvertisement.result", "commandResult", { method: "BluetoothEmulation.simulateAdvertisement" }); +export const SimulateAdvertisementCommand = withCdpCommand("BluetoothEmulation.simulateAdvertisement", SimulateAdvertisementParams, SimulateAdvertisementResult); export const SimulateGATTOperationResponseParams = withCdpMeta(z.object({ "address": z.string(), "type": z.lazy(() => GATTOperationType), "code": z.number().int() }).passthrough(), "BluetoothEmulation.simulateGATTOperationResponse.params", "commandParams", { method: "BluetoothEmulation.simulateGATTOperationResponse" }); export const SimulateGATTOperationResponseResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulateGATTOperationResponse.result", "commandResult", { method: "BluetoothEmulation.simulateGATTOperationResponse" }); +export const SimulateGATTOperationResponseCommand = withCdpCommand("BluetoothEmulation.simulateGATTOperationResponse", SimulateGATTOperationResponseParams, SimulateGATTOperationResponseResult); export const SimulateCharacteristicOperationResponseParams = withCdpMeta(z.object({ "characteristicId": z.string(), "type": z.lazy(() => CharacteristicOperationType), "code": z.number().int(), "data": z.string().optional() }).passthrough(), "BluetoothEmulation.simulateCharacteristicOperationResponse.params", "commandParams", { method: "BluetoothEmulation.simulateCharacteristicOperationResponse" }); export const SimulateCharacteristicOperationResponseResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulateCharacteristicOperationResponse.result", "commandResult", { method: "BluetoothEmulation.simulateCharacteristicOperationResponse" }); +export const SimulateCharacteristicOperationResponseCommand = withCdpCommand("BluetoothEmulation.simulateCharacteristicOperationResponse", SimulateCharacteristicOperationResponseParams, SimulateCharacteristicOperationResponseResult); export const SimulateDescriptorOperationResponseParams = withCdpMeta(z.object({ "descriptorId": z.string(), "type": z.lazy(() => DescriptorOperationType), "code": z.number().int(), "data": z.string().optional() }).passthrough(), "BluetoothEmulation.simulateDescriptorOperationResponse.params", "commandParams", { method: "BluetoothEmulation.simulateDescriptorOperationResponse" }); export const SimulateDescriptorOperationResponseResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulateDescriptorOperationResponse.result", "commandResult", { method: "BluetoothEmulation.simulateDescriptorOperationResponse" }); +export const SimulateDescriptorOperationResponseCommand = withCdpCommand("BluetoothEmulation.simulateDescriptorOperationResponse", SimulateDescriptorOperationResponseParams, SimulateDescriptorOperationResponseResult); export const AddServiceParams = withCdpMeta(z.object({ "address": z.string(), "serviceUuid": z.string() }).passthrough(), "BluetoothEmulation.addService.params", "commandParams", { method: "BluetoothEmulation.addService" }); export const AddServiceResult = withCdpMeta(z.object({ "serviceId": z.string() }).passthrough(), "BluetoothEmulation.addService.result", "commandResult", { method: "BluetoothEmulation.addService" }); +export const AddServiceCommand = withCdpCommand("BluetoothEmulation.addService", AddServiceParams, AddServiceResult); export const RemoveServiceParams = withCdpMeta(z.object({ "serviceId": z.string() }).passthrough(), "BluetoothEmulation.removeService.params", "commandParams", { method: "BluetoothEmulation.removeService" }); export const RemoveServiceResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.removeService.result", "commandResult", { method: "BluetoothEmulation.removeService" }); +export const RemoveServiceCommand = withCdpCommand("BluetoothEmulation.removeService", RemoveServiceParams, RemoveServiceResult); export const AddCharacteristicParams = withCdpMeta(z.object({ "serviceId": z.string(), "characteristicUuid": z.string(), "properties": z.lazy(() => CharacteristicProperties) }).passthrough(), "BluetoothEmulation.addCharacteristic.params", "commandParams", { method: "BluetoothEmulation.addCharacteristic" }); export const AddCharacteristicResult = withCdpMeta(z.object({ "characteristicId": z.string() }).passthrough(), "BluetoothEmulation.addCharacteristic.result", "commandResult", { method: "BluetoothEmulation.addCharacteristic" }); +export const AddCharacteristicCommand = withCdpCommand("BluetoothEmulation.addCharacteristic", AddCharacteristicParams, AddCharacteristicResult); export const RemoveCharacteristicParams = withCdpMeta(z.object({ "characteristicId": z.string() }).passthrough(), "BluetoothEmulation.removeCharacteristic.params", "commandParams", { method: "BluetoothEmulation.removeCharacteristic" }); export const RemoveCharacteristicResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.removeCharacteristic.result", "commandResult", { method: "BluetoothEmulation.removeCharacteristic" }); +export const RemoveCharacteristicCommand = withCdpCommand("BluetoothEmulation.removeCharacteristic", RemoveCharacteristicParams, RemoveCharacteristicResult); export const AddDescriptorParams = withCdpMeta(z.object({ "characteristicId": z.string(), "descriptorUuid": z.string() }).passthrough(), "BluetoothEmulation.addDescriptor.params", "commandParams", { method: "BluetoothEmulation.addDescriptor" }); export const AddDescriptorResult = withCdpMeta(z.object({ "descriptorId": z.string() }).passthrough(), "BluetoothEmulation.addDescriptor.result", "commandResult", { method: "BluetoothEmulation.addDescriptor" }); +export const AddDescriptorCommand = withCdpCommand("BluetoothEmulation.addDescriptor", AddDescriptorParams, AddDescriptorResult); export const RemoveDescriptorParams = withCdpMeta(z.object({ "descriptorId": z.string() }).passthrough(), "BluetoothEmulation.removeDescriptor.params", "commandParams", { method: "BluetoothEmulation.removeDescriptor" }); export const RemoveDescriptorResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.removeDescriptor.result", "commandResult", { method: "BluetoothEmulation.removeDescriptor" }); +export const RemoveDescriptorCommand = withCdpCommand("BluetoothEmulation.removeDescriptor", RemoveDescriptorParams, RemoveDescriptorResult); export const SimulateGATTDisconnectionParams = withCdpMeta(z.object({ "address": z.string() }).passthrough(), "BluetoothEmulation.simulateGATTDisconnection.params", "commandParams", { method: "BluetoothEmulation.simulateGATTDisconnection" }); export const SimulateGATTDisconnectionResult = withCdpMeta(z.object({ }).passthrough(), "BluetoothEmulation.simulateGATTDisconnection.result", "commandResult", { method: "BluetoothEmulation.simulateGATTDisconnection" }); +export const SimulateGATTDisconnectionCommand = withCdpCommand("BluetoothEmulation.simulateGATTDisconnection", SimulateGATTDisconnectionParams, SimulateGATTDisconnectionResult); export const GattOperationReceivedEvent = withCdpMeta(z.object({ "address": z.string(), "type": z.lazy(() => GATTOperationType) }).passthrough(), "BluetoothEmulation.gattOperationReceived", "event", { phase: "event" }); export const CharacteristicOperationReceivedEvent = withCdpMeta(z.object({ "characteristicId": z.string(), "type": z.lazy(() => CharacteristicOperationType), "data": z.string().optional(), "writeType": z.lazy(() => CharacteristicWriteType).optional() }).passthrough(), "BluetoothEmulation.characteristicOperationReceived", "event", { phase: "event" }); export const DescriptorOperationReceivedEvent = withCdpMeta(z.object({ "descriptorId": z.string(), "type": z.lazy(() => DescriptorOperationType), "data": z.string().optional() }).passthrough(), "BluetoothEmulation.descriptorOperationReceived", "event", { phase: "event" }); @@ -91,21 +106,21 @@ export const zod = { DescriptorOperationReceivedEvent: DescriptorOperationReceivedEvent, } as const; export const commands = { - "BluetoothEmulation.enable": { params: EnableParams, result: EnableResult }, - "BluetoothEmulation.setSimulatedCentralState": { params: SetSimulatedCentralStateParams, result: SetSimulatedCentralStateResult }, - "BluetoothEmulation.disable": { params: DisableParams, result: DisableResult }, - "BluetoothEmulation.simulatePreconnectedPeripheral": { params: SimulatePreconnectedPeripheralParams, result: SimulatePreconnectedPeripheralResult }, - "BluetoothEmulation.simulateAdvertisement": { params: SimulateAdvertisementParams, result: SimulateAdvertisementResult }, - "BluetoothEmulation.simulateGATTOperationResponse": { params: SimulateGATTOperationResponseParams, result: SimulateGATTOperationResponseResult }, - "BluetoothEmulation.simulateCharacteristicOperationResponse": { params: SimulateCharacteristicOperationResponseParams, result: SimulateCharacteristicOperationResponseResult }, - "BluetoothEmulation.simulateDescriptorOperationResponse": { params: SimulateDescriptorOperationResponseParams, result: SimulateDescriptorOperationResponseResult }, - "BluetoothEmulation.addService": { params: AddServiceParams, result: AddServiceResult }, - "BluetoothEmulation.removeService": { params: RemoveServiceParams, result: RemoveServiceResult }, - "BluetoothEmulation.addCharacteristic": { params: AddCharacteristicParams, result: AddCharacteristicResult }, - "BluetoothEmulation.removeCharacteristic": { params: RemoveCharacteristicParams, result: RemoveCharacteristicResult }, - "BluetoothEmulation.addDescriptor": { params: AddDescriptorParams, result: AddDescriptorResult }, - "BluetoothEmulation.removeDescriptor": { params: RemoveDescriptorParams, result: RemoveDescriptorResult }, - "BluetoothEmulation.simulateGATTDisconnection": { params: SimulateGATTDisconnectionParams, result: SimulateGATTDisconnectionResult }, + "BluetoothEmulation.enable": EnableCommand, + "BluetoothEmulation.setSimulatedCentralState": SetSimulatedCentralStateCommand, + "BluetoothEmulation.disable": DisableCommand, + "BluetoothEmulation.simulatePreconnectedPeripheral": SimulatePreconnectedPeripheralCommand, + "BluetoothEmulation.simulateAdvertisement": SimulateAdvertisementCommand, + "BluetoothEmulation.simulateGATTOperationResponse": SimulateGATTOperationResponseCommand, + "BluetoothEmulation.simulateCharacteristicOperationResponse": SimulateCharacteristicOperationResponseCommand, + "BluetoothEmulation.simulateDescriptorOperationResponse": SimulateDescriptorOperationResponseCommand, + "BluetoothEmulation.addService": AddServiceCommand, + "BluetoothEmulation.removeService": RemoveServiceCommand, + "BluetoothEmulation.addCharacteristic": AddCharacteristicCommand, + "BluetoothEmulation.removeCharacteristic": RemoveCharacteristicCommand, + "BluetoothEmulation.addDescriptor": AddDescriptorCommand, + "BluetoothEmulation.removeDescriptor": RemoveDescriptorCommand, + "BluetoothEmulation.simulateGATTDisconnection": SimulateGATTDisconnectionCommand, } as const; export const events = { "BluetoothEmulation.gattOperationReceived": GattOperationReceivedEvent, diff --git a/js/src/types/generated/zod/Browser.ts b/js/src/types/generated/zod/Browser.ts index 026a2d73..126339ca 100644 --- a/js/src/types/generated/zod/Browser.ts +++ b/js/src/types/generated/zod/Browser.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Page from "./Page.js"; import * as Target from "./Target.js"; @@ -18,44 +18,64 @@ export const Histogram = withCdpMeta(z.object({ "name": z.string(), "sum": z.num export const PrivacySandboxAPI = withCdpMeta(z.enum(["BiddingAndAuctionServices", "TrustedKeyValue"]), "Browser.PrivacySandboxAPI", "type"); export const SetPermissionParams = withCdpMeta(z.object({ "permission": z.lazy(() => PermissionDescriptor), "setting": z.lazy(() => PermissionSetting), "origin": z.string().optional(), "embeddedOrigin": z.string().optional(), "browserContextId": z.lazy(() => BrowserContextID).optional() }).passthrough(), "Browser.setPermission.params", "commandParams", { method: "Browser.setPermission" }); export const SetPermissionResult = withCdpMeta(z.object({ }).passthrough(), "Browser.setPermission.result", "commandResult", { method: "Browser.setPermission" }); +export const SetPermissionCommand = withCdpCommand("Browser.setPermission", SetPermissionParams, SetPermissionResult); export const GrantPermissionsParams = withCdpMeta(z.object({ "permissions": z.array(z.lazy(() => PermissionType)), "origin": z.string().optional(), "browserContextId": z.lazy(() => BrowserContextID).optional() }).passthrough(), "Browser.grantPermissions.params", "commandParams", { method: "Browser.grantPermissions" }); export const GrantPermissionsResult = withCdpMeta(z.object({ }).passthrough(), "Browser.grantPermissions.result", "commandResult", { method: "Browser.grantPermissions" }); +export const GrantPermissionsCommand = withCdpCommand("Browser.grantPermissions", GrantPermissionsParams, GrantPermissionsResult); export const ResetPermissionsParams = withCdpMeta(z.object({ "browserContextId": z.lazy(() => BrowserContextID).optional() }).passthrough(), "Browser.resetPermissions.params", "commandParams", { method: "Browser.resetPermissions" }); export const ResetPermissionsResult = withCdpMeta(z.object({ }).passthrough(), "Browser.resetPermissions.result", "commandResult", { method: "Browser.resetPermissions" }); +export const ResetPermissionsCommand = withCdpCommand("Browser.resetPermissions", ResetPermissionsParams, ResetPermissionsResult); export const SetDownloadBehaviorParams = withCdpMeta(z.object({ "behavior": z.enum(["deny", "allow", "allowAndName", "default"]), "browserContextId": z.lazy(() => BrowserContextID).optional(), "downloadPath": z.string().optional(), "eventsEnabled": z.boolean().optional() }).passthrough(), "Browser.setDownloadBehavior.params", "commandParams", { method: "Browser.setDownloadBehavior" }); export const SetDownloadBehaviorResult = withCdpMeta(z.object({ }).passthrough(), "Browser.setDownloadBehavior.result", "commandResult", { method: "Browser.setDownloadBehavior" }); +export const SetDownloadBehaviorCommand = withCdpCommand("Browser.setDownloadBehavior", SetDownloadBehaviorParams, SetDownloadBehaviorResult); export const CancelDownloadParams = withCdpMeta(z.object({ "guid": z.string(), "browserContextId": z.lazy(() => BrowserContextID).optional() }).passthrough(), "Browser.cancelDownload.params", "commandParams", { method: "Browser.cancelDownload" }); export const CancelDownloadResult = withCdpMeta(z.object({ }).passthrough(), "Browser.cancelDownload.result", "commandResult", { method: "Browser.cancelDownload" }); +export const CancelDownloadCommand = withCdpCommand("Browser.cancelDownload", CancelDownloadParams, CancelDownloadResult); export const CloseParams = withCdpMeta(z.object({ }).passthrough(), "Browser.close.params", "commandParams", { method: "Browser.close" }); export const CloseResult = withCdpMeta(z.object({ }).passthrough(), "Browser.close.result", "commandResult", { method: "Browser.close" }); +export const CloseCommand = withCdpCommand("Browser.close", CloseParams, CloseResult); export const CrashParams = withCdpMeta(z.object({ }).passthrough(), "Browser.crash.params", "commandParams", { method: "Browser.crash" }); export const CrashResult = withCdpMeta(z.object({ }).passthrough(), "Browser.crash.result", "commandResult", { method: "Browser.crash" }); +export const CrashCommand = withCdpCommand("Browser.crash", CrashParams, CrashResult); export const CrashGpuProcessParams = withCdpMeta(z.object({ }).passthrough(), "Browser.crashGpuProcess.params", "commandParams", { method: "Browser.crashGpuProcess" }); export const CrashGpuProcessResult = withCdpMeta(z.object({ }).passthrough(), "Browser.crashGpuProcess.result", "commandResult", { method: "Browser.crashGpuProcess" }); +export const CrashGpuProcessCommand = withCdpCommand("Browser.crashGpuProcess", CrashGpuProcessParams, CrashGpuProcessResult); export const GetVersionParams = withCdpMeta(z.object({ }).passthrough(), "Browser.getVersion.params", "commandParams", { method: "Browser.getVersion" }); export const GetVersionResult = withCdpMeta(z.object({ "protocolVersion": z.string(), "product": z.string(), "revision": z.string(), "userAgent": z.string(), "jsVersion": z.string() }).passthrough(), "Browser.getVersion.result", "commandResult", { method: "Browser.getVersion" }); +export const GetVersionCommand = withCdpCommand("Browser.getVersion", GetVersionParams, GetVersionResult); export const GetBrowserCommandLineParams = withCdpMeta(z.object({ }).passthrough(), "Browser.getBrowserCommandLine.params", "commandParams", { method: "Browser.getBrowserCommandLine" }); export const GetBrowserCommandLineResult = withCdpMeta(z.object({ "arguments": z.array(z.string()) }).passthrough(), "Browser.getBrowserCommandLine.result", "commandResult", { method: "Browser.getBrowserCommandLine" }); +export const GetBrowserCommandLineCommand = withCdpCommand("Browser.getBrowserCommandLine", GetBrowserCommandLineParams, GetBrowserCommandLineResult); export const GetHistogramsParams = withCdpMeta(z.object({ "query": z.string().optional(), "delta": z.boolean().optional() }).passthrough(), "Browser.getHistograms.params", "commandParams", { method: "Browser.getHistograms" }); export const GetHistogramsResult = withCdpMeta(z.object({ "histograms": z.array(z.lazy(() => Histogram)) }).passthrough(), "Browser.getHistograms.result", "commandResult", { method: "Browser.getHistograms" }); +export const GetHistogramsCommand = withCdpCommand("Browser.getHistograms", GetHistogramsParams, GetHistogramsResult); export const GetHistogramParams = withCdpMeta(z.object({ "name": z.string(), "delta": z.boolean().optional() }).passthrough(), "Browser.getHistogram.params", "commandParams", { method: "Browser.getHistogram" }); export const GetHistogramResult = withCdpMeta(z.object({ "histogram": z.lazy(() => Histogram) }).passthrough(), "Browser.getHistogram.result", "commandResult", { method: "Browser.getHistogram" }); +export const GetHistogramCommand = withCdpCommand("Browser.getHistogram", GetHistogramParams, GetHistogramResult); export const GetWindowBoundsParams = withCdpMeta(z.object({ "windowId": z.lazy(() => WindowID) }).passthrough(), "Browser.getWindowBounds.params", "commandParams", { method: "Browser.getWindowBounds" }); export const GetWindowBoundsResult = withCdpMeta(z.object({ "bounds": z.lazy(() => Bounds) }).passthrough(), "Browser.getWindowBounds.result", "commandResult", { method: "Browser.getWindowBounds" }); +export const GetWindowBoundsCommand = withCdpCommand("Browser.getWindowBounds", GetWindowBoundsParams, GetWindowBoundsResult); export const GetWindowForTargetParams = withCdpMeta(z.object({ "targetId": z.lazy(() => Target.TargetID).optional() }).passthrough(), "Browser.getWindowForTarget.params", "commandParams", { method: "Browser.getWindowForTarget" }); export const GetWindowForTargetResult = withCdpMeta(z.object({ "windowId": z.lazy(() => WindowID), "bounds": z.lazy(() => Bounds) }).passthrough(), "Browser.getWindowForTarget.result", "commandResult", { method: "Browser.getWindowForTarget" }); +export const GetWindowForTargetCommand = withCdpCommand("Browser.getWindowForTarget", GetWindowForTargetParams, GetWindowForTargetResult); export const SetWindowBoundsParams = withCdpMeta(z.object({ "windowId": z.lazy(() => WindowID), "bounds": z.lazy(() => Bounds) }).passthrough(), "Browser.setWindowBounds.params", "commandParams", { method: "Browser.setWindowBounds" }); export const SetWindowBoundsResult = withCdpMeta(z.object({ }).passthrough(), "Browser.setWindowBounds.result", "commandResult", { method: "Browser.setWindowBounds" }); +export const SetWindowBoundsCommand = withCdpCommand("Browser.setWindowBounds", SetWindowBoundsParams, SetWindowBoundsResult); export const SetContentsSizeParams = withCdpMeta(z.object({ "windowId": z.lazy(() => WindowID), "width": z.number().int().optional(), "height": z.number().int().optional() }).passthrough(), "Browser.setContentsSize.params", "commandParams", { method: "Browser.setContentsSize" }); export const SetContentsSizeResult = withCdpMeta(z.object({ }).passthrough(), "Browser.setContentsSize.result", "commandResult", { method: "Browser.setContentsSize" }); +export const SetContentsSizeCommand = withCdpCommand("Browser.setContentsSize", SetContentsSizeParams, SetContentsSizeResult); export const SetDockTileParams = withCdpMeta(z.object({ "badgeLabel": z.string().optional(), "image": z.string().optional() }).passthrough(), "Browser.setDockTile.params", "commandParams", { method: "Browser.setDockTile" }); export const SetDockTileResult = withCdpMeta(z.object({ }).passthrough(), "Browser.setDockTile.result", "commandResult", { method: "Browser.setDockTile" }); +export const SetDockTileCommand = withCdpCommand("Browser.setDockTile", SetDockTileParams, SetDockTileResult); export const ExecuteBrowserCommandParams = withCdpMeta(z.object({ "commandId": z.lazy(() => BrowserCommandId) }).passthrough(), "Browser.executeBrowserCommand.params", "commandParams", { method: "Browser.executeBrowserCommand" }); export const ExecuteBrowserCommandResult = withCdpMeta(z.object({ }).passthrough(), "Browser.executeBrowserCommand.result", "commandResult", { method: "Browser.executeBrowserCommand" }); +export const ExecuteBrowserCommandCommand = withCdpCommand("Browser.executeBrowserCommand", ExecuteBrowserCommandParams, ExecuteBrowserCommandResult); export const AddPrivacySandboxEnrollmentOverrideParams = withCdpMeta(z.object({ "url": z.string() }).passthrough(), "Browser.addPrivacySandboxEnrollmentOverride.params", "commandParams", { method: "Browser.addPrivacySandboxEnrollmentOverride" }); export const AddPrivacySandboxEnrollmentOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Browser.addPrivacySandboxEnrollmentOverride.result", "commandResult", { method: "Browser.addPrivacySandboxEnrollmentOverride" }); +export const AddPrivacySandboxEnrollmentOverrideCommand = withCdpCommand("Browser.addPrivacySandboxEnrollmentOverride", AddPrivacySandboxEnrollmentOverrideParams, AddPrivacySandboxEnrollmentOverrideResult); export const AddPrivacySandboxCoordinatorKeyConfigParams = withCdpMeta(z.object({ "api": z.lazy(() => PrivacySandboxAPI), "coordinatorOrigin": z.string(), "keyConfig": z.string(), "browserContextId": z.lazy(() => BrowserContextID).optional() }).passthrough(), "Browser.addPrivacySandboxCoordinatorKeyConfig.params", "commandParams", { method: "Browser.addPrivacySandboxCoordinatorKeyConfig" }); export const AddPrivacySandboxCoordinatorKeyConfigResult = withCdpMeta(z.object({ }).passthrough(), "Browser.addPrivacySandboxCoordinatorKeyConfig.result", "commandResult", { method: "Browser.addPrivacySandboxCoordinatorKeyConfig" }); +export const AddPrivacySandboxCoordinatorKeyConfigCommand = withCdpCommand("Browser.addPrivacySandboxCoordinatorKeyConfig", AddPrivacySandboxCoordinatorKeyConfigParams, AddPrivacySandboxCoordinatorKeyConfigResult); export const DownloadWillBeginEvent = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId), "guid": z.string(), "url": z.string(), "suggestedFilename": z.string() }).passthrough(), "Browser.downloadWillBegin", "event", { phase: "event" }); export const DownloadProgressEvent = withCdpMeta(z.object({ "guid": z.string(), "totalBytes": z.number(), "receivedBytes": z.number(), "state": z.enum(["inProgress", "completed", "canceled"]), "filePath": z.string().optional() }).passthrough(), "Browser.downloadProgress", "event", { phase: "event" }); @@ -115,26 +135,26 @@ export const zod = { DownloadProgressEvent: DownloadProgressEvent, } as const; export const commands = { - "Browser.setPermission": { params: SetPermissionParams, result: SetPermissionResult }, - "Browser.grantPermissions": { params: GrantPermissionsParams, result: GrantPermissionsResult }, - "Browser.resetPermissions": { params: ResetPermissionsParams, result: ResetPermissionsResult }, - "Browser.setDownloadBehavior": { params: SetDownloadBehaviorParams, result: SetDownloadBehaviorResult }, - "Browser.cancelDownload": { params: CancelDownloadParams, result: CancelDownloadResult }, - "Browser.close": { params: CloseParams, result: CloseResult }, - "Browser.crash": { params: CrashParams, result: CrashResult }, - "Browser.crashGpuProcess": { params: CrashGpuProcessParams, result: CrashGpuProcessResult }, - "Browser.getVersion": { params: GetVersionParams, result: GetVersionResult }, - "Browser.getBrowserCommandLine": { params: GetBrowserCommandLineParams, result: GetBrowserCommandLineResult }, - "Browser.getHistograms": { params: GetHistogramsParams, result: GetHistogramsResult }, - "Browser.getHistogram": { params: GetHistogramParams, result: GetHistogramResult }, - "Browser.getWindowBounds": { params: GetWindowBoundsParams, result: GetWindowBoundsResult }, - "Browser.getWindowForTarget": { params: GetWindowForTargetParams, result: GetWindowForTargetResult }, - "Browser.setWindowBounds": { params: SetWindowBoundsParams, result: SetWindowBoundsResult }, - "Browser.setContentsSize": { params: SetContentsSizeParams, result: SetContentsSizeResult }, - "Browser.setDockTile": { params: SetDockTileParams, result: SetDockTileResult }, - "Browser.executeBrowserCommand": { params: ExecuteBrowserCommandParams, result: ExecuteBrowserCommandResult }, - "Browser.addPrivacySandboxEnrollmentOverride": { params: AddPrivacySandboxEnrollmentOverrideParams, result: AddPrivacySandboxEnrollmentOverrideResult }, - "Browser.addPrivacySandboxCoordinatorKeyConfig": { params: AddPrivacySandboxCoordinatorKeyConfigParams, result: AddPrivacySandboxCoordinatorKeyConfigResult }, + "Browser.setPermission": SetPermissionCommand, + "Browser.grantPermissions": GrantPermissionsCommand, + "Browser.resetPermissions": ResetPermissionsCommand, + "Browser.setDownloadBehavior": SetDownloadBehaviorCommand, + "Browser.cancelDownload": CancelDownloadCommand, + "Browser.close": CloseCommand, + "Browser.crash": CrashCommand, + "Browser.crashGpuProcess": CrashGpuProcessCommand, + "Browser.getVersion": GetVersionCommand, + "Browser.getBrowserCommandLine": GetBrowserCommandLineCommand, + "Browser.getHistograms": GetHistogramsCommand, + "Browser.getHistogram": GetHistogramCommand, + "Browser.getWindowBounds": GetWindowBoundsCommand, + "Browser.getWindowForTarget": GetWindowForTargetCommand, + "Browser.setWindowBounds": SetWindowBoundsCommand, + "Browser.setContentsSize": SetContentsSizeCommand, + "Browser.setDockTile": SetDockTileCommand, + "Browser.executeBrowserCommand": ExecuteBrowserCommandCommand, + "Browser.addPrivacySandboxEnrollmentOverride": AddPrivacySandboxEnrollmentOverrideCommand, + "Browser.addPrivacySandboxCoordinatorKeyConfig": AddPrivacySandboxCoordinatorKeyConfigCommand, } as const; export const events = { "Browser.downloadWillBegin": DownloadWillBeginEvent, diff --git a/js/src/types/generated/zod/CSS.ts b/js/src/types/generated/zod/CSS.ts index 0b99333c..dd64dd69 100644 --- a/js/src/types/generated/zod/CSS.ts +++ b/js/src/types/generated/zod/CSS.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Page from "./Page.js"; @@ -52,80 +52,118 @@ export const CSSKeyframeRule = withCdpMeta(z.object({ "styleSheetId": z.lazy(() export const StyleDeclarationEdit = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.StyleDeclarationEdit", "type"); export const AddRuleParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "ruleText": z.string(), "location": z.lazy(() => SourceRange), "nodeForPropertySyntaxValidation": z.lazy(() => DOM.NodeId).optional() }).passthrough(), "CSS.addRule.params", "commandParams", { method: "CSS.addRule" }); export const AddRuleResult = withCdpMeta(z.object({ "rule": z.lazy(() => CSSRule) }).passthrough(), "CSS.addRule.result", "commandResult", { method: "CSS.addRule" }); +export const AddRuleCommand = withCdpCommand("CSS.addRule", AddRuleParams, AddRuleResult); export const CollectClassNamesParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId) }).passthrough(), "CSS.collectClassNames.params", "commandParams", { method: "CSS.collectClassNames" }); export const CollectClassNamesResult = withCdpMeta(z.object({ "classNames": z.array(z.string()) }).passthrough(), "CSS.collectClassNames.result", "commandResult", { method: "CSS.collectClassNames" }); +export const CollectClassNamesCommand = withCdpCommand("CSS.collectClassNames", CollectClassNamesParams, CollectClassNamesResult); export const CreateStyleSheetParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId), "force": z.boolean().optional() }).passthrough(), "CSS.createStyleSheet.params", "commandParams", { method: "CSS.createStyleSheet" }); export const CreateStyleSheetResult = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId) }).passthrough(), "CSS.createStyleSheet.result", "commandResult", { method: "CSS.createStyleSheet" }); +export const CreateStyleSheetCommand = withCdpCommand("CSS.createStyleSheet", CreateStyleSheetParams, CreateStyleSheetResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "CSS.disable.params", "commandParams", { method: "CSS.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "CSS.disable.result", "commandResult", { method: "CSS.disable" }); +export const DisableCommand = withCdpCommand("CSS.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "CSS.enable.params", "commandParams", { method: "CSS.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "CSS.enable.result", "commandResult", { method: "CSS.enable" }); +export const EnableCommand = withCdpCommand("CSS.enable", EnableParams, EnableResult); export const ForcePseudoStateParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "forcedPseudoClasses": z.array(z.string()) }).passthrough(), "CSS.forcePseudoState.params", "commandParams", { method: "CSS.forcePseudoState" }); export const ForcePseudoStateResult = withCdpMeta(z.object({ }).passthrough(), "CSS.forcePseudoState.result", "commandResult", { method: "CSS.forcePseudoState" }); +export const ForcePseudoStateCommand = withCdpCommand("CSS.forcePseudoState", ForcePseudoStateParams, ForcePseudoStateResult); export const ForceStartingStyleParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "forced": z.boolean() }).passthrough(), "CSS.forceStartingStyle.params", "commandParams", { method: "CSS.forceStartingStyle" }); export const ForceStartingStyleResult = withCdpMeta(z.object({ }).passthrough(), "CSS.forceStartingStyle.result", "commandResult", { method: "CSS.forceStartingStyle" }); +export const ForceStartingStyleCommand = withCdpCommand("CSS.forceStartingStyle", ForceStartingStyleParams, ForceStartingStyleResult); export const GetBackgroundColorsParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getBackgroundColors.params", "commandParams", { method: "CSS.getBackgroundColors" }); export const GetBackgroundColorsResult = withCdpMeta(z.object({ "backgroundColors": z.array(z.string()).optional(), "computedFontSize": z.string().optional(), "computedFontWeight": z.string().optional() }).passthrough(), "CSS.getBackgroundColors.result", "commandResult", { method: "CSS.getBackgroundColors" }); +export const GetBackgroundColorsCommand = withCdpCommand("CSS.getBackgroundColors", GetBackgroundColorsParams, GetBackgroundColorsResult); export const GetComputedStyleForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getComputedStyleForNode.params", "commandParams", { method: "CSS.getComputedStyleForNode" }); export const GetComputedStyleForNodeResult = withCdpMeta(z.object({ "computedStyle": z.array(z.lazy(() => CSSComputedStyleProperty)), "extraFields": z.lazy(() => ComputedStyleExtraFields) }).passthrough(), "CSS.getComputedStyleForNode.result", "commandResult", { method: "CSS.getComputedStyleForNode" }); +export const GetComputedStyleForNodeCommand = withCdpCommand("CSS.getComputedStyleForNode", GetComputedStyleForNodeParams, GetComputedStyleForNodeResult); export const ResolveValuesParams = withCdpMeta(z.object({ "values": z.array(z.string()), "nodeId": z.lazy(() => DOM.NodeId), "propertyName": z.string().optional(), "pseudoType": z.lazy(() => DOM.PseudoType).optional(), "pseudoIdentifier": z.string().optional() }).passthrough(), "CSS.resolveValues.params", "commandParams", { method: "CSS.resolveValues" }); export const ResolveValuesResult = withCdpMeta(z.object({ "results": z.array(z.string()) }).passthrough(), "CSS.resolveValues.result", "commandResult", { method: "CSS.resolveValues" }); +export const ResolveValuesCommand = withCdpCommand("CSS.resolveValues", ResolveValuesParams, ResolveValuesResult); export const GetLonghandPropertiesParams = withCdpMeta(z.object({ "shorthandName": z.string(), "value": z.string() }).passthrough(), "CSS.getLonghandProperties.params", "commandParams", { method: "CSS.getLonghandProperties" }); export const GetLonghandPropertiesResult = withCdpMeta(z.object({ "longhandProperties": z.array(z.lazy(() => CSSProperty)) }).passthrough(), "CSS.getLonghandProperties.result", "commandResult", { method: "CSS.getLonghandProperties" }); +export const GetLonghandPropertiesCommand = withCdpCommand("CSS.getLonghandProperties", GetLonghandPropertiesParams, GetLonghandPropertiesResult); export const GetInlineStylesForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getInlineStylesForNode.params", "commandParams", { method: "CSS.getInlineStylesForNode" }); export const GetInlineStylesForNodeResult = withCdpMeta(z.object({ "inlineStyle": z.lazy(() => CSSStyle).optional(), "attributesStyle": z.lazy(() => CSSStyle).optional() }).passthrough(), "CSS.getInlineStylesForNode.result", "commandResult", { method: "CSS.getInlineStylesForNode" }); +export const GetInlineStylesForNodeCommand = withCdpCommand("CSS.getInlineStylesForNode", GetInlineStylesForNodeParams, GetInlineStylesForNodeResult); export const GetAnimatedStylesForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getAnimatedStylesForNode.params", "commandParams", { method: "CSS.getAnimatedStylesForNode" }); export const GetAnimatedStylesForNodeResult = withCdpMeta(z.object({ "animationStyles": z.array(z.lazy(() => CSSAnimationStyle)).optional(), "transitionsStyle": z.lazy(() => CSSStyle).optional(), "inherited": z.array(z.lazy(() => InheritedAnimatedStyleEntry)).optional() }).passthrough(), "CSS.getAnimatedStylesForNode.result", "commandResult", { method: "CSS.getAnimatedStylesForNode" }); +export const GetAnimatedStylesForNodeCommand = withCdpCommand("CSS.getAnimatedStylesForNode", GetAnimatedStylesForNodeParams, GetAnimatedStylesForNodeResult); export const GetMatchedStylesForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getMatchedStylesForNode.params", "commandParams", { method: "CSS.getMatchedStylesForNode" }); export const GetMatchedStylesForNodeResult = withCdpMeta(z.object({ "inlineStyle": z.lazy(() => CSSStyle).optional(), "attributesStyle": z.lazy(() => CSSStyle).optional(), "matchedCSSRules": z.array(z.lazy(() => RuleMatch)).optional(), "pseudoElements": z.array(z.lazy(() => PseudoElementMatches)).optional(), "inherited": z.array(z.lazy(() => InheritedStyleEntry)).optional(), "inheritedPseudoElements": z.array(z.lazy(() => InheritedPseudoElementMatches)).optional(), "cssKeyframesRules": z.array(z.lazy(() => CSSKeyframesRule)).optional(), "cssPositionTryRules": z.array(z.lazy(() => CSSPositionTryRule)).optional(), "activePositionFallbackIndex": z.number().int().optional(), "cssPropertyRules": z.array(z.lazy(() => CSSPropertyRule)).optional(), "cssPropertyRegistrations": z.array(z.lazy(() => CSSPropertyRegistration)).optional(), "cssAtRules": z.array(z.lazy(() => CSSAtRule)).optional(), "parentLayoutNodeId": z.lazy(() => DOM.NodeId).optional(), "cssFunctionRules": z.array(z.lazy(() => CSSFunctionRule)).optional() }).passthrough(), "CSS.getMatchedStylesForNode.result", "commandResult", { method: "CSS.getMatchedStylesForNode" }); +export const GetMatchedStylesForNodeCommand = withCdpCommand("CSS.getMatchedStylesForNode", GetMatchedStylesForNodeParams, GetMatchedStylesForNodeResult); export const GetEnvironmentVariablesParams = withCdpMeta(z.object({ }).passthrough(), "CSS.getEnvironmentVariables.params", "commandParams", { method: "CSS.getEnvironmentVariables" }); export const GetEnvironmentVariablesResult = withCdpMeta(z.object({ "environmentVariables": z.record(z.string(), z.unknown()) }).passthrough(), "CSS.getEnvironmentVariables.result", "commandResult", { method: "CSS.getEnvironmentVariables" }); +export const GetEnvironmentVariablesCommand = withCdpCommand("CSS.getEnvironmentVariables", GetEnvironmentVariablesParams, GetEnvironmentVariablesResult); export const GetMediaQueriesParams = withCdpMeta(z.object({ }).passthrough(), "CSS.getMediaQueries.params", "commandParams", { method: "CSS.getMediaQueries" }); export const GetMediaQueriesResult = withCdpMeta(z.object({ "medias": z.array(z.lazy(() => CSSMedia)) }).passthrough(), "CSS.getMediaQueries.result", "commandResult", { method: "CSS.getMediaQueries" }); +export const GetMediaQueriesCommand = withCdpCommand("CSS.getMediaQueries", GetMediaQueriesParams, GetMediaQueriesResult); export const GetPlatformFontsForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getPlatformFontsForNode.params", "commandParams", { method: "CSS.getPlatformFontsForNode" }); export const GetPlatformFontsForNodeResult = withCdpMeta(z.object({ "fonts": z.array(z.lazy(() => PlatformFontUsage)) }).passthrough(), "CSS.getPlatformFontsForNode.result", "commandResult", { method: "CSS.getPlatformFontsForNode" }); +export const GetPlatformFontsForNodeCommand = withCdpCommand("CSS.getPlatformFontsForNode", GetPlatformFontsForNodeParams, GetPlatformFontsForNodeResult); export const GetStyleSheetTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId) }).passthrough(), "CSS.getStyleSheetText.params", "commandParams", { method: "CSS.getStyleSheetText" }); export const GetStyleSheetTextResult = withCdpMeta(z.object({ "text": z.string() }).passthrough(), "CSS.getStyleSheetText.result", "commandResult", { method: "CSS.getStyleSheetText" }); +export const GetStyleSheetTextCommand = withCdpCommand("CSS.getStyleSheetText", GetStyleSheetTextParams, GetStyleSheetTextResult); export const GetLayersForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "CSS.getLayersForNode.params", "commandParams", { method: "CSS.getLayersForNode" }); export const GetLayersForNodeResult = withCdpMeta(z.object({ "rootLayer": z.lazy(() => CSSLayerData) }).passthrough(), "CSS.getLayersForNode.result", "commandResult", { method: "CSS.getLayersForNode" }); +export const GetLayersForNodeCommand = withCdpCommand("CSS.getLayersForNode", GetLayersForNodeParams, GetLayersForNodeResult); export const GetLocationForSelectorParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "selectorText": z.string() }).passthrough(), "CSS.getLocationForSelector.params", "commandParams", { method: "CSS.getLocationForSelector" }); export const GetLocationForSelectorResult = withCdpMeta(z.object({ "ranges": z.array(z.lazy(() => SourceRange)) }).passthrough(), "CSS.getLocationForSelector.result", "commandResult", { method: "CSS.getLocationForSelector" }); +export const GetLocationForSelectorCommand = withCdpCommand("CSS.getLocationForSelector", GetLocationForSelectorParams, GetLocationForSelectorResult); export const TrackComputedStyleUpdatesForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId).optional() }).passthrough(), "CSS.trackComputedStyleUpdatesForNode.params", "commandParams", { method: "CSS.trackComputedStyleUpdatesForNode" }); export const TrackComputedStyleUpdatesForNodeResult = withCdpMeta(z.object({ }).passthrough(), "CSS.trackComputedStyleUpdatesForNode.result", "commandResult", { method: "CSS.trackComputedStyleUpdatesForNode" }); +export const TrackComputedStyleUpdatesForNodeCommand = withCdpCommand("CSS.trackComputedStyleUpdatesForNode", TrackComputedStyleUpdatesForNodeParams, TrackComputedStyleUpdatesForNodeResult); export const TrackComputedStyleUpdatesParams = withCdpMeta(z.object({ "propertiesToTrack": z.array(z.lazy(() => CSSComputedStyleProperty)) }).passthrough(), "CSS.trackComputedStyleUpdates.params", "commandParams", { method: "CSS.trackComputedStyleUpdates" }); export const TrackComputedStyleUpdatesResult = withCdpMeta(z.object({ }).passthrough(), "CSS.trackComputedStyleUpdates.result", "commandResult", { method: "CSS.trackComputedStyleUpdates" }); +export const TrackComputedStyleUpdatesCommand = withCdpCommand("CSS.trackComputedStyleUpdates", TrackComputedStyleUpdatesParams, TrackComputedStyleUpdatesResult); export const TakeComputedStyleUpdatesParams = withCdpMeta(z.object({ }).passthrough(), "CSS.takeComputedStyleUpdates.params", "commandParams", { method: "CSS.takeComputedStyleUpdates" }); export const TakeComputedStyleUpdatesResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => DOM.NodeId)) }).passthrough(), "CSS.takeComputedStyleUpdates.result", "commandResult", { method: "CSS.takeComputedStyleUpdates" }); +export const TakeComputedStyleUpdatesCommand = withCdpCommand("CSS.takeComputedStyleUpdates", TakeComputedStyleUpdatesParams, TakeComputedStyleUpdatesResult); export const SetEffectivePropertyValueForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "propertyName": z.string(), "value": z.string() }).passthrough(), "CSS.setEffectivePropertyValueForNode.params", "commandParams", { method: "CSS.setEffectivePropertyValueForNode" }); export const SetEffectivePropertyValueForNodeResult = withCdpMeta(z.object({ }).passthrough(), "CSS.setEffectivePropertyValueForNode.result", "commandResult", { method: "CSS.setEffectivePropertyValueForNode" }); +export const SetEffectivePropertyValueForNodeCommand = withCdpCommand("CSS.setEffectivePropertyValueForNode", SetEffectivePropertyValueForNodeParams, SetEffectivePropertyValueForNodeResult); export const SetPropertyRulePropertyNameParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "propertyName": z.string() }).passthrough(), "CSS.setPropertyRulePropertyName.params", "commandParams", { method: "CSS.setPropertyRulePropertyName" }); export const SetPropertyRulePropertyNameResult = withCdpMeta(z.object({ "propertyName": z.lazy(() => Value) }).passthrough(), "CSS.setPropertyRulePropertyName.result", "commandResult", { method: "CSS.setPropertyRulePropertyName" }); +export const SetPropertyRulePropertyNameCommand = withCdpCommand("CSS.setPropertyRulePropertyName", SetPropertyRulePropertyNameParams, SetPropertyRulePropertyNameResult); export const SetKeyframeKeyParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "keyText": z.string() }).passthrough(), "CSS.setKeyframeKey.params", "commandParams", { method: "CSS.setKeyframeKey" }); export const SetKeyframeKeyResult = withCdpMeta(z.object({ "keyText": z.lazy(() => Value) }).passthrough(), "CSS.setKeyframeKey.result", "commandResult", { method: "CSS.setKeyframeKey" }); +export const SetKeyframeKeyCommand = withCdpCommand("CSS.setKeyframeKey", SetKeyframeKeyParams, SetKeyframeKeyResult); export const SetMediaTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.setMediaText.params", "commandParams", { method: "CSS.setMediaText" }); export const SetMediaTextResult = withCdpMeta(z.object({ "media": z.lazy(() => CSSMedia) }).passthrough(), "CSS.setMediaText.result", "commandResult", { method: "CSS.setMediaText" }); +export const SetMediaTextCommand = withCdpCommand("CSS.setMediaText", SetMediaTextParams, SetMediaTextResult); export const SetContainerQueryTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.setContainerQueryText.params", "commandParams", { method: "CSS.setContainerQueryText" }); export const SetContainerQueryTextResult = withCdpMeta(z.object({ "containerQuery": z.lazy(() => CSSContainerQuery) }).passthrough(), "CSS.setContainerQueryText.result", "commandResult", { method: "CSS.setContainerQueryText" }); +export const SetContainerQueryTextCommand = withCdpCommand("CSS.setContainerQueryText", SetContainerQueryTextParams, SetContainerQueryTextResult); export const SetSupportsTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.setSupportsText.params", "commandParams", { method: "CSS.setSupportsText" }); export const SetSupportsTextResult = withCdpMeta(z.object({ "supports": z.lazy(() => CSSSupports) }).passthrough(), "CSS.setSupportsText.result", "commandResult", { method: "CSS.setSupportsText" }); +export const SetSupportsTextCommand = withCdpCommand("CSS.setSupportsText", SetSupportsTextParams, SetSupportsTextResult); export const SetNavigationTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.setNavigationText.params", "commandParams", { method: "CSS.setNavigationText" }); export const SetNavigationTextResult = withCdpMeta(z.object({ "navigation": z.lazy(() => CSSNavigation) }).passthrough(), "CSS.setNavigationText.result", "commandResult", { method: "CSS.setNavigationText" }); +export const SetNavigationTextCommand = withCdpCommand("CSS.setNavigationText", SetNavigationTextParams, SetNavigationTextResult); export const SetScopeTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "text": z.string() }).passthrough(), "CSS.setScopeText.params", "commandParams", { method: "CSS.setScopeText" }); export const SetScopeTextResult = withCdpMeta(z.object({ "scope": z.lazy(() => CSSScope) }).passthrough(), "CSS.setScopeText.result", "commandResult", { method: "CSS.setScopeText" }); +export const SetScopeTextCommand = withCdpCommand("CSS.setScopeText", SetScopeTextParams, SetScopeTextResult); export const SetRuleSelectorParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "range": z.lazy(() => SourceRange), "selector": z.string() }).passthrough(), "CSS.setRuleSelector.params", "commandParams", { method: "CSS.setRuleSelector" }); export const SetRuleSelectorResult = withCdpMeta(z.object({ "selectorList": z.lazy(() => SelectorList) }).passthrough(), "CSS.setRuleSelector.result", "commandResult", { method: "CSS.setRuleSelector" }); +export const SetRuleSelectorCommand = withCdpCommand("CSS.setRuleSelector", SetRuleSelectorParams, SetRuleSelectorResult); export const SetStyleSheetTextParams = withCdpMeta(z.object({ "styleSheetId": z.lazy(() => DOM.StyleSheetId), "text": z.string() }).passthrough(), "CSS.setStyleSheetText.params", "commandParams", { method: "CSS.setStyleSheetText" }); export const SetStyleSheetTextResult = withCdpMeta(z.object({ "sourceMapURL": z.string().optional() }).passthrough(), "CSS.setStyleSheetText.result", "commandResult", { method: "CSS.setStyleSheetText" }); +export const SetStyleSheetTextCommand = withCdpCommand("CSS.setStyleSheetText", SetStyleSheetTextParams, SetStyleSheetTextResult); export const SetStyleTextsParams = withCdpMeta(z.object({ "edits": z.array(z.lazy(() => StyleDeclarationEdit)), "nodeForPropertySyntaxValidation": z.lazy(() => DOM.NodeId).optional() }).passthrough(), "CSS.setStyleTexts.params", "commandParams", { method: "CSS.setStyleTexts" }); export const SetStyleTextsResult = withCdpMeta(z.object({ "styles": z.array(z.lazy(() => CSSStyle)) }).passthrough(), "CSS.setStyleTexts.result", "commandResult", { method: "CSS.setStyleTexts" }); +export const SetStyleTextsCommand = withCdpCommand("CSS.setStyleTexts", SetStyleTextsParams, SetStyleTextsResult); export const StartRuleUsageTrackingParams = withCdpMeta(z.object({ }).passthrough(), "CSS.startRuleUsageTracking.params", "commandParams", { method: "CSS.startRuleUsageTracking" }); export const StartRuleUsageTrackingResult = withCdpMeta(z.object({ }).passthrough(), "CSS.startRuleUsageTracking.result", "commandResult", { method: "CSS.startRuleUsageTracking" }); +export const StartRuleUsageTrackingCommand = withCdpCommand("CSS.startRuleUsageTracking", StartRuleUsageTrackingParams, StartRuleUsageTrackingResult); export const StopRuleUsageTrackingParams = withCdpMeta(z.object({ }).passthrough(), "CSS.stopRuleUsageTracking.params", "commandParams", { method: "CSS.stopRuleUsageTracking" }); export const StopRuleUsageTrackingResult = withCdpMeta(z.object({ "ruleUsage": z.array(z.lazy(() => RuleUsage)) }).passthrough(), "CSS.stopRuleUsageTracking.result", "commandResult", { method: "CSS.stopRuleUsageTracking" }); +export const StopRuleUsageTrackingCommand = withCdpCommand("CSS.stopRuleUsageTracking", StopRuleUsageTrackingParams, StopRuleUsageTrackingResult); export const TakeCoverageDeltaParams = withCdpMeta(z.object({ }).passthrough(), "CSS.takeCoverageDelta.params", "commandParams", { method: "CSS.takeCoverageDelta" }); export const TakeCoverageDeltaResult = withCdpMeta(z.object({ "coverage": z.array(z.lazy(() => RuleUsage)), "timestamp": z.number() }).passthrough(), "CSS.takeCoverageDelta.result", "commandResult", { method: "CSS.takeCoverageDelta" }); +export const TakeCoverageDeltaCommand = withCdpCommand("CSS.takeCoverageDelta", TakeCoverageDeltaParams, TakeCoverageDeltaResult); export const SetLocalFontsEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "CSS.setLocalFontsEnabled.params", "commandParams", { method: "CSS.setLocalFontsEnabled" }); export const SetLocalFontsEnabledResult = withCdpMeta(z.object({ }).passthrough(), "CSS.setLocalFontsEnabled.result", "commandResult", { method: "CSS.setLocalFontsEnabled" }); +export const SetLocalFontsEnabledCommand = withCdpCommand("CSS.setLocalFontsEnabled", SetLocalFontsEnabledParams, SetLocalFontsEnabledResult); export const FontsUpdatedEvent = withCdpMeta(z.object({ "font": z.lazy(() => FontFace).optional() }).passthrough(), "CSS.fontsUpdated", "event", { phase: "event" }); export const MediaQueryResultChangedEvent = withCdpMeta(z.object({ }).passthrough(), "CSS.mediaQueryResultChanged", "event", { phase: "event" }); export const StyleSheetAddedEvent = withCdpMeta(z.object({ "header": z.lazy(() => CSSStyleSheetHeader) }).passthrough(), "CSS.styleSheetAdded", "event", { phase: "event" }); @@ -263,44 +301,44 @@ export const zod = { ComputedStyleUpdatedEvent: ComputedStyleUpdatedEvent, } as const; export const commands = { - "CSS.addRule": { params: AddRuleParams, result: AddRuleResult }, - "CSS.collectClassNames": { params: CollectClassNamesParams, result: CollectClassNamesResult }, - "CSS.createStyleSheet": { params: CreateStyleSheetParams, result: CreateStyleSheetResult }, - "CSS.disable": { params: DisableParams, result: DisableResult }, - "CSS.enable": { params: EnableParams, result: EnableResult }, - "CSS.forcePseudoState": { params: ForcePseudoStateParams, result: ForcePseudoStateResult }, - "CSS.forceStartingStyle": { params: ForceStartingStyleParams, result: ForceStartingStyleResult }, - "CSS.getBackgroundColors": { params: GetBackgroundColorsParams, result: GetBackgroundColorsResult }, - "CSS.getComputedStyleForNode": { params: GetComputedStyleForNodeParams, result: GetComputedStyleForNodeResult }, - "CSS.resolveValues": { params: ResolveValuesParams, result: ResolveValuesResult }, - "CSS.getLonghandProperties": { params: GetLonghandPropertiesParams, result: GetLonghandPropertiesResult }, - "CSS.getInlineStylesForNode": { params: GetInlineStylesForNodeParams, result: GetInlineStylesForNodeResult }, - "CSS.getAnimatedStylesForNode": { params: GetAnimatedStylesForNodeParams, result: GetAnimatedStylesForNodeResult }, - "CSS.getMatchedStylesForNode": { params: GetMatchedStylesForNodeParams, result: GetMatchedStylesForNodeResult }, - "CSS.getEnvironmentVariables": { params: GetEnvironmentVariablesParams, result: GetEnvironmentVariablesResult }, - "CSS.getMediaQueries": { params: GetMediaQueriesParams, result: GetMediaQueriesResult }, - "CSS.getPlatformFontsForNode": { params: GetPlatformFontsForNodeParams, result: GetPlatformFontsForNodeResult }, - "CSS.getStyleSheetText": { params: GetStyleSheetTextParams, result: GetStyleSheetTextResult }, - "CSS.getLayersForNode": { params: GetLayersForNodeParams, result: GetLayersForNodeResult }, - "CSS.getLocationForSelector": { params: GetLocationForSelectorParams, result: GetLocationForSelectorResult }, - "CSS.trackComputedStyleUpdatesForNode": { params: TrackComputedStyleUpdatesForNodeParams, result: TrackComputedStyleUpdatesForNodeResult }, - "CSS.trackComputedStyleUpdates": { params: TrackComputedStyleUpdatesParams, result: TrackComputedStyleUpdatesResult }, - "CSS.takeComputedStyleUpdates": { params: TakeComputedStyleUpdatesParams, result: TakeComputedStyleUpdatesResult }, - "CSS.setEffectivePropertyValueForNode": { params: SetEffectivePropertyValueForNodeParams, result: SetEffectivePropertyValueForNodeResult }, - "CSS.setPropertyRulePropertyName": { params: SetPropertyRulePropertyNameParams, result: SetPropertyRulePropertyNameResult }, - "CSS.setKeyframeKey": { params: SetKeyframeKeyParams, result: SetKeyframeKeyResult }, - "CSS.setMediaText": { params: SetMediaTextParams, result: SetMediaTextResult }, - "CSS.setContainerQueryText": { params: SetContainerQueryTextParams, result: SetContainerQueryTextResult }, - "CSS.setSupportsText": { params: SetSupportsTextParams, result: SetSupportsTextResult }, - "CSS.setNavigationText": { params: SetNavigationTextParams, result: SetNavigationTextResult }, - "CSS.setScopeText": { params: SetScopeTextParams, result: SetScopeTextResult }, - "CSS.setRuleSelector": { params: SetRuleSelectorParams, result: SetRuleSelectorResult }, - "CSS.setStyleSheetText": { params: SetStyleSheetTextParams, result: SetStyleSheetTextResult }, - "CSS.setStyleTexts": { params: SetStyleTextsParams, result: SetStyleTextsResult }, - "CSS.startRuleUsageTracking": { params: StartRuleUsageTrackingParams, result: StartRuleUsageTrackingResult }, - "CSS.stopRuleUsageTracking": { params: StopRuleUsageTrackingParams, result: StopRuleUsageTrackingResult }, - "CSS.takeCoverageDelta": { params: TakeCoverageDeltaParams, result: TakeCoverageDeltaResult }, - "CSS.setLocalFontsEnabled": { params: SetLocalFontsEnabledParams, result: SetLocalFontsEnabledResult }, + "CSS.addRule": AddRuleCommand, + "CSS.collectClassNames": CollectClassNamesCommand, + "CSS.createStyleSheet": CreateStyleSheetCommand, + "CSS.disable": DisableCommand, + "CSS.enable": EnableCommand, + "CSS.forcePseudoState": ForcePseudoStateCommand, + "CSS.forceStartingStyle": ForceStartingStyleCommand, + "CSS.getBackgroundColors": GetBackgroundColorsCommand, + "CSS.getComputedStyleForNode": GetComputedStyleForNodeCommand, + "CSS.resolveValues": ResolveValuesCommand, + "CSS.getLonghandProperties": GetLonghandPropertiesCommand, + "CSS.getInlineStylesForNode": GetInlineStylesForNodeCommand, + "CSS.getAnimatedStylesForNode": GetAnimatedStylesForNodeCommand, + "CSS.getMatchedStylesForNode": GetMatchedStylesForNodeCommand, + "CSS.getEnvironmentVariables": GetEnvironmentVariablesCommand, + "CSS.getMediaQueries": GetMediaQueriesCommand, + "CSS.getPlatformFontsForNode": GetPlatformFontsForNodeCommand, + "CSS.getStyleSheetText": GetStyleSheetTextCommand, + "CSS.getLayersForNode": GetLayersForNodeCommand, + "CSS.getLocationForSelector": GetLocationForSelectorCommand, + "CSS.trackComputedStyleUpdatesForNode": TrackComputedStyleUpdatesForNodeCommand, + "CSS.trackComputedStyleUpdates": TrackComputedStyleUpdatesCommand, + "CSS.takeComputedStyleUpdates": TakeComputedStyleUpdatesCommand, + "CSS.setEffectivePropertyValueForNode": SetEffectivePropertyValueForNodeCommand, + "CSS.setPropertyRulePropertyName": SetPropertyRulePropertyNameCommand, + "CSS.setKeyframeKey": SetKeyframeKeyCommand, + "CSS.setMediaText": SetMediaTextCommand, + "CSS.setContainerQueryText": SetContainerQueryTextCommand, + "CSS.setSupportsText": SetSupportsTextCommand, + "CSS.setNavigationText": SetNavigationTextCommand, + "CSS.setScopeText": SetScopeTextCommand, + "CSS.setRuleSelector": SetRuleSelectorCommand, + "CSS.setStyleSheetText": SetStyleSheetTextCommand, + "CSS.setStyleTexts": SetStyleTextsCommand, + "CSS.startRuleUsageTracking": StartRuleUsageTrackingCommand, + "CSS.stopRuleUsageTracking": StopRuleUsageTrackingCommand, + "CSS.takeCoverageDelta": TakeCoverageDeltaCommand, + "CSS.setLocalFontsEnabled": SetLocalFontsEnabledCommand, } as const; export const events = { "CSS.fontsUpdated": FontsUpdatedEvent, diff --git a/js/src/types/generated/zod/CacheStorage.ts b/js/src/types/generated/zod/CacheStorage.ts index 817a252e..ea4fef1b 100644 --- a/js/src/types/generated/zod/CacheStorage.ts +++ b/js/src/types/generated/zod/CacheStorage.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Storage from "./Storage.js"; export const CacheId = withCdpMeta(z.string(), "CacheStorage.CacheId", "type"); @@ -12,14 +12,19 @@ export const Header = withCdpMeta(z.object({ "name": z.string(), "value": z.stri export const CachedResponse = withCdpMeta(z.object({ "body": z.string() }).passthrough(), "CacheStorage.CachedResponse", "type"); export const DeleteCacheParams = withCdpMeta(z.object({ "cacheId": z.lazy(() => CacheId) }).passthrough(), "CacheStorage.deleteCache.params", "commandParams", { method: "CacheStorage.deleteCache" }); export const DeleteCacheResult = withCdpMeta(z.object({ }).passthrough(), "CacheStorage.deleteCache.result", "commandResult", { method: "CacheStorage.deleteCache" }); +export const DeleteCacheCommand = withCdpCommand("CacheStorage.deleteCache", DeleteCacheParams, DeleteCacheResult); export const DeleteEntryParams = withCdpMeta(z.object({ "cacheId": z.lazy(() => CacheId), "request": z.string() }).passthrough(), "CacheStorage.deleteEntry.params", "commandParams", { method: "CacheStorage.deleteEntry" }); export const DeleteEntryResult = withCdpMeta(z.object({ }).passthrough(), "CacheStorage.deleteEntry.result", "commandResult", { method: "CacheStorage.deleteEntry" }); +export const DeleteEntryCommand = withCdpCommand("CacheStorage.deleteEntry", DeleteEntryParams, DeleteEntryResult); export const RequestCacheNamesParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional() }).passthrough().refine((value) => [value.securityOrigin, value.storageKey, value.storageBucket].filter((item) => item !== undefined).length === 1, { message: "Exactly one of securityOrigin, storageKey, or storageBucket must be provided." }), "CacheStorage.requestCacheNames.params", "commandParams", { method: "CacheStorage.requestCacheNames" }); export const RequestCacheNamesResult = withCdpMeta(z.object({ "caches": z.array(z.lazy(() => Cache)) }).passthrough(), "CacheStorage.requestCacheNames.result", "commandResult", { method: "CacheStorage.requestCacheNames" }); +export const RequestCacheNamesCommand = withCdpCommand("CacheStorage.requestCacheNames", RequestCacheNamesParams, RequestCacheNamesResult); export const RequestCachedResponseParams = withCdpMeta(z.object({ "cacheId": z.lazy(() => CacheId), "requestURL": z.string(), "requestHeaders": z.array(z.lazy(() => Header)) }).passthrough(), "CacheStorage.requestCachedResponse.params", "commandParams", { method: "CacheStorage.requestCachedResponse" }); export const RequestCachedResponseResult = withCdpMeta(z.object({ "response": z.lazy(() => CachedResponse) }).passthrough(), "CacheStorage.requestCachedResponse.result", "commandResult", { method: "CacheStorage.requestCachedResponse" }); +export const RequestCachedResponseCommand = withCdpCommand("CacheStorage.requestCachedResponse", RequestCachedResponseParams, RequestCachedResponseResult); export const RequestEntriesParams = withCdpMeta(z.object({ "cacheId": z.lazy(() => CacheId), "skipCount": z.number().int().optional(), "pageSize": z.number().int().optional(), "pathFilter": z.string().optional() }).passthrough(), "CacheStorage.requestEntries.params", "commandParams", { method: "CacheStorage.requestEntries" }); export const RequestEntriesResult = withCdpMeta(z.object({ "cacheDataEntries": z.array(z.lazy(() => DataEntry)), "returnCount": z.number() }).passthrough(), "CacheStorage.requestEntries.result", "commandResult", { method: "CacheStorage.requestEntries" }); +export const RequestEntriesCommand = withCdpCommand("CacheStorage.requestEntries", RequestEntriesParams, RequestEntriesResult); export const zod = { CacheId: CacheId, @@ -40,11 +45,11 @@ export const zod = { RequestEntriesResult: RequestEntriesResult, } as const; export const commands = { - "CacheStorage.deleteCache": { params: DeleteCacheParams, result: DeleteCacheResult }, - "CacheStorage.deleteEntry": { params: DeleteEntryParams, result: DeleteEntryResult }, - "CacheStorage.requestCacheNames": { params: RequestCacheNamesParams, result: RequestCacheNamesResult }, - "CacheStorage.requestCachedResponse": { params: RequestCachedResponseParams, result: RequestCachedResponseResult }, - "CacheStorage.requestEntries": { params: RequestEntriesParams, result: RequestEntriesResult }, + "CacheStorage.deleteCache": DeleteCacheCommand, + "CacheStorage.deleteEntry": DeleteEntryCommand, + "CacheStorage.requestCacheNames": RequestCacheNamesCommand, + "CacheStorage.requestCachedResponse": RequestCachedResponseCommand, + "CacheStorage.requestEntries": RequestEntriesCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Cast.ts b/js/src/types/generated/zod/Cast.ts index e9f25ddf..49c54afc 100644 --- a/js/src/types/generated/zod/Cast.ts +++ b/js/src/types/generated/zod/Cast.ts @@ -1,21 +1,27 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const Sink = withCdpMeta(z.object({ "name": z.string(), "id": z.string(), "session": z.string().optional() }).passthrough(), "Cast.Sink", "type"); export const EnableParams = withCdpMeta(z.object({ "presentationUrl": z.string().optional() }).passthrough(), "Cast.enable.params", "commandParams", { method: "Cast.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Cast.enable.result", "commandResult", { method: "Cast.enable" }); +export const EnableCommand = withCdpCommand("Cast.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Cast.disable.params", "commandParams", { method: "Cast.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Cast.disable.result", "commandResult", { method: "Cast.disable" }); +export const DisableCommand = withCdpCommand("Cast.disable", DisableParams, DisableResult); export const SetSinkToUseParams = withCdpMeta(z.object({ "sinkName": z.string() }).passthrough(), "Cast.setSinkToUse.params", "commandParams", { method: "Cast.setSinkToUse" }); export const SetSinkToUseResult = withCdpMeta(z.object({ }).passthrough(), "Cast.setSinkToUse.result", "commandResult", { method: "Cast.setSinkToUse" }); +export const SetSinkToUseCommand = withCdpCommand("Cast.setSinkToUse", SetSinkToUseParams, SetSinkToUseResult); export const StartDesktopMirroringParams = withCdpMeta(z.object({ "sinkName": z.string() }).passthrough(), "Cast.startDesktopMirroring.params", "commandParams", { method: "Cast.startDesktopMirroring" }); export const StartDesktopMirroringResult = withCdpMeta(z.object({ }).passthrough(), "Cast.startDesktopMirroring.result", "commandResult", { method: "Cast.startDesktopMirroring" }); +export const StartDesktopMirroringCommand = withCdpCommand("Cast.startDesktopMirroring", StartDesktopMirroringParams, StartDesktopMirroringResult); export const StartTabMirroringParams = withCdpMeta(z.object({ "sinkName": z.string() }).passthrough(), "Cast.startTabMirroring.params", "commandParams", { method: "Cast.startTabMirroring" }); export const StartTabMirroringResult = withCdpMeta(z.object({ }).passthrough(), "Cast.startTabMirroring.result", "commandResult", { method: "Cast.startTabMirroring" }); +export const StartTabMirroringCommand = withCdpCommand("Cast.startTabMirroring", StartTabMirroringParams, StartTabMirroringResult); export const StopCastingParams = withCdpMeta(z.object({ "sinkName": z.string() }).passthrough(), "Cast.stopCasting.params", "commandParams", { method: "Cast.stopCasting" }); export const StopCastingResult = withCdpMeta(z.object({ }).passthrough(), "Cast.stopCasting.result", "commandResult", { method: "Cast.stopCasting" }); +export const StopCastingCommand = withCdpCommand("Cast.stopCasting", StopCastingParams, StopCastingResult); export const SinksUpdatedEvent = withCdpMeta(z.object({ "sinks": z.array(z.lazy(() => Sink)) }).passthrough(), "Cast.sinksUpdated", "event", { phase: "event" }); export const IssueUpdatedEvent = withCdpMeta(z.object({ "issueMessage": z.string() }).passthrough(), "Cast.issueUpdated", "event", { phase: "event" }); @@ -37,12 +43,12 @@ export const zod = { IssueUpdatedEvent: IssueUpdatedEvent, } as const; export const commands = { - "Cast.enable": { params: EnableParams, result: EnableResult }, - "Cast.disable": { params: DisableParams, result: DisableResult }, - "Cast.setSinkToUse": { params: SetSinkToUseParams, result: SetSinkToUseResult }, - "Cast.startDesktopMirroring": { params: StartDesktopMirroringParams, result: StartDesktopMirroringResult }, - "Cast.startTabMirroring": { params: StartTabMirroringParams, result: StartTabMirroringResult }, - "Cast.stopCasting": { params: StopCastingParams, result: StopCastingResult }, + "Cast.enable": EnableCommand, + "Cast.disable": DisableCommand, + "Cast.setSinkToUse": SetSinkToUseCommand, + "Cast.startDesktopMirroring": StartDesktopMirroringCommand, + "Cast.startTabMirroring": StartTabMirroringCommand, + "Cast.stopCasting": StopCastingCommand, } as const; export const events = { "Cast.sinksUpdated": SinksUpdatedEvent, diff --git a/js/src/types/generated/zod/Console.ts b/js/src/types/generated/zod/Console.ts index d9579424..723be277 100644 --- a/js/src/types/generated/zod/Console.ts +++ b/js/src/types/generated/zod/Console.ts @@ -1,15 +1,18 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const ConsoleMessage = withCdpMeta(z.object({ "source": z.enum(["xml", "javascript", "network", "console-api", "storage", "appcache", "rendering", "security", "other", "deprecation", "worker"]), "level": z.enum(["log", "warning", "error", "debug", "info"]), "text": z.string(), "url": z.string().optional(), "line": z.number().int().optional(), "column": z.number().int().optional() }).passthrough(), "Console.ConsoleMessage", "type"); export const ClearMessagesParams = withCdpMeta(z.object({ }).passthrough(), "Console.clearMessages.params", "commandParams", { method: "Console.clearMessages" }); export const ClearMessagesResult = withCdpMeta(z.object({ }).passthrough(), "Console.clearMessages.result", "commandResult", { method: "Console.clearMessages" }); +export const ClearMessagesCommand = withCdpCommand("Console.clearMessages", ClearMessagesParams, ClearMessagesResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Console.disable.params", "commandParams", { method: "Console.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Console.disable.result", "commandResult", { method: "Console.disable" }); +export const DisableCommand = withCdpCommand("Console.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Console.enable.params", "commandParams", { method: "Console.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Console.enable.result", "commandResult", { method: "Console.enable" }); +export const EnableCommand = withCdpCommand("Console.enable", EnableParams, EnableResult); export const MessageAddedEvent = withCdpMeta(z.object({ "message": z.lazy(() => ConsoleMessage) }).passthrough(), "Console.messageAdded", "event", { phase: "event" }); export const zod = { @@ -23,9 +26,9 @@ export const zod = { MessageAddedEvent: MessageAddedEvent, } as const; export const commands = { - "Console.clearMessages": { params: ClearMessagesParams, result: ClearMessagesResult }, - "Console.disable": { params: DisableParams, result: DisableResult }, - "Console.enable": { params: EnableParams, result: EnableResult }, + "Console.clearMessages": ClearMessagesCommand, + "Console.disable": DisableCommand, + "Console.enable": EnableCommand, } as const; export const events = { "Console.messageAdded": MessageAddedEvent, diff --git a/js/src/types/generated/zod/CrashReportContext.ts b/js/src/types/generated/zod/CrashReportContext.ts index 75dc0138..965715f8 100644 --- a/js/src/types/generated/zod/CrashReportContext.ts +++ b/js/src/types/generated/zod/CrashReportContext.ts @@ -1,12 +1,13 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Page from "./Page.js"; export const CrashReportContextEntry = withCdpMeta(z.object({ "key": z.string(), "value": z.string(), "frameId": z.lazy(() => Page.FrameId) }).passthrough(), "CrashReportContext.CrashReportContextEntry", "type"); export const GetEntriesParams = withCdpMeta(z.object({ }).passthrough(), "CrashReportContext.getEntries.params", "commandParams", { method: "CrashReportContext.getEntries" }); export const GetEntriesResult = withCdpMeta(z.object({ "entries": z.array(z.lazy(() => CrashReportContextEntry)) }).passthrough(), "CrashReportContext.getEntries.result", "commandResult", { method: "CrashReportContext.getEntries" }); +export const GetEntriesCommand = withCdpCommand("CrashReportContext.getEntries", GetEntriesParams, GetEntriesResult); export const zod = { CrashReportContextEntry: CrashReportContextEntry, @@ -14,7 +15,7 @@ export const zod = { GetEntriesResult: GetEntriesResult, } as const; export const commands = { - "CrashReportContext.getEntries": { params: GetEntriesParams, result: GetEntriesResult }, + "CrashReportContext.getEntries": GetEntriesCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/DOM.ts b/js/src/types/generated/zod/DOM.ts index 0058ad51..72f2db8d 100644 --- a/js/src/types/generated/zod/DOM.ts +++ b/js/src/types/generated/zod/DOM.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; import * as Runtime from "./Runtime.js"; @@ -26,110 +26,163 @@ export const Rect = withCdpMeta(z.object({ "x": z.number(), "y": z.number(), "wi export const CSSComputedStyleProperty = withCdpMeta(z.object({ "name": z.string(), "value": z.string() }).passthrough(), "DOM.CSSComputedStyleProperty", "type"); export const CollectClassNamesFromSubtreeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.collectClassNamesFromSubtree.params", "commandParams", { method: "DOM.collectClassNamesFromSubtree" }); export const CollectClassNamesFromSubtreeResult = withCdpMeta(z.object({ "classNames": z.array(z.string()) }).passthrough(), "DOM.collectClassNamesFromSubtree.result", "commandResult", { method: "DOM.collectClassNamesFromSubtree" }); +export const CollectClassNamesFromSubtreeCommand = withCdpCommand("DOM.collectClassNamesFromSubtree", CollectClassNamesFromSubtreeParams, CollectClassNamesFromSubtreeResult); export const CopyToParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "targetNodeId": z.lazy(() => NodeId), "insertBeforeNodeId": z.lazy(() => NodeId).optional() }).passthrough(), "DOM.copyTo.params", "commandParams", { method: "DOM.copyTo" }); export const CopyToResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.copyTo.result", "commandResult", { method: "DOM.copyTo" }); +export const CopyToCommand = withCdpCommand("DOM.copyTo", CopyToParams, CopyToResult); export const DescribeNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "depth": z.number().int().optional(), "pierce": z.boolean().optional() }).passthrough(), "DOM.describeNode.params", "commandParams", { method: "DOM.describeNode" }); export const DescribeNodeResult = withCdpMeta(z.object({ "node": z.lazy(() => Node) }).passthrough(), "DOM.describeNode.result", "commandResult", { method: "DOM.describeNode" }); +export const DescribeNodeCommand = withCdpCommand("DOM.describeNode", DescribeNodeParams, DescribeNodeResult); export const ScrollIntoViewIfNeededParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "rect": z.lazy(() => Rect).optional() }).passthrough(), "DOM.scrollIntoViewIfNeeded.params", "commandParams", { method: "DOM.scrollIntoViewIfNeeded" }); export const ScrollIntoViewIfNeededResult = withCdpMeta(z.object({ }).passthrough(), "DOM.scrollIntoViewIfNeeded.result", "commandResult", { method: "DOM.scrollIntoViewIfNeeded" }); +export const ScrollIntoViewIfNeededCommand = withCdpCommand("DOM.scrollIntoViewIfNeeded", ScrollIntoViewIfNeededParams, ScrollIntoViewIfNeededResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "DOM.disable.params", "commandParams", { method: "DOM.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "DOM.disable.result", "commandResult", { method: "DOM.disable" }); +export const DisableCommand = withCdpCommand("DOM.disable", DisableParams, DisableResult); export const DiscardSearchResultsParams = withCdpMeta(z.object({ "searchId": z.string() }).passthrough(), "DOM.discardSearchResults.params", "commandParams", { method: "DOM.discardSearchResults" }); export const DiscardSearchResultsResult = withCdpMeta(z.object({ }).passthrough(), "DOM.discardSearchResults.result", "commandResult", { method: "DOM.discardSearchResults" }); +export const DiscardSearchResultsCommand = withCdpCommand("DOM.discardSearchResults", DiscardSearchResultsParams, DiscardSearchResultsResult); export const EnableParams = withCdpMeta(z.object({ "includeWhitespace": z.enum(["none", "all"]).optional() }).passthrough(), "DOM.enable.params", "commandParams", { method: "DOM.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "DOM.enable.result", "commandResult", { method: "DOM.enable" }); +export const EnableCommand = withCdpCommand("DOM.enable", EnableParams, EnableResult); export const FocusParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "DOM.focus.params", "commandParams", { method: "DOM.focus" }); export const FocusResult = withCdpMeta(z.object({ }).passthrough(), "DOM.focus.result", "commandResult", { method: "DOM.focus" }); +export const FocusCommand = withCdpCommand("DOM.focus", FocusParams, FocusResult); export const GetAttributesParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getAttributes.params", "commandParams", { method: "DOM.getAttributes" }); export const GetAttributesResult = withCdpMeta(z.object({ "attributes": z.array(z.string()) }).passthrough(), "DOM.getAttributes.result", "commandResult", { method: "DOM.getAttributes" }); +export const GetAttributesCommand = withCdpCommand("DOM.getAttributes", GetAttributesParams, GetAttributesResult); export const GetBoxModelParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "DOM.getBoxModel.params", "commandParams", { method: "DOM.getBoxModel" }); export const GetBoxModelResult = withCdpMeta(z.object({ "model": z.lazy(() => BoxModel) }).passthrough(), "DOM.getBoxModel.result", "commandResult", { method: "DOM.getBoxModel" }); +export const GetBoxModelCommand = withCdpCommand("DOM.getBoxModel", GetBoxModelParams, GetBoxModelResult); export const GetContentQuadsParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "DOM.getContentQuads.params", "commandParams", { method: "DOM.getContentQuads" }); export const GetContentQuadsResult = withCdpMeta(z.object({ "quads": z.array(z.lazy(() => Quad)) }).passthrough(), "DOM.getContentQuads.result", "commandResult", { method: "DOM.getContentQuads" }); +export const GetContentQuadsCommand = withCdpCommand("DOM.getContentQuads", GetContentQuadsParams, GetContentQuadsResult); export const GetDocumentParams = withCdpMeta(z.object({ "depth": z.number().int().optional(), "pierce": z.boolean().optional() }).passthrough(), "DOM.getDocument.params", "commandParams", { method: "DOM.getDocument" }); export const GetDocumentResult = withCdpMeta(z.object({ "root": z.lazy(() => Node) }).passthrough(), "DOM.getDocument.result", "commandResult", { method: "DOM.getDocument" }); +export const GetDocumentCommand = withCdpCommand("DOM.getDocument", GetDocumentParams, GetDocumentResult); export const GetFlattenedDocumentParams = withCdpMeta(z.object({ "depth": z.number().int().optional(), "pierce": z.boolean().optional() }).passthrough(), "DOM.getFlattenedDocument.params", "commandParams", { method: "DOM.getFlattenedDocument" }); export const GetFlattenedDocumentResult = withCdpMeta(z.object({ "nodes": z.array(z.lazy(() => Node)) }).passthrough(), "DOM.getFlattenedDocument.result", "commandResult", { method: "DOM.getFlattenedDocument" }); +export const GetFlattenedDocumentCommand = withCdpCommand("DOM.getFlattenedDocument", GetFlattenedDocumentParams, GetFlattenedDocumentResult); export const GetNodesForSubtreeByStyleParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "computedStyles": z.array(z.lazy(() => CSSComputedStyleProperty)), "pierce": z.boolean().optional() }).passthrough(), "DOM.getNodesForSubtreeByStyle.params", "commandParams", { method: "DOM.getNodesForSubtreeByStyle" }); export const GetNodesForSubtreeByStyleResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.getNodesForSubtreeByStyle.result", "commandResult", { method: "DOM.getNodesForSubtreeByStyle" }); +export const GetNodesForSubtreeByStyleCommand = withCdpCommand("DOM.getNodesForSubtreeByStyle", GetNodesForSubtreeByStyleParams, GetNodesForSubtreeByStyleResult); export const GetNodeForLocationParams = withCdpMeta(z.object({ "x": z.number().int(), "y": z.number().int(), "includeUserAgentShadowDOM": z.boolean().optional(), "ignorePointerEventsNone": z.boolean().optional() }).passthrough(), "DOM.getNodeForLocation.params", "commandParams", { method: "DOM.getNodeForLocation" }); export const GetNodeForLocationResult = withCdpMeta(z.object({ "backendNodeId": z.lazy(() => BackendNodeId), "frameId": z.lazy(() => Page.FrameId), "nodeId": z.lazy(() => NodeId).optional() }).passthrough(), "DOM.getNodeForLocation.result", "commandResult", { method: "DOM.getNodeForLocation" }); +export const GetNodeForLocationCommand = withCdpCommand("DOM.getNodeForLocation", GetNodeForLocationParams, GetNodeForLocationResult); export const GetOuterHTMLParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "includeShadowDOM": z.boolean().optional() }).passthrough(), "DOM.getOuterHTML.params", "commandParams", { method: "DOM.getOuterHTML" }); export const GetOuterHTMLResult = withCdpMeta(z.object({ "outerHTML": z.string() }).passthrough(), "DOM.getOuterHTML.result", "commandResult", { method: "DOM.getOuterHTML" }); +export const GetOuterHTMLCommand = withCdpCommand("DOM.getOuterHTML", GetOuterHTMLParams, GetOuterHTMLResult); export const GetRelayoutBoundaryParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getRelayoutBoundary.params", "commandParams", { method: "DOM.getRelayoutBoundary" }); export const GetRelayoutBoundaryResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getRelayoutBoundary.result", "commandResult", { method: "DOM.getRelayoutBoundary" }); +export const GetRelayoutBoundaryCommand = withCdpCommand("DOM.getRelayoutBoundary", GetRelayoutBoundaryParams, GetRelayoutBoundaryResult); export const GetSearchResultsParams = withCdpMeta(z.object({ "searchId": z.string(), "fromIndex": z.number().int(), "toIndex": z.number().int() }).passthrough(), "DOM.getSearchResults.params", "commandParams", { method: "DOM.getSearchResults" }); export const GetSearchResultsResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.getSearchResults.result", "commandResult", { method: "DOM.getSearchResults" }); +export const GetSearchResultsCommand = withCdpCommand("DOM.getSearchResults", GetSearchResultsParams, GetSearchResultsResult); export const HideHighlightParams = withCdpMeta(z.object({ }).passthrough(), "DOM.hideHighlight.params", "commandParams", { method: "DOM.hideHighlight" }); export const HideHighlightResult = withCdpMeta(z.object({ }).passthrough(), "DOM.hideHighlight.result", "commandResult", { method: "DOM.hideHighlight" }); +export const HideHighlightCommand = withCdpCommand("DOM.hideHighlight", HideHighlightParams, HideHighlightResult); export const HighlightNodeParams = withCdpMeta(z.object({ }).passthrough(), "DOM.highlightNode.params", "commandParams", { method: "DOM.highlightNode" }); export const HighlightNodeResult = withCdpMeta(z.object({ }).passthrough(), "DOM.highlightNode.result", "commandResult", { method: "DOM.highlightNode" }); +export const HighlightNodeCommand = withCdpCommand("DOM.highlightNode", HighlightNodeParams, HighlightNodeResult); export const HighlightRectParams = withCdpMeta(z.object({ }).passthrough(), "DOM.highlightRect.params", "commandParams", { method: "DOM.highlightRect" }); export const HighlightRectResult = withCdpMeta(z.object({ }).passthrough(), "DOM.highlightRect.result", "commandResult", { method: "DOM.highlightRect" }); +export const HighlightRectCommand = withCdpCommand("DOM.highlightRect", HighlightRectParams, HighlightRectResult); export const MarkUndoableStateParams = withCdpMeta(z.object({ }).passthrough(), "DOM.markUndoableState.params", "commandParams", { method: "DOM.markUndoableState" }); export const MarkUndoableStateResult = withCdpMeta(z.object({ }).passthrough(), "DOM.markUndoableState.result", "commandResult", { method: "DOM.markUndoableState" }); +export const MarkUndoableStateCommand = withCdpCommand("DOM.markUndoableState", MarkUndoableStateParams, MarkUndoableStateResult); export const MoveToParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "targetNodeId": z.lazy(() => NodeId), "insertBeforeNodeId": z.lazy(() => NodeId).optional() }).passthrough(), "DOM.moveTo.params", "commandParams", { method: "DOM.moveTo" }); export const MoveToResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.moveTo.result", "commandResult", { method: "DOM.moveTo" }); +export const MoveToCommand = withCdpCommand("DOM.moveTo", MoveToParams, MoveToResult); export const PerformSearchParams = withCdpMeta(z.object({ "query": z.string(), "includeUserAgentShadowDOM": z.boolean().optional() }).passthrough(), "DOM.performSearch.params", "commandParams", { method: "DOM.performSearch" }); export const PerformSearchResult = withCdpMeta(z.object({ "searchId": z.string(), "resultCount": z.number().int() }).passthrough(), "DOM.performSearch.result", "commandResult", { method: "DOM.performSearch" }); +export const PerformSearchCommand = withCdpCommand("DOM.performSearch", PerformSearchParams, PerformSearchResult); export const PushNodeByPathToFrontendParams = withCdpMeta(z.object({ "path": z.string() }).passthrough(), "DOM.pushNodeByPathToFrontend.params", "commandParams", { method: "DOM.pushNodeByPathToFrontend" }); export const PushNodeByPathToFrontendResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.pushNodeByPathToFrontend.result", "commandResult", { method: "DOM.pushNodeByPathToFrontend" }); +export const PushNodeByPathToFrontendCommand = withCdpCommand("DOM.pushNodeByPathToFrontend", PushNodeByPathToFrontendParams, PushNodeByPathToFrontendResult); export const PushNodesByBackendIdsToFrontendParams = withCdpMeta(z.object({ "backendNodeIds": z.array(z.lazy(() => BackendNodeId)) }).passthrough(), "DOM.pushNodesByBackendIdsToFrontend.params", "commandParams", { method: "DOM.pushNodesByBackendIdsToFrontend" }); export const PushNodesByBackendIdsToFrontendResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.pushNodesByBackendIdsToFrontend.result", "commandResult", { method: "DOM.pushNodesByBackendIdsToFrontend" }); +export const PushNodesByBackendIdsToFrontendCommand = withCdpCommand("DOM.pushNodesByBackendIdsToFrontend", PushNodesByBackendIdsToFrontendParams, PushNodesByBackendIdsToFrontendResult); export const QuerySelectorParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "selector": z.string() }).passthrough(), "DOM.querySelector.params", "commandParams", { method: "DOM.querySelector" }); export const QuerySelectorResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.querySelector.result", "commandResult", { method: "DOM.querySelector" }); +export const QuerySelectorCommand = withCdpCommand("DOM.querySelector", QuerySelectorParams, QuerySelectorResult); export const QuerySelectorAllParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "selector": z.string() }).passthrough(), "DOM.querySelectorAll.params", "commandParams", { method: "DOM.querySelectorAll" }); export const QuerySelectorAllResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.querySelectorAll.result", "commandResult", { method: "DOM.querySelectorAll" }); +export const QuerySelectorAllCommand = withCdpCommand("DOM.querySelectorAll", QuerySelectorAllParams, QuerySelectorAllResult); export const GetTopLayerElementsParams = withCdpMeta(z.object({ }).passthrough(), "DOM.getTopLayerElements.params", "commandParams", { method: "DOM.getTopLayerElements" }); export const GetTopLayerElementsResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.getTopLayerElements.result", "commandResult", { method: "DOM.getTopLayerElements" }); +export const GetTopLayerElementsCommand = withCdpCommand("DOM.getTopLayerElements", GetTopLayerElementsParams, GetTopLayerElementsResult); export const GetElementByRelationParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "relation": z.enum(["PopoverTarget", "InterestTarget", "CommandFor"]) }).passthrough(), "DOM.getElementByRelation.params", "commandParams", { method: "DOM.getElementByRelation" }); export const GetElementByRelationResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getElementByRelation.result", "commandResult", { method: "DOM.getElementByRelation" }); +export const GetElementByRelationCommand = withCdpCommand("DOM.getElementByRelation", GetElementByRelationParams, GetElementByRelationResult); export const RedoParams = withCdpMeta(z.object({ }).passthrough(), "DOM.redo.params", "commandParams", { method: "DOM.redo" }); export const RedoResult = withCdpMeta(z.object({ }).passthrough(), "DOM.redo.result", "commandResult", { method: "DOM.redo" }); +export const RedoCommand = withCdpCommand("DOM.redo", RedoParams, RedoResult); export const RemoveAttributeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "name": z.string() }).passthrough(), "DOM.removeAttribute.params", "commandParams", { method: "DOM.removeAttribute" }); export const RemoveAttributeResult = withCdpMeta(z.object({ }).passthrough(), "DOM.removeAttribute.result", "commandResult", { method: "DOM.removeAttribute" }); +export const RemoveAttributeCommand = withCdpCommand("DOM.removeAttribute", RemoveAttributeParams, RemoveAttributeResult); export const RemoveNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.removeNode.params", "commandParams", { method: "DOM.removeNode" }); export const RemoveNodeResult = withCdpMeta(z.object({ }).passthrough(), "DOM.removeNode.result", "commandResult", { method: "DOM.removeNode" }); +export const RemoveNodeCommand = withCdpCommand("DOM.removeNode", RemoveNodeParams, RemoveNodeResult); export const RequestChildNodesParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "depth": z.number().int().optional(), "pierce": z.boolean().optional() }).passthrough(), "DOM.requestChildNodes.params", "commandParams", { method: "DOM.requestChildNodes" }); export const RequestChildNodesResult = withCdpMeta(z.object({ }).passthrough(), "DOM.requestChildNodes.result", "commandResult", { method: "DOM.requestChildNodes" }); +export const RequestChildNodesCommand = withCdpCommand("DOM.requestChildNodes", RequestChildNodesParams, RequestChildNodesResult); export const RequestNodeParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId) }).passthrough(), "DOM.requestNode.params", "commandParams", { method: "DOM.requestNode" }); export const RequestNodeResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.requestNode.result", "commandResult", { method: "DOM.requestNode" }); +export const RequestNodeCommand = withCdpCommand("DOM.requestNode", RequestNodeParams, RequestNodeResult); export const ResolveNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectGroup": z.string().optional(), "executionContextId": z.lazy(() => Runtime.ExecutionContextId).optional() }).passthrough(), "DOM.resolveNode.params", "commandParams", { method: "DOM.resolveNode" }); export const ResolveNodeResult = withCdpMeta(z.object({ "object": z.lazy(() => Runtime.RemoteObject) }).passthrough(), "DOM.resolveNode.result", "commandResult", { method: "DOM.resolveNode" }); +export const ResolveNodeCommand = withCdpCommand("DOM.resolveNode", ResolveNodeParams, ResolveNodeResult); export const SetAttributeValueParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "name": z.string(), "value": z.string() }).passthrough(), "DOM.setAttributeValue.params", "commandParams", { method: "DOM.setAttributeValue" }); export const SetAttributeValueResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setAttributeValue.result", "commandResult", { method: "DOM.setAttributeValue" }); +export const SetAttributeValueCommand = withCdpCommand("DOM.setAttributeValue", SetAttributeValueParams, SetAttributeValueResult); export const SetAttributesAsTextParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "text": z.string(), "name": z.string().optional() }).passthrough(), "DOM.setAttributesAsText.params", "commandParams", { method: "DOM.setAttributesAsText" }); export const SetAttributesAsTextResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setAttributesAsText.result", "commandResult", { method: "DOM.setAttributesAsText" }); +export const SetAttributesAsTextCommand = withCdpCommand("DOM.setAttributesAsText", SetAttributesAsTextParams, SetAttributesAsTextResult); export const SetFileInputFilesParams = withCdpMeta(z.object({ "files": z.array(z.string()), "nodeId": z.lazy(() => NodeId).optional(), "backendNodeId": z.lazy(() => BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "DOM.setFileInputFiles.params", "commandParams", { method: "DOM.setFileInputFiles" }); export const SetFileInputFilesResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setFileInputFiles.result", "commandResult", { method: "DOM.setFileInputFiles" }); +export const SetFileInputFilesCommand = withCdpCommand("DOM.setFileInputFiles", SetFileInputFilesParams, SetFileInputFilesResult); export const SetNodeStackTracesEnabledParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "DOM.setNodeStackTracesEnabled.params", "commandParams", { method: "DOM.setNodeStackTracesEnabled" }); export const SetNodeStackTracesEnabledResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setNodeStackTracesEnabled.result", "commandResult", { method: "DOM.setNodeStackTracesEnabled" }); +export const SetNodeStackTracesEnabledCommand = withCdpCommand("DOM.setNodeStackTracesEnabled", SetNodeStackTracesEnabledParams, SetNodeStackTracesEnabledResult); export const GetNodeStackTracesParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getNodeStackTraces.params", "commandParams", { method: "DOM.getNodeStackTraces" }); export const GetNodeStackTracesResult = withCdpMeta(z.object({ "creation": z.lazy(() => Runtime.StackTrace).optional() }).passthrough(), "DOM.getNodeStackTraces.result", "commandResult", { method: "DOM.getNodeStackTraces" }); +export const GetNodeStackTracesCommand = withCdpCommand("DOM.getNodeStackTraces", GetNodeStackTracesParams, GetNodeStackTracesResult); export const GetFileInfoParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId) }).passthrough(), "DOM.getFileInfo.params", "commandParams", { method: "DOM.getFileInfo" }); export const GetFileInfoResult = withCdpMeta(z.object({ "path": z.string() }).passthrough(), "DOM.getFileInfo.result", "commandResult", { method: "DOM.getFileInfo" }); +export const GetFileInfoCommand = withCdpCommand("DOM.getFileInfo", GetFileInfoParams, GetFileInfoResult); export const GetDetachedDomNodesParams = withCdpMeta(z.object({ }).passthrough(), "DOM.getDetachedDomNodes.params", "commandParams", { method: "DOM.getDetachedDomNodes" }); export const GetDetachedDomNodesResult = withCdpMeta(z.object({ "detachedNodes": z.array(z.lazy(() => DetachedElementInfo)) }).passthrough(), "DOM.getDetachedDomNodes.result", "commandResult", { method: "DOM.getDetachedDomNodes" }); +export const GetDetachedDomNodesCommand = withCdpCommand("DOM.getDetachedDomNodes", GetDetachedDomNodesParams, GetDetachedDomNodesResult); export const SetInspectedNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.setInspectedNode.params", "commandParams", { method: "DOM.setInspectedNode" }); export const SetInspectedNodeResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setInspectedNode.result", "commandResult", { method: "DOM.setInspectedNode" }); +export const SetInspectedNodeCommand = withCdpCommand("DOM.setInspectedNode", SetInspectedNodeParams, SetInspectedNodeResult); export const SetNodeNameParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "name": z.string() }).passthrough(), "DOM.setNodeName.params", "commandParams", { method: "DOM.setNodeName" }); export const SetNodeNameResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.setNodeName.result", "commandResult", { method: "DOM.setNodeName" }); +export const SetNodeNameCommand = withCdpCommand("DOM.setNodeName", SetNodeNameParams, SetNodeNameResult); export const SetNodeValueParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "value": z.string() }).passthrough(), "DOM.setNodeValue.params", "commandParams", { method: "DOM.setNodeValue" }); export const SetNodeValueResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setNodeValue.result", "commandResult", { method: "DOM.setNodeValue" }); +export const SetNodeValueCommand = withCdpCommand("DOM.setNodeValue", SetNodeValueParams, SetNodeValueResult); export const SetOuterHTMLParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "outerHTML": z.string() }).passthrough(), "DOM.setOuterHTML.params", "commandParams", { method: "DOM.setOuterHTML" }); export const SetOuterHTMLResult = withCdpMeta(z.object({ }).passthrough(), "DOM.setOuterHTML.result", "commandResult", { method: "DOM.setOuterHTML" }); +export const SetOuterHTMLCommand = withCdpCommand("DOM.setOuterHTML", SetOuterHTMLParams, SetOuterHTMLResult); export const UndoParams = withCdpMeta(z.object({ }).passthrough(), "DOM.undo.params", "commandParams", { method: "DOM.undo" }); export const UndoResult = withCdpMeta(z.object({ }).passthrough(), "DOM.undo.result", "commandResult", { method: "DOM.undo" }); +export const UndoCommand = withCdpCommand("DOM.undo", UndoParams, UndoResult); export const GetFrameOwnerParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId) }).passthrough(), "DOM.getFrameOwner.params", "commandParams", { method: "DOM.getFrameOwner" }); export const GetFrameOwnerResult = withCdpMeta(z.object({ "backendNodeId": z.lazy(() => BackendNodeId), "nodeId": z.lazy(() => NodeId).optional() }).passthrough(), "DOM.getFrameOwner.result", "commandResult", { method: "DOM.getFrameOwner" }); +export const GetFrameOwnerCommand = withCdpCommand("DOM.getFrameOwner", GetFrameOwnerParams, GetFrameOwnerResult); export const GetContainerForNodeParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "containerName": z.string().optional(), "physicalAxes": z.lazy(() => PhysicalAxes).optional(), "logicalAxes": z.lazy(() => LogicalAxes).optional(), "queriesScrollState": z.boolean().optional(), "queriesAnchored": z.boolean().optional() }).passthrough(), "DOM.getContainerForNode.params", "commandParams", { method: "DOM.getContainerForNode" }); export const GetContainerForNodeResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId).optional() }).passthrough(), "DOM.getContainerForNode.result", "commandResult", { method: "DOM.getContainerForNode" }); +export const GetContainerForNodeCommand = withCdpCommand("DOM.getContainerForNode", GetContainerForNodeParams, GetContainerForNodeResult); export const GetQueryingDescendantsForContainerParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getQueryingDescendantsForContainer.params", "commandParams", { method: "DOM.getQueryingDescendantsForContainer" }); export const GetQueryingDescendantsForContainerResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.getQueryingDescendantsForContainer.result", "commandResult", { method: "DOM.getQueryingDescendantsForContainer" }); +export const GetQueryingDescendantsForContainerCommand = withCdpCommand("DOM.getQueryingDescendantsForContainer", GetQueryingDescendantsForContainerParams, GetQueryingDescendantsForContainerResult); export const GetAnchorElementParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "anchorSpecifier": z.string().optional() }).passthrough(), "DOM.getAnchorElement.params", "commandParams", { method: "DOM.getAnchorElement" }); export const GetAnchorElementResult = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId) }).passthrough(), "DOM.getAnchorElement.result", "commandResult", { method: "DOM.getAnchorElement" }); +export const GetAnchorElementCommand = withCdpCommand("DOM.getAnchorElement", GetAnchorElementParams, GetAnchorElementResult); export const ForceShowPopoverParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "enable": z.boolean() }).passthrough(), "DOM.forceShowPopover.params", "commandParams", { method: "DOM.forceShowPopover" }); export const ForceShowPopoverResult = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => NodeId)) }).passthrough(), "DOM.forceShowPopover.result", "commandResult", { method: "DOM.forceShowPopover" }); +export const ForceShowPopoverCommand = withCdpCommand("DOM.forceShowPopover", ForceShowPopoverParams, ForceShowPopoverResult); export const AttributeModifiedEvent = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "name": z.string(), "value": z.string() }).passthrough(), "DOM.attributeModified", "event", { phase: "event" }); export const AdoptedStyleSheetsModifiedEvent = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "adoptedStyleSheets": z.array(z.lazy(() => StyleSheetId)) }).passthrough(), "DOM.adoptedStyleSheetsModified", "event", { phase: "event" }); export const AttributeRemovedEvent = withCdpMeta(z.object({ "nodeId": z.lazy(() => NodeId), "name": z.string() }).passthrough(), "DOM.attributeRemoved", "event", { phase: "event" }); @@ -296,59 +349,59 @@ export const zod = { ShadowRootPushedEvent: ShadowRootPushedEvent, } as const; export const commands = { - "DOM.collectClassNamesFromSubtree": { params: CollectClassNamesFromSubtreeParams, result: CollectClassNamesFromSubtreeResult }, - "DOM.copyTo": { params: CopyToParams, result: CopyToResult }, - "DOM.describeNode": { params: DescribeNodeParams, result: DescribeNodeResult }, - "DOM.scrollIntoViewIfNeeded": { params: ScrollIntoViewIfNeededParams, result: ScrollIntoViewIfNeededResult }, - "DOM.disable": { params: DisableParams, result: DisableResult }, - "DOM.discardSearchResults": { params: DiscardSearchResultsParams, result: DiscardSearchResultsResult }, - "DOM.enable": { params: EnableParams, result: EnableResult }, - "DOM.focus": { params: FocusParams, result: FocusResult }, - "DOM.getAttributes": { params: GetAttributesParams, result: GetAttributesResult }, - "DOM.getBoxModel": { params: GetBoxModelParams, result: GetBoxModelResult }, - "DOM.getContentQuads": { params: GetContentQuadsParams, result: GetContentQuadsResult }, - "DOM.getDocument": { params: GetDocumentParams, result: GetDocumentResult }, - "DOM.getFlattenedDocument": { params: GetFlattenedDocumentParams, result: GetFlattenedDocumentResult }, - "DOM.getNodesForSubtreeByStyle": { params: GetNodesForSubtreeByStyleParams, result: GetNodesForSubtreeByStyleResult }, - "DOM.getNodeForLocation": { params: GetNodeForLocationParams, result: GetNodeForLocationResult }, - "DOM.getOuterHTML": { params: GetOuterHTMLParams, result: GetOuterHTMLResult }, - "DOM.getRelayoutBoundary": { params: GetRelayoutBoundaryParams, result: GetRelayoutBoundaryResult }, - "DOM.getSearchResults": { params: GetSearchResultsParams, result: GetSearchResultsResult }, - "DOM.hideHighlight": { params: HideHighlightParams, result: HideHighlightResult }, - "DOM.highlightNode": { params: HighlightNodeParams, result: HighlightNodeResult }, - "DOM.highlightRect": { params: HighlightRectParams, result: HighlightRectResult }, - "DOM.markUndoableState": { params: MarkUndoableStateParams, result: MarkUndoableStateResult }, - "DOM.moveTo": { params: MoveToParams, result: MoveToResult }, - "DOM.performSearch": { params: PerformSearchParams, result: PerformSearchResult }, - "DOM.pushNodeByPathToFrontend": { params: PushNodeByPathToFrontendParams, result: PushNodeByPathToFrontendResult }, - "DOM.pushNodesByBackendIdsToFrontend": { params: PushNodesByBackendIdsToFrontendParams, result: PushNodesByBackendIdsToFrontendResult }, - "DOM.querySelector": { params: QuerySelectorParams, result: QuerySelectorResult }, - "DOM.querySelectorAll": { params: QuerySelectorAllParams, result: QuerySelectorAllResult }, - "DOM.getTopLayerElements": { params: GetTopLayerElementsParams, result: GetTopLayerElementsResult }, - "DOM.getElementByRelation": { params: GetElementByRelationParams, result: GetElementByRelationResult }, - "DOM.redo": { params: RedoParams, result: RedoResult }, - "DOM.removeAttribute": { params: RemoveAttributeParams, result: RemoveAttributeResult }, - "DOM.removeNode": { params: RemoveNodeParams, result: RemoveNodeResult }, - "DOM.requestChildNodes": { params: RequestChildNodesParams, result: RequestChildNodesResult }, - "DOM.requestNode": { params: RequestNodeParams, result: RequestNodeResult }, - "DOM.resolveNode": { params: ResolveNodeParams, result: ResolveNodeResult }, - "DOM.setAttributeValue": { params: SetAttributeValueParams, result: SetAttributeValueResult }, - "DOM.setAttributesAsText": { params: SetAttributesAsTextParams, result: SetAttributesAsTextResult }, - "DOM.setFileInputFiles": { params: SetFileInputFilesParams, result: SetFileInputFilesResult }, - "DOM.setNodeStackTracesEnabled": { params: SetNodeStackTracesEnabledParams, result: SetNodeStackTracesEnabledResult }, - "DOM.getNodeStackTraces": { params: GetNodeStackTracesParams, result: GetNodeStackTracesResult }, - "DOM.getFileInfo": { params: GetFileInfoParams, result: GetFileInfoResult }, - "DOM.getDetachedDomNodes": { params: GetDetachedDomNodesParams, result: GetDetachedDomNodesResult }, - "DOM.setInspectedNode": { params: SetInspectedNodeParams, result: SetInspectedNodeResult }, - "DOM.setNodeName": { params: SetNodeNameParams, result: SetNodeNameResult }, - "DOM.setNodeValue": { params: SetNodeValueParams, result: SetNodeValueResult }, - "DOM.setOuterHTML": { params: SetOuterHTMLParams, result: SetOuterHTMLResult }, - "DOM.undo": { params: UndoParams, result: UndoResult }, - "DOM.getFrameOwner": { params: GetFrameOwnerParams, result: GetFrameOwnerResult }, - "DOM.getContainerForNode": { params: GetContainerForNodeParams, result: GetContainerForNodeResult }, - "DOM.getQueryingDescendantsForContainer": { params: GetQueryingDescendantsForContainerParams, result: GetQueryingDescendantsForContainerResult }, - "DOM.getAnchorElement": { params: GetAnchorElementParams, result: GetAnchorElementResult }, - "DOM.forceShowPopover": { params: ForceShowPopoverParams, result: ForceShowPopoverResult }, + "DOM.collectClassNamesFromSubtree": CollectClassNamesFromSubtreeCommand, + "DOM.copyTo": CopyToCommand, + "DOM.describeNode": DescribeNodeCommand, + "DOM.scrollIntoViewIfNeeded": ScrollIntoViewIfNeededCommand, + "DOM.disable": DisableCommand, + "DOM.discardSearchResults": DiscardSearchResultsCommand, + "DOM.enable": EnableCommand, + "DOM.focus": FocusCommand, + "DOM.getAttributes": GetAttributesCommand, + "DOM.getBoxModel": GetBoxModelCommand, + "DOM.getContentQuads": GetContentQuadsCommand, + "DOM.getDocument": GetDocumentCommand, + "DOM.getFlattenedDocument": GetFlattenedDocumentCommand, + "DOM.getNodesForSubtreeByStyle": GetNodesForSubtreeByStyleCommand, + "DOM.getNodeForLocation": GetNodeForLocationCommand, + "DOM.getOuterHTML": GetOuterHTMLCommand, + "DOM.getRelayoutBoundary": GetRelayoutBoundaryCommand, + "DOM.getSearchResults": GetSearchResultsCommand, + "DOM.hideHighlight": HideHighlightCommand, + "DOM.highlightNode": HighlightNodeCommand, + "DOM.highlightRect": HighlightRectCommand, + "DOM.markUndoableState": MarkUndoableStateCommand, + "DOM.moveTo": MoveToCommand, + "DOM.performSearch": PerformSearchCommand, + "DOM.pushNodeByPathToFrontend": PushNodeByPathToFrontendCommand, + "DOM.pushNodesByBackendIdsToFrontend": PushNodesByBackendIdsToFrontendCommand, + "DOM.querySelector": QuerySelectorCommand, + "DOM.querySelectorAll": QuerySelectorAllCommand, + "DOM.getTopLayerElements": GetTopLayerElementsCommand, + "DOM.getElementByRelation": GetElementByRelationCommand, + "DOM.redo": RedoCommand, + "DOM.removeAttribute": RemoveAttributeCommand, + "DOM.removeNode": RemoveNodeCommand, + "DOM.requestChildNodes": RequestChildNodesCommand, + "DOM.requestNode": RequestNodeCommand, + "DOM.resolveNode": ResolveNodeCommand, + "DOM.setAttributeValue": SetAttributeValueCommand, + "DOM.setAttributesAsText": SetAttributesAsTextCommand, + "DOM.setFileInputFiles": SetFileInputFilesCommand, + "DOM.setNodeStackTracesEnabled": SetNodeStackTracesEnabledCommand, + "DOM.getNodeStackTraces": GetNodeStackTracesCommand, + "DOM.getFileInfo": GetFileInfoCommand, + "DOM.getDetachedDomNodes": GetDetachedDomNodesCommand, + "DOM.setInspectedNode": SetInspectedNodeCommand, + "DOM.setNodeName": SetNodeNameCommand, + "DOM.setNodeValue": SetNodeValueCommand, + "DOM.setOuterHTML": SetOuterHTMLCommand, + "DOM.undo": UndoCommand, + "DOM.getFrameOwner": GetFrameOwnerCommand, + "DOM.getContainerForNode": GetContainerForNodeCommand, + "DOM.getQueryingDescendantsForContainer": GetQueryingDescendantsForContainerCommand, + "DOM.getAnchorElement": GetAnchorElementCommand, + "DOM.forceShowPopover": ForceShowPopoverCommand, } as const; export const events = { "DOM.attributeModified": AttributeModifiedEvent, diff --git a/js/src/types/generated/zod/DOMDebugger.ts b/js/src/types/generated/zod/DOMDebugger.ts index b3073866..617961bc 100644 --- a/js/src/types/generated/zod/DOMDebugger.ts +++ b/js/src/types/generated/zod/DOMDebugger.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Runtime from "./Runtime.js"; @@ -10,24 +10,34 @@ export const CSPViolationType = withCdpMeta(z.enum(["trustedtype-sink-violation" export const EventListener = withCdpMeta(z.object({ "type": z.string(), "useCapture": z.boolean(), "passive": z.boolean(), "once": z.boolean(), "scriptId": z.lazy(() => Runtime.ScriptId), "lineNumber": z.number().int(), "columnNumber": z.number().int(), "handler": z.lazy(() => Runtime.RemoteObject).optional(), "originalHandler": z.lazy(() => Runtime.RemoteObject).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional() }).passthrough(), "DOMDebugger.EventListener", "type"); export const GetEventListenersParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId), "depth": z.number().int().optional(), "pierce": z.boolean().optional() }).passthrough(), "DOMDebugger.getEventListeners.params", "commandParams", { method: "DOMDebugger.getEventListeners" }); export const GetEventListenersResult = withCdpMeta(z.object({ "listeners": z.array(z.lazy(() => EventListener)) }).passthrough(), "DOMDebugger.getEventListeners.result", "commandResult", { method: "DOMDebugger.getEventListeners" }); +export const GetEventListenersCommand = withCdpCommand("DOMDebugger.getEventListeners", GetEventListenersParams, GetEventListenersResult); export const RemoveDOMBreakpointParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "type": z.lazy(() => DOMBreakpointType) }).passthrough(), "DOMDebugger.removeDOMBreakpoint.params", "commandParams", { method: "DOMDebugger.removeDOMBreakpoint" }); export const RemoveDOMBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.removeDOMBreakpoint.result", "commandResult", { method: "DOMDebugger.removeDOMBreakpoint" }); +export const RemoveDOMBreakpointCommand = withCdpCommand("DOMDebugger.removeDOMBreakpoint", RemoveDOMBreakpointParams, RemoveDOMBreakpointResult); export const RemoveEventListenerBreakpointParams = withCdpMeta(z.object({ "eventName": z.string(), "targetName": z.string().optional() }).passthrough(), "DOMDebugger.removeEventListenerBreakpoint.params", "commandParams", { method: "DOMDebugger.removeEventListenerBreakpoint" }); export const RemoveEventListenerBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.removeEventListenerBreakpoint.result", "commandResult", { method: "DOMDebugger.removeEventListenerBreakpoint" }); +export const RemoveEventListenerBreakpointCommand = withCdpCommand("DOMDebugger.removeEventListenerBreakpoint", RemoveEventListenerBreakpointParams, RemoveEventListenerBreakpointResult); export const RemoveInstrumentationBreakpointParams = withCdpMeta(z.object({ "eventName": z.string() }).passthrough(), "DOMDebugger.removeInstrumentationBreakpoint.params", "commandParams", { method: "DOMDebugger.removeInstrumentationBreakpoint" }); export const RemoveInstrumentationBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.removeInstrumentationBreakpoint.result", "commandResult", { method: "DOMDebugger.removeInstrumentationBreakpoint" }); +export const RemoveInstrumentationBreakpointCommand = withCdpCommand("DOMDebugger.removeInstrumentationBreakpoint", RemoveInstrumentationBreakpointParams, RemoveInstrumentationBreakpointResult); export const RemoveXHRBreakpointParams = withCdpMeta(z.object({ "url": z.string() }).passthrough(), "DOMDebugger.removeXHRBreakpoint.params", "commandParams", { method: "DOMDebugger.removeXHRBreakpoint" }); export const RemoveXHRBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.removeXHRBreakpoint.result", "commandResult", { method: "DOMDebugger.removeXHRBreakpoint" }); +export const RemoveXHRBreakpointCommand = withCdpCommand("DOMDebugger.removeXHRBreakpoint", RemoveXHRBreakpointParams, RemoveXHRBreakpointResult); export const SetBreakOnCSPViolationParams = withCdpMeta(z.object({ "violationTypes": z.array(z.lazy(() => CSPViolationType)) }).passthrough(), "DOMDebugger.setBreakOnCSPViolation.params", "commandParams", { method: "DOMDebugger.setBreakOnCSPViolation" }); export const SetBreakOnCSPViolationResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.setBreakOnCSPViolation.result", "commandResult", { method: "DOMDebugger.setBreakOnCSPViolation" }); +export const SetBreakOnCSPViolationCommand = withCdpCommand("DOMDebugger.setBreakOnCSPViolation", SetBreakOnCSPViolationParams, SetBreakOnCSPViolationResult); export const SetDOMBreakpointParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "type": z.lazy(() => DOMBreakpointType) }).passthrough(), "DOMDebugger.setDOMBreakpoint.params", "commandParams", { method: "DOMDebugger.setDOMBreakpoint" }); export const SetDOMBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.setDOMBreakpoint.result", "commandResult", { method: "DOMDebugger.setDOMBreakpoint" }); +export const SetDOMBreakpointCommand = withCdpCommand("DOMDebugger.setDOMBreakpoint", SetDOMBreakpointParams, SetDOMBreakpointResult); export const SetEventListenerBreakpointParams = withCdpMeta(z.object({ "eventName": z.string(), "targetName": z.string().optional() }).passthrough(), "DOMDebugger.setEventListenerBreakpoint.params", "commandParams", { method: "DOMDebugger.setEventListenerBreakpoint" }); export const SetEventListenerBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.setEventListenerBreakpoint.result", "commandResult", { method: "DOMDebugger.setEventListenerBreakpoint" }); +export const SetEventListenerBreakpointCommand = withCdpCommand("DOMDebugger.setEventListenerBreakpoint", SetEventListenerBreakpointParams, SetEventListenerBreakpointResult); export const SetInstrumentationBreakpointParams = withCdpMeta(z.object({ "eventName": z.string() }).passthrough(), "DOMDebugger.setInstrumentationBreakpoint.params", "commandParams", { method: "DOMDebugger.setInstrumentationBreakpoint" }); export const SetInstrumentationBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.setInstrumentationBreakpoint.result", "commandResult", { method: "DOMDebugger.setInstrumentationBreakpoint" }); +export const SetInstrumentationBreakpointCommand = withCdpCommand("DOMDebugger.setInstrumentationBreakpoint", SetInstrumentationBreakpointParams, SetInstrumentationBreakpointResult); export const SetXHRBreakpointParams = withCdpMeta(z.object({ "url": z.string() }).passthrough(), "DOMDebugger.setXHRBreakpoint.params", "commandParams", { method: "DOMDebugger.setXHRBreakpoint" }); export const SetXHRBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "DOMDebugger.setXHRBreakpoint.result", "commandResult", { method: "DOMDebugger.setXHRBreakpoint" }); +export const SetXHRBreakpointCommand = withCdpCommand("DOMDebugger.setXHRBreakpoint", SetXHRBreakpointParams, SetXHRBreakpointResult); export const zod = { DOMBreakpointType: DOMBreakpointType, @@ -55,16 +65,16 @@ export const zod = { SetXHRBreakpointResult: SetXHRBreakpointResult, } as const; export const commands = { - "DOMDebugger.getEventListeners": { params: GetEventListenersParams, result: GetEventListenersResult }, - "DOMDebugger.removeDOMBreakpoint": { params: RemoveDOMBreakpointParams, result: RemoveDOMBreakpointResult }, - "DOMDebugger.removeEventListenerBreakpoint": { params: RemoveEventListenerBreakpointParams, result: RemoveEventListenerBreakpointResult }, - "DOMDebugger.removeInstrumentationBreakpoint": { params: RemoveInstrumentationBreakpointParams, result: RemoveInstrumentationBreakpointResult }, - "DOMDebugger.removeXHRBreakpoint": { params: RemoveXHRBreakpointParams, result: RemoveXHRBreakpointResult }, - "DOMDebugger.setBreakOnCSPViolation": { params: SetBreakOnCSPViolationParams, result: SetBreakOnCSPViolationResult }, - "DOMDebugger.setDOMBreakpoint": { params: SetDOMBreakpointParams, result: SetDOMBreakpointResult }, - "DOMDebugger.setEventListenerBreakpoint": { params: SetEventListenerBreakpointParams, result: SetEventListenerBreakpointResult }, - "DOMDebugger.setInstrumentationBreakpoint": { params: SetInstrumentationBreakpointParams, result: SetInstrumentationBreakpointResult }, - "DOMDebugger.setXHRBreakpoint": { params: SetXHRBreakpointParams, result: SetXHRBreakpointResult }, + "DOMDebugger.getEventListeners": GetEventListenersCommand, + "DOMDebugger.removeDOMBreakpoint": RemoveDOMBreakpointCommand, + "DOMDebugger.removeEventListenerBreakpoint": RemoveEventListenerBreakpointCommand, + "DOMDebugger.removeInstrumentationBreakpoint": RemoveInstrumentationBreakpointCommand, + "DOMDebugger.removeXHRBreakpoint": RemoveXHRBreakpointCommand, + "DOMDebugger.setBreakOnCSPViolation": SetBreakOnCSPViolationCommand, + "DOMDebugger.setDOMBreakpoint": SetDOMBreakpointCommand, + "DOMDebugger.setEventListenerBreakpoint": SetEventListenerBreakpointCommand, + "DOMDebugger.setInstrumentationBreakpoint": SetInstrumentationBreakpointCommand, + "DOMDebugger.setXHRBreakpoint": SetXHRBreakpointCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/DOMSnapshot.ts b/js/src/types/generated/zod/DOMSnapshot.ts index 1b855c29..636a9578 100644 --- a/js/src/types/generated/zod/DOMSnapshot.ts +++ b/js/src/types/generated/zod/DOMSnapshot.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as DOMDebugger from "./DOMDebugger.js"; import * as Page from "./Page.js"; @@ -23,12 +23,16 @@ export const LayoutTreeSnapshot = withCdpMeta(z.object({ "nodeIndex": z.array(z. export const TextBoxSnapshot = withCdpMeta(z.object({ "layoutIndex": z.array(z.number().int()), "bounds": z.array(z.lazy(() => Rectangle)), "start": z.array(z.number().int()), "length": z.array(z.number().int()) }).passthrough(), "DOMSnapshot.TextBoxSnapshot", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "DOMSnapshot.disable.params", "commandParams", { method: "DOMSnapshot.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "DOMSnapshot.disable.result", "commandResult", { method: "DOMSnapshot.disable" }); +export const DisableCommand = withCdpCommand("DOMSnapshot.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "DOMSnapshot.enable.params", "commandParams", { method: "DOMSnapshot.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "DOMSnapshot.enable.result", "commandResult", { method: "DOMSnapshot.enable" }); +export const EnableCommand = withCdpCommand("DOMSnapshot.enable", EnableParams, EnableResult); export const GetSnapshotParams = withCdpMeta(z.object({ "computedStyleWhitelist": z.array(z.string()), "includeEventListeners": z.boolean().optional(), "includePaintOrder": z.boolean().optional(), "includeUserAgentShadowTree": z.boolean().optional() }).passthrough(), "DOMSnapshot.getSnapshot.params", "commandParams", { method: "DOMSnapshot.getSnapshot" }); export const GetSnapshotResult = withCdpMeta(z.object({ "domNodes": z.array(z.lazy(() => DOMNode)), "layoutTreeNodes": z.array(z.lazy(() => LayoutTreeNode)), "computedStyles": z.array(z.lazy(() => ComputedStyle)) }).passthrough(), "DOMSnapshot.getSnapshot.result", "commandResult", { method: "DOMSnapshot.getSnapshot" }); +export const GetSnapshotCommand = withCdpCommand("DOMSnapshot.getSnapshot", GetSnapshotParams, GetSnapshotResult); export const CaptureSnapshotParams = withCdpMeta(z.object({ "computedStyles": z.array(z.string()), "includePaintOrder": z.boolean().optional(), "includeDOMRects": z.boolean().optional(), "includeBlendedBackgroundColors": z.boolean().optional(), "includeTextColorOpacities": z.boolean().optional() }).passthrough(), "DOMSnapshot.captureSnapshot.params", "commandParams", { method: "DOMSnapshot.captureSnapshot" }); export const CaptureSnapshotResult = withCdpMeta(z.object({ "documents": z.array(z.lazy(() => DocumentSnapshot)), "strings": z.array(z.string()) }).passthrough(), "DOMSnapshot.captureSnapshot.result", "commandResult", { method: "DOMSnapshot.captureSnapshot" }); +export const CaptureSnapshotCommand = withCdpCommand("DOMSnapshot.captureSnapshot", CaptureSnapshotParams, CaptureSnapshotResult); export const zod = { DOMNode: DOMNode, @@ -56,10 +60,10 @@ export const zod = { CaptureSnapshotResult: CaptureSnapshotResult, } as const; export const commands = { - "DOMSnapshot.disable": { params: DisableParams, result: DisableResult }, - "DOMSnapshot.enable": { params: EnableParams, result: EnableResult }, - "DOMSnapshot.getSnapshot": { params: GetSnapshotParams, result: GetSnapshotResult }, - "DOMSnapshot.captureSnapshot": { params: CaptureSnapshotParams, result: CaptureSnapshotResult }, + "DOMSnapshot.disable": DisableCommand, + "DOMSnapshot.enable": EnableCommand, + "DOMSnapshot.getSnapshot": GetSnapshotCommand, + "DOMSnapshot.captureSnapshot": CaptureSnapshotCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/DOMStorage.ts b/js/src/types/generated/zod/DOMStorage.ts index 43c59094..5f999cbc 100644 --- a/js/src/types/generated/zod/DOMStorage.ts +++ b/js/src/types/generated/zod/DOMStorage.ts @@ -1,23 +1,29 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const SerializedStorageKey = withCdpMeta(z.string(), "DOMStorage.SerializedStorageKey", "type"); export const StorageId = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.lazy(() => SerializedStorageKey).optional(), "isLocalStorage": z.boolean() }).passthrough(), "DOMStorage.StorageId", "type"); export const Item = withCdpMeta(z.array(z.string()), "DOMStorage.Item", "type"); export const ClearParams = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId) }).passthrough(), "DOMStorage.clear.params", "commandParams", { method: "DOMStorage.clear" }); export const ClearResult = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.clear.result", "commandResult", { method: "DOMStorage.clear" }); +export const ClearCommand = withCdpCommand("DOMStorage.clear", ClearParams, ClearResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.disable.params", "commandParams", { method: "DOMStorage.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.disable.result", "commandResult", { method: "DOMStorage.disable" }); +export const DisableCommand = withCdpCommand("DOMStorage.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.enable.params", "commandParams", { method: "DOMStorage.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.enable.result", "commandResult", { method: "DOMStorage.enable" }); +export const EnableCommand = withCdpCommand("DOMStorage.enable", EnableParams, EnableResult); export const GetDOMStorageItemsParams = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId) }).passthrough(), "DOMStorage.getDOMStorageItems.params", "commandParams", { method: "DOMStorage.getDOMStorageItems" }); export const GetDOMStorageItemsResult = withCdpMeta(z.object({ "entries": z.array(z.lazy(() => Item)) }).passthrough(), "DOMStorage.getDOMStorageItems.result", "commandResult", { method: "DOMStorage.getDOMStorageItems" }); +export const GetDOMStorageItemsCommand = withCdpCommand("DOMStorage.getDOMStorageItems", GetDOMStorageItemsParams, GetDOMStorageItemsResult); export const RemoveDOMStorageItemParams = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId), "key": z.string() }).passthrough(), "DOMStorage.removeDOMStorageItem.params", "commandParams", { method: "DOMStorage.removeDOMStorageItem" }); export const RemoveDOMStorageItemResult = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.removeDOMStorageItem.result", "commandResult", { method: "DOMStorage.removeDOMStorageItem" }); +export const RemoveDOMStorageItemCommand = withCdpCommand("DOMStorage.removeDOMStorageItem", RemoveDOMStorageItemParams, RemoveDOMStorageItemResult); export const SetDOMStorageItemParams = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId), "key": z.string(), "value": z.string() }).passthrough(), "DOMStorage.setDOMStorageItem.params", "commandParams", { method: "DOMStorage.setDOMStorageItem" }); export const SetDOMStorageItemResult = withCdpMeta(z.object({ }).passthrough(), "DOMStorage.setDOMStorageItem.result", "commandResult", { method: "DOMStorage.setDOMStorageItem" }); +export const SetDOMStorageItemCommand = withCdpCommand("DOMStorage.setDOMStorageItem", SetDOMStorageItemParams, SetDOMStorageItemResult); export const DomStorageItemAddedEvent = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId), "key": z.string(), "newValue": z.string() }).passthrough(), "DOMStorage.domStorageItemAdded", "event", { phase: "event" }); export const DomStorageItemRemovedEvent = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId), "key": z.string() }).passthrough(), "DOMStorage.domStorageItemRemoved", "event", { phase: "event" }); export const DomStorageItemUpdatedEvent = withCdpMeta(z.object({ "storageId": z.lazy(() => StorageId), "key": z.string(), "oldValue": z.string(), "newValue": z.string() }).passthrough(), "DOMStorage.domStorageItemUpdated", "event", { phase: "event" }); @@ -45,12 +51,12 @@ export const zod = { DomStorageItemsClearedEvent: DomStorageItemsClearedEvent, } as const; export const commands = { - "DOMStorage.clear": { params: ClearParams, result: ClearResult }, - "DOMStorage.disable": { params: DisableParams, result: DisableResult }, - "DOMStorage.enable": { params: EnableParams, result: EnableResult }, - "DOMStorage.getDOMStorageItems": { params: GetDOMStorageItemsParams, result: GetDOMStorageItemsResult }, - "DOMStorage.removeDOMStorageItem": { params: RemoveDOMStorageItemParams, result: RemoveDOMStorageItemResult }, - "DOMStorage.setDOMStorageItem": { params: SetDOMStorageItemParams, result: SetDOMStorageItemResult }, + "DOMStorage.clear": ClearCommand, + "DOMStorage.disable": DisableCommand, + "DOMStorage.enable": EnableCommand, + "DOMStorage.getDOMStorageItems": GetDOMStorageItemsCommand, + "DOMStorage.removeDOMStorageItem": RemoveDOMStorageItemCommand, + "DOMStorage.setDOMStorageItem": SetDOMStorageItemCommand, } as const; export const events = { "DOMStorage.domStorageItemAdded": DomStorageItemAddedEvent, diff --git a/js/src/types/generated/zod/Debugger.ts b/js/src/types/generated/zod/Debugger.ts index 0dd8c5f1..7bfa306e 100644 --- a/js/src/types/generated/zod/Debugger.ts +++ b/js/src/types/generated/zod/Debugger.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Runtime from "./Runtime.js"; export const BreakpointId = withCdpMeta(z.string(), "Debugger.BreakpointId", "type"); @@ -19,70 +19,103 @@ export const DebugSymbols = withCdpMeta(z.object({ "type": z.enum(["SourceMap", export const ResolvedBreakpoint = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId), "location": z.lazy(() => Location) }).passthrough(), "Debugger.ResolvedBreakpoint", "type"); export const ContinueToLocationParams = withCdpMeta(z.object({ "location": z.lazy(() => Location), "targetCallFrames": z.enum(["any", "current"]).optional() }).passthrough(), "Debugger.continueToLocation.params", "commandParams", { method: "Debugger.continueToLocation" }); export const ContinueToLocationResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.continueToLocation.result", "commandResult", { method: "Debugger.continueToLocation" }); +export const ContinueToLocationCommand = withCdpCommand("Debugger.continueToLocation", ContinueToLocationParams, ContinueToLocationResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Debugger.disable.params", "commandParams", { method: "Debugger.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.disable.result", "commandResult", { method: "Debugger.disable" }); +export const DisableCommand = withCdpCommand("Debugger.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ "maxScriptsCacheSize": z.number().optional() }).passthrough(), "Debugger.enable.params", "commandParams", { method: "Debugger.enable" }); export const EnableResult = withCdpMeta(z.object({ "debuggerId": z.lazy(() => Runtime.UniqueDebuggerId) }).passthrough(), "Debugger.enable.result", "commandResult", { method: "Debugger.enable" }); +export const EnableCommand = withCdpCommand("Debugger.enable", EnableParams, EnableResult); export const EvaluateOnCallFrameParams = withCdpMeta(z.object({ "callFrameId": z.lazy(() => CallFrameId), "expression": z.string(), "objectGroup": z.string().optional(), "includeCommandLineAPI": z.boolean().optional(), "silent": z.boolean().optional(), "returnByValue": z.boolean().optional(), "generatePreview": z.boolean().optional(), "throwOnSideEffect": z.boolean().optional(), "timeout": z.lazy(() => Runtime.TimeDelta).optional() }).passthrough(), "Debugger.evaluateOnCallFrame.params", "commandParams", { method: "Debugger.evaluateOnCallFrame" }); export const EvaluateOnCallFrameResult = withCdpMeta(z.object({ "result": z.lazy(() => Runtime.RemoteObject), "exceptionDetails": z.lazy(() => Runtime.ExceptionDetails).optional() }).passthrough(), "Debugger.evaluateOnCallFrame.result", "commandResult", { method: "Debugger.evaluateOnCallFrame" }); +export const EvaluateOnCallFrameCommand = withCdpCommand("Debugger.evaluateOnCallFrame", EvaluateOnCallFrameParams, EvaluateOnCallFrameResult); export const GetPossibleBreakpointsParams = withCdpMeta(z.object({ "start": z.lazy(() => Location), "end": z.lazy(() => Location).optional(), "restrictToFunction": z.boolean().optional() }).passthrough(), "Debugger.getPossibleBreakpoints.params", "commandParams", { method: "Debugger.getPossibleBreakpoints" }); export const GetPossibleBreakpointsResult = withCdpMeta(z.object({ "locations": z.array(z.lazy(() => BreakLocation)) }).passthrough(), "Debugger.getPossibleBreakpoints.result", "commandResult", { method: "Debugger.getPossibleBreakpoints" }); +export const GetPossibleBreakpointsCommand = withCdpCommand("Debugger.getPossibleBreakpoints", GetPossibleBreakpointsParams, GetPossibleBreakpointsResult); export const GetScriptSourceParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId) }).passthrough(), "Debugger.getScriptSource.params", "commandParams", { method: "Debugger.getScriptSource" }); export const GetScriptSourceResult = withCdpMeta(z.object({ "scriptSource": z.string(), "bytecode": z.string().optional() }).passthrough(), "Debugger.getScriptSource.result", "commandResult", { method: "Debugger.getScriptSource" }); +export const GetScriptSourceCommand = withCdpCommand("Debugger.getScriptSource", GetScriptSourceParams, GetScriptSourceResult); export const DisassembleWasmModuleParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId) }).passthrough(), "Debugger.disassembleWasmModule.params", "commandParams", { method: "Debugger.disassembleWasmModule" }); export const DisassembleWasmModuleResult = withCdpMeta(z.object({ "streamId": z.string().optional(), "totalNumberOfLines": z.number().int(), "functionBodyOffsets": z.array(z.number().int()), "chunk": z.lazy(() => WasmDisassemblyChunk) }).passthrough(), "Debugger.disassembleWasmModule.result", "commandResult", { method: "Debugger.disassembleWasmModule" }); +export const DisassembleWasmModuleCommand = withCdpCommand("Debugger.disassembleWasmModule", DisassembleWasmModuleParams, DisassembleWasmModuleResult); export const NextWasmDisassemblyChunkParams = withCdpMeta(z.object({ "streamId": z.string() }).passthrough(), "Debugger.nextWasmDisassemblyChunk.params", "commandParams", { method: "Debugger.nextWasmDisassemblyChunk" }); export const NextWasmDisassemblyChunkResult = withCdpMeta(z.object({ "chunk": z.lazy(() => WasmDisassemblyChunk) }).passthrough(), "Debugger.nextWasmDisassemblyChunk.result", "commandResult", { method: "Debugger.nextWasmDisassemblyChunk" }); +export const NextWasmDisassemblyChunkCommand = withCdpCommand("Debugger.nextWasmDisassemblyChunk", NextWasmDisassemblyChunkParams, NextWasmDisassemblyChunkResult); export const GetWasmBytecodeParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId) }).passthrough(), "Debugger.getWasmBytecode.params", "commandParams", { method: "Debugger.getWasmBytecode" }); export const GetWasmBytecodeResult = withCdpMeta(z.object({ "bytecode": z.string() }).passthrough(), "Debugger.getWasmBytecode.result", "commandResult", { method: "Debugger.getWasmBytecode" }); +export const GetWasmBytecodeCommand = withCdpCommand("Debugger.getWasmBytecode", GetWasmBytecodeParams, GetWasmBytecodeResult); export const GetStackTraceParams = withCdpMeta(z.object({ "stackTraceId": z.lazy(() => Runtime.StackTraceId) }).passthrough(), "Debugger.getStackTrace.params", "commandParams", { method: "Debugger.getStackTrace" }); export const GetStackTraceResult = withCdpMeta(z.object({ "stackTrace": z.lazy(() => Runtime.StackTrace) }).passthrough(), "Debugger.getStackTrace.result", "commandResult", { method: "Debugger.getStackTrace" }); +export const GetStackTraceCommand = withCdpCommand("Debugger.getStackTrace", GetStackTraceParams, GetStackTraceResult); export const PauseParams = withCdpMeta(z.object({ }).passthrough(), "Debugger.pause.params", "commandParams", { method: "Debugger.pause" }); export const PauseResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.pause.result", "commandResult", { method: "Debugger.pause" }); +export const PauseCommand = withCdpCommand("Debugger.pause", PauseParams, PauseResult); export const PauseOnAsyncCallParams = withCdpMeta(z.object({ "parentStackTraceId": z.lazy(() => Runtime.StackTraceId) }).passthrough(), "Debugger.pauseOnAsyncCall.params", "commandParams", { method: "Debugger.pauseOnAsyncCall" }); export const PauseOnAsyncCallResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.pauseOnAsyncCall.result", "commandResult", { method: "Debugger.pauseOnAsyncCall" }); +export const PauseOnAsyncCallCommand = withCdpCommand("Debugger.pauseOnAsyncCall", PauseOnAsyncCallParams, PauseOnAsyncCallResult); export const RemoveBreakpointParams = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId) }).passthrough(), "Debugger.removeBreakpoint.params", "commandParams", { method: "Debugger.removeBreakpoint" }); export const RemoveBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.removeBreakpoint.result", "commandResult", { method: "Debugger.removeBreakpoint" }); +export const RemoveBreakpointCommand = withCdpCommand("Debugger.removeBreakpoint", RemoveBreakpointParams, RemoveBreakpointResult); export const RestartFrameParams = withCdpMeta(z.object({ "callFrameId": z.lazy(() => CallFrameId), "mode": z.enum(["StepInto"]).optional() }).passthrough(), "Debugger.restartFrame.params", "commandParams", { method: "Debugger.restartFrame" }); export const RestartFrameResult = withCdpMeta(z.object({ "callFrames": z.array(z.lazy(() => CallFrame)), "asyncStackTrace": z.lazy(() => Runtime.StackTrace).optional(), "asyncStackTraceId": z.lazy(() => Runtime.StackTraceId).optional() }).passthrough(), "Debugger.restartFrame.result", "commandResult", { method: "Debugger.restartFrame" }); +export const RestartFrameCommand = withCdpCommand("Debugger.restartFrame", RestartFrameParams, RestartFrameResult); export const ResumeParams = withCdpMeta(z.object({ "terminateOnResume": z.boolean().optional() }).passthrough(), "Debugger.resume.params", "commandParams", { method: "Debugger.resume" }); export const ResumeResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.resume.result", "commandResult", { method: "Debugger.resume" }); +export const ResumeCommand = withCdpCommand("Debugger.resume", ResumeParams, ResumeResult); export const SearchInContentParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId), "query": z.string(), "caseSensitive": z.boolean().optional(), "isRegex": z.boolean().optional() }).passthrough(), "Debugger.searchInContent.params", "commandParams", { method: "Debugger.searchInContent" }); export const SearchInContentResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => SearchMatch)) }).passthrough(), "Debugger.searchInContent.result", "commandResult", { method: "Debugger.searchInContent" }); +export const SearchInContentCommand = withCdpCommand("Debugger.searchInContent", SearchInContentParams, SearchInContentResult); export const SetAsyncCallStackDepthParams = withCdpMeta(z.object({ "maxDepth": z.number().int() }).passthrough(), "Debugger.setAsyncCallStackDepth.params", "commandParams", { method: "Debugger.setAsyncCallStackDepth" }); export const SetAsyncCallStackDepthResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setAsyncCallStackDepth.result", "commandResult", { method: "Debugger.setAsyncCallStackDepth" }); +export const SetAsyncCallStackDepthCommand = withCdpCommand("Debugger.setAsyncCallStackDepth", SetAsyncCallStackDepthParams, SetAsyncCallStackDepthResult); export const SetBlackboxExecutionContextsParams = withCdpMeta(z.object({ "uniqueIds": z.array(z.string()) }).passthrough(), "Debugger.setBlackboxExecutionContexts.params", "commandParams", { method: "Debugger.setBlackboxExecutionContexts" }); export const SetBlackboxExecutionContextsResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setBlackboxExecutionContexts.result", "commandResult", { method: "Debugger.setBlackboxExecutionContexts" }); +export const SetBlackboxExecutionContextsCommand = withCdpCommand("Debugger.setBlackboxExecutionContexts", SetBlackboxExecutionContextsParams, SetBlackboxExecutionContextsResult); export const SetBlackboxPatternsParams = withCdpMeta(z.object({ "patterns": z.array(z.string()), "skipAnonymous": z.boolean().optional() }).passthrough(), "Debugger.setBlackboxPatterns.params", "commandParams", { method: "Debugger.setBlackboxPatterns" }); export const SetBlackboxPatternsResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setBlackboxPatterns.result", "commandResult", { method: "Debugger.setBlackboxPatterns" }); +export const SetBlackboxPatternsCommand = withCdpCommand("Debugger.setBlackboxPatterns", SetBlackboxPatternsParams, SetBlackboxPatternsResult); export const SetBlackboxedRangesParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId), "positions": z.array(z.lazy(() => ScriptPosition)) }).passthrough(), "Debugger.setBlackboxedRanges.params", "commandParams", { method: "Debugger.setBlackboxedRanges" }); export const SetBlackboxedRangesResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setBlackboxedRanges.result", "commandResult", { method: "Debugger.setBlackboxedRanges" }); +export const SetBlackboxedRangesCommand = withCdpCommand("Debugger.setBlackboxedRanges", SetBlackboxedRangesParams, SetBlackboxedRangesResult); export const SetBreakpointParams = withCdpMeta(z.object({ "location": z.lazy(() => Location), "condition": z.string().optional() }).passthrough(), "Debugger.setBreakpoint.params", "commandParams", { method: "Debugger.setBreakpoint" }); export const SetBreakpointResult = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId), "actualLocation": z.lazy(() => Location) }).passthrough(), "Debugger.setBreakpoint.result", "commandResult", { method: "Debugger.setBreakpoint" }); +export const SetBreakpointCommand = withCdpCommand("Debugger.setBreakpoint", SetBreakpointParams, SetBreakpointResult); export const SetInstrumentationBreakpointParams = withCdpMeta(z.object({ "instrumentation": z.enum(["beforeScriptExecution", "beforeScriptWithSourceMapExecution"]) }).passthrough(), "Debugger.setInstrumentationBreakpoint.params", "commandParams", { method: "Debugger.setInstrumentationBreakpoint" }); export const SetInstrumentationBreakpointResult = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId) }).passthrough(), "Debugger.setInstrumentationBreakpoint.result", "commandResult", { method: "Debugger.setInstrumentationBreakpoint" }); +export const SetInstrumentationBreakpointCommand = withCdpCommand("Debugger.setInstrumentationBreakpoint", SetInstrumentationBreakpointParams, SetInstrumentationBreakpointResult); export const SetBreakpointByUrlParams = withCdpMeta(z.object({ "lineNumber": z.number().int(), "url": z.string().optional(), "urlRegex": z.string().optional(), "scriptHash": z.string().optional(), "columnNumber": z.number().int().optional(), "condition": z.string().optional() }).passthrough(), "Debugger.setBreakpointByUrl.params", "commandParams", { method: "Debugger.setBreakpointByUrl" }); export const SetBreakpointByUrlResult = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId), "locations": z.array(z.lazy(() => Location)) }).passthrough(), "Debugger.setBreakpointByUrl.result", "commandResult", { method: "Debugger.setBreakpointByUrl" }); +export const SetBreakpointByUrlCommand = withCdpCommand("Debugger.setBreakpointByUrl", SetBreakpointByUrlParams, SetBreakpointByUrlResult); export const SetBreakpointOnFunctionCallParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId), "condition": z.string().optional() }).passthrough(), "Debugger.setBreakpointOnFunctionCall.params", "commandParams", { method: "Debugger.setBreakpointOnFunctionCall" }); export const SetBreakpointOnFunctionCallResult = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId) }).passthrough(), "Debugger.setBreakpointOnFunctionCall.result", "commandResult", { method: "Debugger.setBreakpointOnFunctionCall" }); +export const SetBreakpointOnFunctionCallCommand = withCdpCommand("Debugger.setBreakpointOnFunctionCall", SetBreakpointOnFunctionCallParams, SetBreakpointOnFunctionCallResult); export const SetBreakpointsActiveParams = withCdpMeta(z.object({ "active": z.boolean() }).passthrough(), "Debugger.setBreakpointsActive.params", "commandParams", { method: "Debugger.setBreakpointsActive" }); export const SetBreakpointsActiveResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setBreakpointsActive.result", "commandResult", { method: "Debugger.setBreakpointsActive" }); +export const SetBreakpointsActiveCommand = withCdpCommand("Debugger.setBreakpointsActive", SetBreakpointsActiveParams, SetBreakpointsActiveResult); export const SetPauseOnExceptionsParams = withCdpMeta(z.object({ "state": z.enum(["none", "caught", "uncaught", "all"]) }).passthrough(), "Debugger.setPauseOnExceptions.params", "commandParams", { method: "Debugger.setPauseOnExceptions" }); export const SetPauseOnExceptionsResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setPauseOnExceptions.result", "commandResult", { method: "Debugger.setPauseOnExceptions" }); +export const SetPauseOnExceptionsCommand = withCdpCommand("Debugger.setPauseOnExceptions", SetPauseOnExceptionsParams, SetPauseOnExceptionsResult); export const SetReturnValueParams = withCdpMeta(z.object({ "newValue": z.lazy(() => Runtime.CallArgument) }).passthrough(), "Debugger.setReturnValue.params", "commandParams", { method: "Debugger.setReturnValue" }); export const SetReturnValueResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setReturnValue.result", "commandResult", { method: "Debugger.setReturnValue" }); +export const SetReturnValueCommand = withCdpCommand("Debugger.setReturnValue", SetReturnValueParams, SetReturnValueResult); export const SetScriptSourceParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId), "scriptSource": z.string(), "dryRun": z.boolean().optional(), "allowTopFrameEditing": z.boolean().optional() }).passthrough(), "Debugger.setScriptSource.params", "commandParams", { method: "Debugger.setScriptSource" }); export const SetScriptSourceResult = withCdpMeta(z.object({ "callFrames": z.array(z.lazy(() => CallFrame)).optional(), "stackChanged": z.boolean().optional(), "asyncStackTrace": z.lazy(() => Runtime.StackTrace).optional(), "asyncStackTraceId": z.lazy(() => Runtime.StackTraceId).optional(), "status": z.enum(["Ok", "CompileError", "BlockedByActiveGenerator", "BlockedByActiveFunction", "BlockedByTopLevelEsModuleChange"]), "exceptionDetails": z.lazy(() => Runtime.ExceptionDetails).optional() }).passthrough(), "Debugger.setScriptSource.result", "commandResult", { method: "Debugger.setScriptSource" }); +export const SetScriptSourceCommand = withCdpCommand("Debugger.setScriptSource", SetScriptSourceParams, SetScriptSourceResult); export const SetSkipAllPausesParams = withCdpMeta(z.object({ "skip": z.boolean() }).passthrough(), "Debugger.setSkipAllPauses.params", "commandParams", { method: "Debugger.setSkipAllPauses" }); export const SetSkipAllPausesResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setSkipAllPauses.result", "commandResult", { method: "Debugger.setSkipAllPauses" }); +export const SetSkipAllPausesCommand = withCdpCommand("Debugger.setSkipAllPauses", SetSkipAllPausesParams, SetSkipAllPausesResult); export const SetVariableValueParams = withCdpMeta(z.object({ "scopeNumber": z.number().int(), "variableName": z.string(), "newValue": z.lazy(() => Runtime.CallArgument), "callFrameId": z.lazy(() => CallFrameId) }).passthrough(), "Debugger.setVariableValue.params", "commandParams", { method: "Debugger.setVariableValue" }); export const SetVariableValueResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.setVariableValue.result", "commandResult", { method: "Debugger.setVariableValue" }); +export const SetVariableValueCommand = withCdpCommand("Debugger.setVariableValue", SetVariableValueParams, SetVariableValueResult); export const StepIntoParams = withCdpMeta(z.object({ "breakOnAsyncCall": z.boolean().optional(), "skipList": z.array(z.lazy(() => LocationRange)).optional() }).passthrough(), "Debugger.stepInto.params", "commandParams", { method: "Debugger.stepInto" }); export const StepIntoResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.stepInto.result", "commandResult", { method: "Debugger.stepInto" }); +export const StepIntoCommand = withCdpCommand("Debugger.stepInto", StepIntoParams, StepIntoResult); export const StepOutParams = withCdpMeta(z.object({ }).passthrough(), "Debugger.stepOut.params", "commandParams", { method: "Debugger.stepOut" }); export const StepOutResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.stepOut.result", "commandResult", { method: "Debugger.stepOut" }); +export const StepOutCommand = withCdpCommand("Debugger.stepOut", StepOutParams, StepOutResult); export const StepOverParams = withCdpMeta(z.object({ "skipList": z.array(z.lazy(() => LocationRange)).optional() }).passthrough(), "Debugger.stepOver.params", "commandParams", { method: "Debugger.stepOver" }); export const StepOverResult = withCdpMeta(z.object({ }).passthrough(), "Debugger.stepOver.result", "commandResult", { method: "Debugger.stepOver" }); +export const StepOverCommand = withCdpCommand("Debugger.stepOver", StepOverParams, StepOverResult); export const BreakpointResolvedEvent = withCdpMeta(z.object({ "breakpointId": z.lazy(() => BreakpointId), "location": z.lazy(() => Location) }).passthrough(), "Debugger.breakpointResolved", "event", { phase: "event" }); export const PausedEvent = withCdpMeta(z.object({ "callFrames": z.array(z.lazy(() => CallFrame)), "reason": z.enum(["ambiguous", "assert", "CSPViolation", "debugCommand", "DOM", "EventListener", "exception", "instrumentation", "OOM", "other", "promiseRejection", "XHR", "step"]), "data": z.record(z.string(), z.unknown()).optional(), "hitBreakpoints": z.array(z.string()).optional(), "asyncStackTrace": z.lazy(() => Runtime.StackTrace).optional(), "asyncStackTraceId": z.lazy(() => Runtime.StackTraceId).optional(), "asyncCallStackTraceId": z.lazy(() => Runtime.StackTraceId).optional() }).passthrough(), "Debugger.paused", "event", { phase: "event" }); export const ResumedEvent = withCdpMeta(z.object({ }).passthrough(), "Debugger.resumed", "event", { phase: "event" }); @@ -176,39 +209,39 @@ export const zod = { ScriptParsedEvent: ScriptParsedEvent, } as const; export const commands = { - "Debugger.continueToLocation": { params: ContinueToLocationParams, result: ContinueToLocationResult }, - "Debugger.disable": { params: DisableParams, result: DisableResult }, - "Debugger.enable": { params: EnableParams, result: EnableResult }, - "Debugger.evaluateOnCallFrame": { params: EvaluateOnCallFrameParams, result: EvaluateOnCallFrameResult }, - "Debugger.getPossibleBreakpoints": { params: GetPossibleBreakpointsParams, result: GetPossibleBreakpointsResult }, - "Debugger.getScriptSource": { params: GetScriptSourceParams, result: GetScriptSourceResult }, - "Debugger.disassembleWasmModule": { params: DisassembleWasmModuleParams, result: DisassembleWasmModuleResult }, - "Debugger.nextWasmDisassemblyChunk": { params: NextWasmDisassemblyChunkParams, result: NextWasmDisassemblyChunkResult }, - "Debugger.getWasmBytecode": { params: GetWasmBytecodeParams, result: GetWasmBytecodeResult }, - "Debugger.getStackTrace": { params: GetStackTraceParams, result: GetStackTraceResult }, - "Debugger.pause": { params: PauseParams, result: PauseResult }, - "Debugger.pauseOnAsyncCall": { params: PauseOnAsyncCallParams, result: PauseOnAsyncCallResult }, - "Debugger.removeBreakpoint": { params: RemoveBreakpointParams, result: RemoveBreakpointResult }, - "Debugger.restartFrame": { params: RestartFrameParams, result: RestartFrameResult }, - "Debugger.resume": { params: ResumeParams, result: ResumeResult }, - "Debugger.searchInContent": { params: SearchInContentParams, result: SearchInContentResult }, - "Debugger.setAsyncCallStackDepth": { params: SetAsyncCallStackDepthParams, result: SetAsyncCallStackDepthResult }, - "Debugger.setBlackboxExecutionContexts": { params: SetBlackboxExecutionContextsParams, result: SetBlackboxExecutionContextsResult }, - "Debugger.setBlackboxPatterns": { params: SetBlackboxPatternsParams, result: SetBlackboxPatternsResult }, - "Debugger.setBlackboxedRanges": { params: SetBlackboxedRangesParams, result: SetBlackboxedRangesResult }, - "Debugger.setBreakpoint": { params: SetBreakpointParams, result: SetBreakpointResult }, - "Debugger.setInstrumentationBreakpoint": { params: SetInstrumentationBreakpointParams, result: SetInstrumentationBreakpointResult }, - "Debugger.setBreakpointByUrl": { params: SetBreakpointByUrlParams, result: SetBreakpointByUrlResult }, - "Debugger.setBreakpointOnFunctionCall": { params: SetBreakpointOnFunctionCallParams, result: SetBreakpointOnFunctionCallResult }, - "Debugger.setBreakpointsActive": { params: SetBreakpointsActiveParams, result: SetBreakpointsActiveResult }, - "Debugger.setPauseOnExceptions": { params: SetPauseOnExceptionsParams, result: SetPauseOnExceptionsResult }, - "Debugger.setReturnValue": { params: SetReturnValueParams, result: SetReturnValueResult }, - "Debugger.setScriptSource": { params: SetScriptSourceParams, result: SetScriptSourceResult }, - "Debugger.setSkipAllPauses": { params: SetSkipAllPausesParams, result: SetSkipAllPausesResult }, - "Debugger.setVariableValue": { params: SetVariableValueParams, result: SetVariableValueResult }, - "Debugger.stepInto": { params: StepIntoParams, result: StepIntoResult }, - "Debugger.stepOut": { params: StepOutParams, result: StepOutResult }, - "Debugger.stepOver": { params: StepOverParams, result: StepOverResult }, + "Debugger.continueToLocation": ContinueToLocationCommand, + "Debugger.disable": DisableCommand, + "Debugger.enable": EnableCommand, + "Debugger.evaluateOnCallFrame": EvaluateOnCallFrameCommand, + "Debugger.getPossibleBreakpoints": GetPossibleBreakpointsCommand, + "Debugger.getScriptSource": GetScriptSourceCommand, + "Debugger.disassembleWasmModule": DisassembleWasmModuleCommand, + "Debugger.nextWasmDisassemblyChunk": NextWasmDisassemblyChunkCommand, + "Debugger.getWasmBytecode": GetWasmBytecodeCommand, + "Debugger.getStackTrace": GetStackTraceCommand, + "Debugger.pause": PauseCommand, + "Debugger.pauseOnAsyncCall": PauseOnAsyncCallCommand, + "Debugger.removeBreakpoint": RemoveBreakpointCommand, + "Debugger.restartFrame": RestartFrameCommand, + "Debugger.resume": ResumeCommand, + "Debugger.searchInContent": SearchInContentCommand, + "Debugger.setAsyncCallStackDepth": SetAsyncCallStackDepthCommand, + "Debugger.setBlackboxExecutionContexts": SetBlackboxExecutionContextsCommand, + "Debugger.setBlackboxPatterns": SetBlackboxPatternsCommand, + "Debugger.setBlackboxedRanges": SetBlackboxedRangesCommand, + "Debugger.setBreakpoint": SetBreakpointCommand, + "Debugger.setInstrumentationBreakpoint": SetInstrumentationBreakpointCommand, + "Debugger.setBreakpointByUrl": SetBreakpointByUrlCommand, + "Debugger.setBreakpointOnFunctionCall": SetBreakpointOnFunctionCallCommand, + "Debugger.setBreakpointsActive": SetBreakpointsActiveCommand, + "Debugger.setPauseOnExceptions": SetPauseOnExceptionsCommand, + "Debugger.setReturnValue": SetReturnValueCommand, + "Debugger.setScriptSource": SetScriptSourceCommand, + "Debugger.setSkipAllPauses": SetSkipAllPausesCommand, + "Debugger.setVariableValue": SetVariableValueCommand, + "Debugger.stepInto": StepIntoCommand, + "Debugger.stepOut": StepOutCommand, + "Debugger.stepOver": StepOverCommand, } as const; export const events = { "Debugger.breakpointResolved": BreakpointResolvedEvent, diff --git a/js/src/types/generated/zod/DeviceAccess.ts b/js/src/types/generated/zod/DeviceAccess.ts index 5d860a85..8a69d343 100644 --- a/js/src/types/generated/zod/DeviceAccess.ts +++ b/js/src/types/generated/zod/DeviceAccess.ts @@ -1,19 +1,23 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const RequestId = withCdpMeta(z.string(), "DeviceAccess.RequestId", "type"); export const DeviceId = withCdpMeta(z.string(), "DeviceAccess.DeviceId", "type"); export const PromptDevice = withCdpMeta(z.object({ "id": z.lazy(() => DeviceId), "name": z.string() }).passthrough(), "DeviceAccess.PromptDevice", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.enable.params", "commandParams", { method: "DeviceAccess.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.enable.result", "commandResult", { method: "DeviceAccess.enable" }); +export const EnableCommand = withCdpCommand("DeviceAccess.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.disable.params", "commandParams", { method: "DeviceAccess.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.disable.result", "commandResult", { method: "DeviceAccess.disable" }); +export const DisableCommand = withCdpCommand("DeviceAccess.disable", DisableParams, DisableResult); export const SelectPromptParams = withCdpMeta(z.object({ "id": z.lazy(() => RequestId), "deviceId": z.lazy(() => DeviceId) }).passthrough(), "DeviceAccess.selectPrompt.params", "commandParams", { method: "DeviceAccess.selectPrompt" }); export const SelectPromptResult = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.selectPrompt.result", "commandResult", { method: "DeviceAccess.selectPrompt" }); +export const SelectPromptCommand = withCdpCommand("DeviceAccess.selectPrompt", SelectPromptParams, SelectPromptResult); export const CancelPromptParams = withCdpMeta(z.object({ "id": z.lazy(() => RequestId) }).passthrough(), "DeviceAccess.cancelPrompt.params", "commandParams", { method: "DeviceAccess.cancelPrompt" }); export const CancelPromptResult = withCdpMeta(z.object({ }).passthrough(), "DeviceAccess.cancelPrompt.result", "commandResult", { method: "DeviceAccess.cancelPrompt" }); +export const CancelPromptCommand = withCdpCommand("DeviceAccess.cancelPrompt", CancelPromptParams, CancelPromptResult); export const DeviceRequestPromptedEvent = withCdpMeta(z.object({ "id": z.lazy(() => RequestId), "devices": z.array(z.lazy(() => PromptDevice)) }).passthrough(), "DeviceAccess.deviceRequestPrompted", "event", { phase: "event" }); export const zod = { @@ -31,10 +35,10 @@ export const zod = { DeviceRequestPromptedEvent: DeviceRequestPromptedEvent, } as const; export const commands = { - "DeviceAccess.enable": { params: EnableParams, result: EnableResult }, - "DeviceAccess.disable": { params: DisableParams, result: DisableResult }, - "DeviceAccess.selectPrompt": { params: SelectPromptParams, result: SelectPromptResult }, - "DeviceAccess.cancelPrompt": { params: CancelPromptParams, result: CancelPromptResult }, + "DeviceAccess.enable": EnableCommand, + "DeviceAccess.disable": DisableCommand, + "DeviceAccess.selectPrompt": SelectPromptCommand, + "DeviceAccess.cancelPrompt": CancelPromptCommand, } as const; export const events = { "DeviceAccess.deviceRequestPrompted": DeviceRequestPromptedEvent, diff --git a/js/src/types/generated/zod/DeviceOrientation.ts b/js/src/types/generated/zod/DeviceOrientation.ts index 3f06e148..af297fb7 100644 --- a/js/src/types/generated/zod/DeviceOrientation.ts +++ b/js/src/types/generated/zod/DeviceOrientation.ts @@ -1,12 +1,14 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const ClearDeviceOrientationOverrideParams = withCdpMeta(z.object({ }).passthrough(), "DeviceOrientation.clearDeviceOrientationOverride.params", "commandParams", { method: "DeviceOrientation.clearDeviceOrientationOverride" }); export const ClearDeviceOrientationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "DeviceOrientation.clearDeviceOrientationOverride.result", "commandResult", { method: "DeviceOrientation.clearDeviceOrientationOverride" }); +export const ClearDeviceOrientationOverrideCommand = withCdpCommand("DeviceOrientation.clearDeviceOrientationOverride", ClearDeviceOrientationOverrideParams, ClearDeviceOrientationOverrideResult); export const SetDeviceOrientationOverrideParams = withCdpMeta(z.object({ "alpha": z.number(), "beta": z.number(), "gamma": z.number() }).passthrough(), "DeviceOrientation.setDeviceOrientationOverride.params", "commandParams", { method: "DeviceOrientation.setDeviceOrientationOverride" }); export const SetDeviceOrientationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "DeviceOrientation.setDeviceOrientationOverride.result", "commandResult", { method: "DeviceOrientation.setDeviceOrientationOverride" }); +export const SetDeviceOrientationOverrideCommand = withCdpCommand("DeviceOrientation.setDeviceOrientationOverride", SetDeviceOrientationOverrideParams, SetDeviceOrientationOverrideResult); export const zod = { ClearDeviceOrientationOverrideParams: ClearDeviceOrientationOverrideParams, @@ -15,8 +17,8 @@ export const zod = { SetDeviceOrientationOverrideResult: SetDeviceOrientationOverrideResult, } as const; export const commands = { - "DeviceOrientation.clearDeviceOrientationOverride": { params: ClearDeviceOrientationOverrideParams, result: ClearDeviceOrientationOverrideResult }, - "DeviceOrientation.setDeviceOrientationOverride": { params: SetDeviceOrientationOverrideParams, result: SetDeviceOrientationOverrideResult }, + "DeviceOrientation.clearDeviceOrientationOverride": ClearDeviceOrientationOverrideCommand, + "DeviceOrientation.setDeviceOrientationOverride": SetDeviceOrientationOverrideCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Emulation.ts b/js/src/types/generated/zod/Emulation.ts index 00cfea62..6c26da7f 100644 --- a/js/src/types/generated/zod/Emulation.ts +++ b/js/src/types/generated/zod/Emulation.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -29,100 +29,148 @@ export const ScreenInfo = withCdpMeta(z.object({ "left": z.number().int(), "top" export const DisabledImageType = withCdpMeta(z.enum(["avif", "jxl", "webp"]), "Emulation.DisabledImageType", "type"); export const CanEmulateParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.canEmulate.params", "commandParams", { method: "Emulation.canEmulate" }); export const CanEmulateResult = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Emulation.canEmulate.result", "commandResult", { method: "Emulation.canEmulate" }); +export const CanEmulateCommand = withCdpCommand("Emulation.canEmulate", CanEmulateParams, CanEmulateResult); export const ClearDeviceMetricsOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDeviceMetricsOverride.params", "commandParams", { method: "Emulation.clearDeviceMetricsOverride" }); export const ClearDeviceMetricsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDeviceMetricsOverride.result", "commandResult", { method: "Emulation.clearDeviceMetricsOverride" }); +export const ClearDeviceMetricsOverrideCommand = withCdpCommand("Emulation.clearDeviceMetricsOverride", ClearDeviceMetricsOverrideParams, ClearDeviceMetricsOverrideResult); export const ClearGeolocationOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearGeolocationOverride.params", "commandParams", { method: "Emulation.clearGeolocationOverride" }); export const ClearGeolocationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearGeolocationOverride.result", "commandResult", { method: "Emulation.clearGeolocationOverride" }); +export const ClearGeolocationOverrideCommand = withCdpCommand("Emulation.clearGeolocationOverride", ClearGeolocationOverrideParams, ClearGeolocationOverrideResult); export const ResetPageScaleFactorParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.resetPageScaleFactor.params", "commandParams", { method: "Emulation.resetPageScaleFactor" }); export const ResetPageScaleFactorResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.resetPageScaleFactor.result", "commandResult", { method: "Emulation.resetPageScaleFactor" }); +export const ResetPageScaleFactorCommand = withCdpCommand("Emulation.resetPageScaleFactor", ResetPageScaleFactorParams, ResetPageScaleFactorResult); export const SetFocusEmulationEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Emulation.setFocusEmulationEnabled.params", "commandParams", { method: "Emulation.setFocusEmulationEnabled" }); export const SetFocusEmulationEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setFocusEmulationEnabled.result", "commandResult", { method: "Emulation.setFocusEmulationEnabled" }); +export const SetFocusEmulationEnabledCommand = withCdpCommand("Emulation.setFocusEmulationEnabled", SetFocusEmulationEnabledParams, SetFocusEmulationEnabledResult); export const SetAutoDarkModeOverrideParams = withCdpMeta(z.object({ "enabled": z.boolean().optional() }).passthrough(), "Emulation.setAutoDarkModeOverride.params", "commandParams", { method: "Emulation.setAutoDarkModeOverride" }); export const SetAutoDarkModeOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setAutoDarkModeOverride.result", "commandResult", { method: "Emulation.setAutoDarkModeOverride" }); +export const SetAutoDarkModeOverrideCommand = withCdpCommand("Emulation.setAutoDarkModeOverride", SetAutoDarkModeOverrideParams, SetAutoDarkModeOverrideResult); export const SetCPUThrottlingRateParams = withCdpMeta(z.object({ "rate": z.number() }).passthrough(), "Emulation.setCPUThrottlingRate.params", "commandParams", { method: "Emulation.setCPUThrottlingRate" }); export const SetCPUThrottlingRateResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setCPUThrottlingRate.result", "commandResult", { method: "Emulation.setCPUThrottlingRate" }); +export const SetCPUThrottlingRateCommand = withCdpCommand("Emulation.setCPUThrottlingRate", SetCPUThrottlingRateParams, SetCPUThrottlingRateResult); export const SetDefaultBackgroundColorOverrideParams = withCdpMeta(z.object({ "color": z.lazy(() => DOM.RGBA).optional() }).passthrough(), "Emulation.setDefaultBackgroundColorOverride.params", "commandParams", { method: "Emulation.setDefaultBackgroundColorOverride" }); export const SetDefaultBackgroundColorOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDefaultBackgroundColorOverride.result", "commandResult", { method: "Emulation.setDefaultBackgroundColorOverride" }); +export const SetDefaultBackgroundColorOverrideCommand = withCdpCommand("Emulation.setDefaultBackgroundColorOverride", SetDefaultBackgroundColorOverrideParams, SetDefaultBackgroundColorOverrideResult); export const SetSafeAreaInsetsOverrideParams = withCdpMeta(z.object({ "insets": z.lazy(() => SafeAreaInsets) }).passthrough(), "Emulation.setSafeAreaInsetsOverride.params", "commandParams", { method: "Emulation.setSafeAreaInsetsOverride" }); export const SetSafeAreaInsetsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setSafeAreaInsetsOverride.result", "commandResult", { method: "Emulation.setSafeAreaInsetsOverride" }); +export const SetSafeAreaInsetsOverrideCommand = withCdpCommand("Emulation.setSafeAreaInsetsOverride", SetSafeAreaInsetsOverrideParams, SetSafeAreaInsetsOverrideResult); export const SetDeviceMetricsOverrideParams = withCdpMeta(z.object({ "width": z.number().int(), "height": z.number().int(), "deviceScaleFactor": z.number(), "mobile": z.boolean(), "scale": z.number().optional(), "screenWidth": z.number().int().optional(), "screenHeight": z.number().int().optional(), "positionX": z.number().int().optional(), "positionY": z.number().int().optional(), "dontSetVisibleSize": z.boolean().optional(), "screenOrientation": z.lazy(() => ScreenOrientation).optional(), "viewport": z.lazy(() => Page.Viewport).optional(), "displayFeature": z.lazy(() => DisplayFeature).optional(), "devicePosture": z.lazy(() => DevicePosture).optional(), "scrollbarType": z.enum(["overlay", "default"]).optional(), "screenOrientationLockEmulation": z.boolean().optional() }).passthrough(), "Emulation.setDeviceMetricsOverride.params", "commandParams", { method: "Emulation.setDeviceMetricsOverride" }); export const SetDeviceMetricsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDeviceMetricsOverride.result", "commandResult", { method: "Emulation.setDeviceMetricsOverride" }); +export const SetDeviceMetricsOverrideCommand = withCdpCommand("Emulation.setDeviceMetricsOverride", SetDeviceMetricsOverrideParams, SetDeviceMetricsOverrideResult); export const SetDevicePostureOverrideParams = withCdpMeta(z.object({ "posture": z.lazy(() => DevicePosture) }).passthrough(), "Emulation.setDevicePostureOverride.params", "commandParams", { method: "Emulation.setDevicePostureOverride" }); export const SetDevicePostureOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDevicePostureOverride.result", "commandResult", { method: "Emulation.setDevicePostureOverride" }); +export const SetDevicePostureOverrideCommand = withCdpCommand("Emulation.setDevicePostureOverride", SetDevicePostureOverrideParams, SetDevicePostureOverrideResult); export const ClearDevicePostureOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDevicePostureOverride.params", "commandParams", { method: "Emulation.clearDevicePostureOverride" }); export const ClearDevicePostureOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDevicePostureOverride.result", "commandResult", { method: "Emulation.clearDevicePostureOverride" }); +export const ClearDevicePostureOverrideCommand = withCdpCommand("Emulation.clearDevicePostureOverride", ClearDevicePostureOverrideParams, ClearDevicePostureOverrideResult); export const SetDisplayFeaturesOverrideParams = withCdpMeta(z.object({ "features": z.array(z.lazy(() => DisplayFeature)) }).passthrough(), "Emulation.setDisplayFeaturesOverride.params", "commandParams", { method: "Emulation.setDisplayFeaturesOverride" }); export const SetDisplayFeaturesOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDisplayFeaturesOverride.result", "commandResult", { method: "Emulation.setDisplayFeaturesOverride" }); +export const SetDisplayFeaturesOverrideCommand = withCdpCommand("Emulation.setDisplayFeaturesOverride", SetDisplayFeaturesOverrideParams, SetDisplayFeaturesOverrideResult); export const ClearDisplayFeaturesOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDisplayFeaturesOverride.params", "commandParams", { method: "Emulation.clearDisplayFeaturesOverride" }); export const ClearDisplayFeaturesOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearDisplayFeaturesOverride.result", "commandResult", { method: "Emulation.clearDisplayFeaturesOverride" }); +export const ClearDisplayFeaturesOverrideCommand = withCdpCommand("Emulation.clearDisplayFeaturesOverride", ClearDisplayFeaturesOverrideParams, ClearDisplayFeaturesOverrideResult); export const SetScrollbarsHiddenParams = withCdpMeta(z.object({ "hidden": z.boolean() }).passthrough(), "Emulation.setScrollbarsHidden.params", "commandParams", { method: "Emulation.setScrollbarsHidden" }); export const SetScrollbarsHiddenResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setScrollbarsHidden.result", "commandResult", { method: "Emulation.setScrollbarsHidden" }); +export const SetScrollbarsHiddenCommand = withCdpCommand("Emulation.setScrollbarsHidden", SetScrollbarsHiddenParams, SetScrollbarsHiddenResult); export const SetDocumentCookieDisabledParams = withCdpMeta(z.object({ "disabled": z.boolean() }).passthrough(), "Emulation.setDocumentCookieDisabled.params", "commandParams", { method: "Emulation.setDocumentCookieDisabled" }); export const SetDocumentCookieDisabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDocumentCookieDisabled.result", "commandResult", { method: "Emulation.setDocumentCookieDisabled" }); +export const SetDocumentCookieDisabledCommand = withCdpCommand("Emulation.setDocumentCookieDisabled", SetDocumentCookieDisabledParams, SetDocumentCookieDisabledResult); export const SetEmitTouchEventsForMouseParams = withCdpMeta(z.object({ "enabled": z.boolean(), "configuration": z.enum(["mobile", "desktop"]).optional() }).passthrough(), "Emulation.setEmitTouchEventsForMouse.params", "commandParams", { method: "Emulation.setEmitTouchEventsForMouse" }); export const SetEmitTouchEventsForMouseResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setEmitTouchEventsForMouse.result", "commandResult", { method: "Emulation.setEmitTouchEventsForMouse" }); +export const SetEmitTouchEventsForMouseCommand = withCdpCommand("Emulation.setEmitTouchEventsForMouse", SetEmitTouchEventsForMouseParams, SetEmitTouchEventsForMouseResult); export const SetEmulatedMediaParams = withCdpMeta(z.object({ "media": z.string().optional(), "features": z.array(z.lazy(() => MediaFeature)).optional() }).passthrough(), "Emulation.setEmulatedMedia.params", "commandParams", { method: "Emulation.setEmulatedMedia" }); export const SetEmulatedMediaResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setEmulatedMedia.result", "commandResult", { method: "Emulation.setEmulatedMedia" }); +export const SetEmulatedMediaCommand = withCdpCommand("Emulation.setEmulatedMedia", SetEmulatedMediaParams, SetEmulatedMediaResult); export const SetEmulatedVisionDeficiencyParams = withCdpMeta(z.object({ "type": z.enum(["none", "blurredVision", "reducedContrast", "achromatopsia", "deuteranopia", "protanopia", "tritanopia"]) }).passthrough(), "Emulation.setEmulatedVisionDeficiency.params", "commandParams", { method: "Emulation.setEmulatedVisionDeficiency" }); export const SetEmulatedVisionDeficiencyResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setEmulatedVisionDeficiency.result", "commandResult", { method: "Emulation.setEmulatedVisionDeficiency" }); +export const SetEmulatedVisionDeficiencyCommand = withCdpCommand("Emulation.setEmulatedVisionDeficiency", SetEmulatedVisionDeficiencyParams, SetEmulatedVisionDeficiencyResult); export const SetEmulatedOSTextScaleParams = withCdpMeta(z.object({ "scale": z.number().optional() }).passthrough(), "Emulation.setEmulatedOSTextScale.params", "commandParams", { method: "Emulation.setEmulatedOSTextScale" }); export const SetEmulatedOSTextScaleResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setEmulatedOSTextScale.result", "commandResult", { method: "Emulation.setEmulatedOSTextScale" }); +export const SetEmulatedOSTextScaleCommand = withCdpCommand("Emulation.setEmulatedOSTextScale", SetEmulatedOSTextScaleParams, SetEmulatedOSTextScaleResult); export const SetGeolocationOverrideParams = withCdpMeta(z.object({ "latitude": z.number().optional(), "longitude": z.number().optional(), "accuracy": z.number().optional(), "altitude": z.number().optional(), "altitudeAccuracy": z.number().optional(), "heading": z.number().optional(), "speed": z.number().optional() }).passthrough(), "Emulation.setGeolocationOverride.params", "commandParams", { method: "Emulation.setGeolocationOverride" }); export const SetGeolocationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setGeolocationOverride.result", "commandResult", { method: "Emulation.setGeolocationOverride" }); +export const SetGeolocationOverrideCommand = withCdpCommand("Emulation.setGeolocationOverride", SetGeolocationOverrideParams, SetGeolocationOverrideResult); export const GetOverriddenSensorInformationParams = withCdpMeta(z.object({ "type": z.lazy(() => SensorType) }).passthrough(), "Emulation.getOverriddenSensorInformation.params", "commandParams", { method: "Emulation.getOverriddenSensorInformation" }); export const GetOverriddenSensorInformationResult = withCdpMeta(z.object({ "requestedSamplingFrequency": z.number() }).passthrough(), "Emulation.getOverriddenSensorInformation.result", "commandResult", { method: "Emulation.getOverriddenSensorInformation" }); +export const GetOverriddenSensorInformationCommand = withCdpCommand("Emulation.getOverriddenSensorInformation", GetOverriddenSensorInformationParams, GetOverriddenSensorInformationResult); export const SetSensorOverrideEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean(), "type": z.lazy(() => SensorType), "metadata": z.lazy(() => SensorMetadata).optional() }).passthrough(), "Emulation.setSensorOverrideEnabled.params", "commandParams", { method: "Emulation.setSensorOverrideEnabled" }); export const SetSensorOverrideEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setSensorOverrideEnabled.result", "commandResult", { method: "Emulation.setSensorOverrideEnabled" }); +export const SetSensorOverrideEnabledCommand = withCdpCommand("Emulation.setSensorOverrideEnabled", SetSensorOverrideEnabledParams, SetSensorOverrideEnabledResult); export const SetSensorOverrideReadingsParams = withCdpMeta(z.object({ "type": z.lazy(() => SensorType), "reading": z.lazy(() => SensorReading) }).passthrough(), "Emulation.setSensorOverrideReadings.params", "commandParams", { method: "Emulation.setSensorOverrideReadings" }); export const SetSensorOverrideReadingsResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setSensorOverrideReadings.result", "commandResult", { method: "Emulation.setSensorOverrideReadings" }); +export const SetSensorOverrideReadingsCommand = withCdpCommand("Emulation.setSensorOverrideReadings", SetSensorOverrideReadingsParams, SetSensorOverrideReadingsResult); export const SetPressureSourceOverrideEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean(), "source": z.lazy(() => PressureSource), "metadata": z.lazy(() => PressureMetadata).optional() }).passthrough(), "Emulation.setPressureSourceOverrideEnabled.params", "commandParams", { method: "Emulation.setPressureSourceOverrideEnabled" }); export const SetPressureSourceOverrideEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setPressureSourceOverrideEnabled.result", "commandResult", { method: "Emulation.setPressureSourceOverrideEnabled" }); +export const SetPressureSourceOverrideEnabledCommand = withCdpCommand("Emulation.setPressureSourceOverrideEnabled", SetPressureSourceOverrideEnabledParams, SetPressureSourceOverrideEnabledResult); export const SetPressureStateOverrideParams = withCdpMeta(z.object({ "source": z.lazy(() => PressureSource), "state": z.lazy(() => PressureState) }).passthrough(), "Emulation.setPressureStateOverride.params", "commandParams", { method: "Emulation.setPressureStateOverride" }); export const SetPressureStateOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setPressureStateOverride.result", "commandResult", { method: "Emulation.setPressureStateOverride" }); +export const SetPressureStateOverrideCommand = withCdpCommand("Emulation.setPressureStateOverride", SetPressureStateOverrideParams, SetPressureStateOverrideResult); export const SetPressureDataOverrideParams = withCdpMeta(z.object({ "source": z.lazy(() => PressureSource), "state": z.lazy(() => PressureState), "ownContributionEstimate": z.number().optional() }).passthrough(), "Emulation.setPressureDataOverride.params", "commandParams", { method: "Emulation.setPressureDataOverride" }); export const SetPressureDataOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setPressureDataOverride.result", "commandResult", { method: "Emulation.setPressureDataOverride" }); +export const SetPressureDataOverrideCommand = withCdpCommand("Emulation.setPressureDataOverride", SetPressureDataOverrideParams, SetPressureDataOverrideResult); export const SetIdleOverrideParams = withCdpMeta(z.object({ "isUserActive": z.boolean(), "isScreenUnlocked": z.boolean() }).passthrough(), "Emulation.setIdleOverride.params", "commandParams", { method: "Emulation.setIdleOverride" }); export const SetIdleOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setIdleOverride.result", "commandResult", { method: "Emulation.setIdleOverride" }); +export const SetIdleOverrideCommand = withCdpCommand("Emulation.setIdleOverride", SetIdleOverrideParams, SetIdleOverrideResult); export const ClearIdleOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearIdleOverride.params", "commandParams", { method: "Emulation.clearIdleOverride" }); export const ClearIdleOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.clearIdleOverride.result", "commandResult", { method: "Emulation.clearIdleOverride" }); +export const ClearIdleOverrideCommand = withCdpCommand("Emulation.clearIdleOverride", ClearIdleOverrideParams, ClearIdleOverrideResult); export const SetNavigatorOverridesParams = withCdpMeta(z.object({ "platform": z.string() }).passthrough(), "Emulation.setNavigatorOverrides.params", "commandParams", { method: "Emulation.setNavigatorOverrides" }); export const SetNavigatorOverridesResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setNavigatorOverrides.result", "commandResult", { method: "Emulation.setNavigatorOverrides" }); +export const SetNavigatorOverridesCommand = withCdpCommand("Emulation.setNavigatorOverrides", SetNavigatorOverridesParams, SetNavigatorOverridesResult); export const SetPageScaleFactorParams = withCdpMeta(z.object({ "pageScaleFactor": z.number() }).passthrough(), "Emulation.setPageScaleFactor.params", "commandParams", { method: "Emulation.setPageScaleFactor" }); export const SetPageScaleFactorResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setPageScaleFactor.result", "commandResult", { method: "Emulation.setPageScaleFactor" }); +export const SetPageScaleFactorCommand = withCdpCommand("Emulation.setPageScaleFactor", SetPageScaleFactorParams, SetPageScaleFactorResult); export const SetScriptExecutionDisabledParams = withCdpMeta(z.object({ "value": z.boolean() }).passthrough(), "Emulation.setScriptExecutionDisabled.params", "commandParams", { method: "Emulation.setScriptExecutionDisabled" }); export const SetScriptExecutionDisabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setScriptExecutionDisabled.result", "commandResult", { method: "Emulation.setScriptExecutionDisabled" }); +export const SetScriptExecutionDisabledCommand = withCdpCommand("Emulation.setScriptExecutionDisabled", SetScriptExecutionDisabledParams, SetScriptExecutionDisabledResult); export const SetTouchEmulationEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean(), "maxTouchPoints": z.number().int().optional() }).passthrough(), "Emulation.setTouchEmulationEnabled.params", "commandParams", { method: "Emulation.setTouchEmulationEnabled" }); export const SetTouchEmulationEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setTouchEmulationEnabled.result", "commandResult", { method: "Emulation.setTouchEmulationEnabled" }); +export const SetTouchEmulationEnabledCommand = withCdpCommand("Emulation.setTouchEmulationEnabled", SetTouchEmulationEnabledParams, SetTouchEmulationEnabledResult); export const SetVirtualTimePolicyParams = withCdpMeta(z.object({ "policy": z.lazy(() => VirtualTimePolicy), "budget": z.number().optional(), "maxVirtualTimeTaskStarvationCount": z.number().int().optional(), "initialVirtualTime": z.lazy(() => Network.TimeSinceEpoch).optional() }).passthrough(), "Emulation.setVirtualTimePolicy.params", "commandParams", { method: "Emulation.setVirtualTimePolicy" }); export const SetVirtualTimePolicyResult = withCdpMeta(z.object({ "virtualTimeTicksBase": z.number() }).passthrough(), "Emulation.setVirtualTimePolicy.result", "commandResult", { method: "Emulation.setVirtualTimePolicy" }); +export const SetVirtualTimePolicyCommand = withCdpCommand("Emulation.setVirtualTimePolicy", SetVirtualTimePolicyParams, SetVirtualTimePolicyResult); export const SetLocaleOverrideParams = withCdpMeta(z.object({ "locale": z.string().optional() }).passthrough(), "Emulation.setLocaleOverride.params", "commandParams", { method: "Emulation.setLocaleOverride" }); export const SetLocaleOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setLocaleOverride.result", "commandResult", { method: "Emulation.setLocaleOverride" }); +export const SetLocaleOverrideCommand = withCdpCommand("Emulation.setLocaleOverride", SetLocaleOverrideParams, SetLocaleOverrideResult); export const SetTimezoneOverrideParams = withCdpMeta(z.object({ "timezoneId": z.string() }).passthrough(), "Emulation.setTimezoneOverride.params", "commandParams", { method: "Emulation.setTimezoneOverride" }); export const SetTimezoneOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setTimezoneOverride.result", "commandResult", { method: "Emulation.setTimezoneOverride" }); +export const SetTimezoneOverrideCommand = withCdpCommand("Emulation.setTimezoneOverride", SetTimezoneOverrideParams, SetTimezoneOverrideResult); export const SetVisibleSizeParams = withCdpMeta(z.object({ "width": z.number().int(), "height": z.number().int() }).passthrough(), "Emulation.setVisibleSize.params", "commandParams", { method: "Emulation.setVisibleSize" }); export const SetVisibleSizeResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setVisibleSize.result", "commandResult", { method: "Emulation.setVisibleSize" }); +export const SetVisibleSizeCommand = withCdpCommand("Emulation.setVisibleSize", SetVisibleSizeParams, SetVisibleSizeResult); export const SetDisabledImageTypesParams = withCdpMeta(z.object({ "imageTypes": z.array(z.lazy(() => DisabledImageType)) }).passthrough(), "Emulation.setDisabledImageTypes.params", "commandParams", { method: "Emulation.setDisabledImageTypes" }); export const SetDisabledImageTypesResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDisabledImageTypes.result", "commandResult", { method: "Emulation.setDisabledImageTypes" }); +export const SetDisabledImageTypesCommand = withCdpCommand("Emulation.setDisabledImageTypes", SetDisabledImageTypesParams, SetDisabledImageTypesResult); export const SetDataSaverOverrideParams = withCdpMeta(z.object({ "dataSaverEnabled": z.boolean().optional() }).passthrough(), "Emulation.setDataSaverOverride.params", "commandParams", { method: "Emulation.setDataSaverOverride" }); export const SetDataSaverOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setDataSaverOverride.result", "commandResult", { method: "Emulation.setDataSaverOverride" }); +export const SetDataSaverOverrideCommand = withCdpCommand("Emulation.setDataSaverOverride", SetDataSaverOverrideParams, SetDataSaverOverrideResult); export const SetHardwareConcurrencyOverrideParams = withCdpMeta(z.object({ "hardwareConcurrency": z.number().int() }).passthrough(), "Emulation.setHardwareConcurrencyOverride.params", "commandParams", { method: "Emulation.setHardwareConcurrencyOverride" }); export const SetHardwareConcurrencyOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setHardwareConcurrencyOverride.result", "commandResult", { method: "Emulation.setHardwareConcurrencyOverride" }); +export const SetHardwareConcurrencyOverrideCommand = withCdpCommand("Emulation.setHardwareConcurrencyOverride", SetHardwareConcurrencyOverrideParams, SetHardwareConcurrencyOverrideResult); export const SetUserAgentOverrideParams = withCdpMeta(z.object({ "userAgent": z.string(), "acceptLanguage": z.string().optional(), "platform": z.string().optional(), "userAgentMetadata": z.lazy(() => UserAgentMetadata).optional() }).passthrough(), "Emulation.setUserAgentOverride.params", "commandParams", { method: "Emulation.setUserAgentOverride" }); export const SetUserAgentOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setUserAgentOverride.result", "commandResult", { method: "Emulation.setUserAgentOverride" }); +export const SetUserAgentOverrideCommand = withCdpCommand("Emulation.setUserAgentOverride", SetUserAgentOverrideParams, SetUserAgentOverrideResult); export const SetAutomationOverrideParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Emulation.setAutomationOverride.params", "commandParams", { method: "Emulation.setAutomationOverride" }); export const SetAutomationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setAutomationOverride.result", "commandResult", { method: "Emulation.setAutomationOverride" }); +export const SetAutomationOverrideCommand = withCdpCommand("Emulation.setAutomationOverride", SetAutomationOverrideParams, SetAutomationOverrideResult); export const SetSmallViewportHeightDifferenceOverrideParams = withCdpMeta(z.object({ "difference": z.number().int() }).passthrough(), "Emulation.setSmallViewportHeightDifferenceOverride.params", "commandParams", { method: "Emulation.setSmallViewportHeightDifferenceOverride" }); export const SetSmallViewportHeightDifferenceOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setSmallViewportHeightDifferenceOverride.result", "commandResult", { method: "Emulation.setSmallViewportHeightDifferenceOverride" }); +export const SetSmallViewportHeightDifferenceOverrideCommand = withCdpCommand("Emulation.setSmallViewportHeightDifferenceOverride", SetSmallViewportHeightDifferenceOverrideParams, SetSmallViewportHeightDifferenceOverrideResult); export const GetScreenInfosParams = withCdpMeta(z.object({ }).passthrough(), "Emulation.getScreenInfos.params", "commandParams", { method: "Emulation.getScreenInfos" }); export const GetScreenInfosResult = withCdpMeta(z.object({ "screenInfos": z.array(z.lazy(() => ScreenInfo)) }).passthrough(), "Emulation.getScreenInfos.result", "commandResult", { method: "Emulation.getScreenInfos" }); +export const GetScreenInfosCommand = withCdpCommand("Emulation.getScreenInfos", GetScreenInfosParams, GetScreenInfosResult); export const AddScreenParams = withCdpMeta(z.object({ "left": z.number().int(), "top": z.number().int(), "width": z.number().int(), "height": z.number().int(), "workAreaInsets": z.lazy(() => WorkAreaInsets).optional(), "devicePixelRatio": z.number().optional(), "rotation": z.number().int().optional(), "colorDepth": z.number().int().optional(), "label": z.string().optional(), "isInternal": z.boolean().optional() }).passthrough(), "Emulation.addScreen.params", "commandParams", { method: "Emulation.addScreen" }); export const AddScreenResult = withCdpMeta(z.object({ "screenInfo": z.lazy(() => ScreenInfo) }).passthrough(), "Emulation.addScreen.result", "commandResult", { method: "Emulation.addScreen" }); +export const AddScreenCommand = withCdpCommand("Emulation.addScreen", AddScreenParams, AddScreenResult); export const UpdateScreenParams = withCdpMeta(z.object({ "screenId": z.lazy(() => ScreenId), "left": z.number().int().optional(), "top": z.number().int().optional(), "width": z.number().int().optional(), "height": z.number().int().optional(), "workAreaInsets": z.lazy(() => WorkAreaInsets).optional(), "devicePixelRatio": z.number().optional(), "rotation": z.number().int().optional(), "colorDepth": z.number().int().optional(), "label": z.string().optional(), "isInternal": z.boolean().optional() }).passthrough(), "Emulation.updateScreen.params", "commandParams", { method: "Emulation.updateScreen" }); export const UpdateScreenResult = withCdpMeta(z.object({ "screenInfo": z.lazy(() => ScreenInfo) }).passthrough(), "Emulation.updateScreen.result", "commandResult", { method: "Emulation.updateScreen" }); +export const UpdateScreenCommand = withCdpCommand("Emulation.updateScreen", UpdateScreenParams, UpdateScreenResult); export const RemoveScreenParams = withCdpMeta(z.object({ "screenId": z.lazy(() => ScreenId) }).passthrough(), "Emulation.removeScreen.params", "commandParams", { method: "Emulation.removeScreen" }); export const RemoveScreenResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.removeScreen.result", "commandResult", { method: "Emulation.removeScreen" }); +export const RemoveScreenCommand = withCdpCommand("Emulation.removeScreen", RemoveScreenParams, RemoveScreenResult); export const SetPrimaryScreenParams = withCdpMeta(z.object({ "screenId": z.lazy(() => ScreenId) }).passthrough(), "Emulation.setPrimaryScreen.params", "commandParams", { method: "Emulation.setPrimaryScreen" }); export const SetPrimaryScreenResult = withCdpMeta(z.object({ }).passthrough(), "Emulation.setPrimaryScreen.result", "commandResult", { method: "Emulation.setPrimaryScreen" }); +export const SetPrimaryScreenCommand = withCdpCommand("Emulation.setPrimaryScreen", SetPrimaryScreenParams, SetPrimaryScreenResult); export const VirtualTimeBudgetExpiredEvent = withCdpMeta(z.object({ }).passthrough(), "Emulation.virtualTimeBudgetExpired", "event", { phase: "event" }); export const ScreenOrientationLockChangedEvent = withCdpMeta(z.object({ "locked": z.boolean(), "orientation": z.lazy(() => ScreenOrientation).optional() }).passthrough(), "Emulation.screenOrientationLockChanged", "event", { phase: "event" }); @@ -248,54 +296,54 @@ export const zod = { ScreenOrientationLockChangedEvent: ScreenOrientationLockChangedEvent, } as const; export const commands = { - "Emulation.canEmulate": { params: CanEmulateParams, result: CanEmulateResult }, - "Emulation.clearDeviceMetricsOverride": { params: ClearDeviceMetricsOverrideParams, result: ClearDeviceMetricsOverrideResult }, - "Emulation.clearGeolocationOverride": { params: ClearGeolocationOverrideParams, result: ClearGeolocationOverrideResult }, - "Emulation.resetPageScaleFactor": { params: ResetPageScaleFactorParams, result: ResetPageScaleFactorResult }, - "Emulation.setFocusEmulationEnabled": { params: SetFocusEmulationEnabledParams, result: SetFocusEmulationEnabledResult }, - "Emulation.setAutoDarkModeOverride": { params: SetAutoDarkModeOverrideParams, result: SetAutoDarkModeOverrideResult }, - "Emulation.setCPUThrottlingRate": { params: SetCPUThrottlingRateParams, result: SetCPUThrottlingRateResult }, - "Emulation.setDefaultBackgroundColorOverride": { params: SetDefaultBackgroundColorOverrideParams, result: SetDefaultBackgroundColorOverrideResult }, - "Emulation.setSafeAreaInsetsOverride": { params: SetSafeAreaInsetsOverrideParams, result: SetSafeAreaInsetsOverrideResult }, - "Emulation.setDeviceMetricsOverride": { params: SetDeviceMetricsOverrideParams, result: SetDeviceMetricsOverrideResult }, - "Emulation.setDevicePostureOverride": { params: SetDevicePostureOverrideParams, result: SetDevicePostureOverrideResult }, - "Emulation.clearDevicePostureOverride": { params: ClearDevicePostureOverrideParams, result: ClearDevicePostureOverrideResult }, - "Emulation.setDisplayFeaturesOverride": { params: SetDisplayFeaturesOverrideParams, result: SetDisplayFeaturesOverrideResult }, - "Emulation.clearDisplayFeaturesOverride": { params: ClearDisplayFeaturesOverrideParams, result: ClearDisplayFeaturesOverrideResult }, - "Emulation.setScrollbarsHidden": { params: SetScrollbarsHiddenParams, result: SetScrollbarsHiddenResult }, - "Emulation.setDocumentCookieDisabled": { params: SetDocumentCookieDisabledParams, result: SetDocumentCookieDisabledResult }, - "Emulation.setEmitTouchEventsForMouse": { params: SetEmitTouchEventsForMouseParams, result: SetEmitTouchEventsForMouseResult }, - "Emulation.setEmulatedMedia": { params: SetEmulatedMediaParams, result: SetEmulatedMediaResult }, - "Emulation.setEmulatedVisionDeficiency": { params: SetEmulatedVisionDeficiencyParams, result: SetEmulatedVisionDeficiencyResult }, - "Emulation.setEmulatedOSTextScale": { params: SetEmulatedOSTextScaleParams, result: SetEmulatedOSTextScaleResult }, - "Emulation.setGeolocationOverride": { params: SetGeolocationOverrideParams, result: SetGeolocationOverrideResult }, - "Emulation.getOverriddenSensorInformation": { params: GetOverriddenSensorInformationParams, result: GetOverriddenSensorInformationResult }, - "Emulation.setSensorOverrideEnabled": { params: SetSensorOverrideEnabledParams, result: SetSensorOverrideEnabledResult }, - "Emulation.setSensorOverrideReadings": { params: SetSensorOverrideReadingsParams, result: SetSensorOverrideReadingsResult }, - "Emulation.setPressureSourceOverrideEnabled": { params: SetPressureSourceOverrideEnabledParams, result: SetPressureSourceOverrideEnabledResult }, - "Emulation.setPressureStateOverride": { params: SetPressureStateOverrideParams, result: SetPressureStateOverrideResult }, - "Emulation.setPressureDataOverride": { params: SetPressureDataOverrideParams, result: SetPressureDataOverrideResult }, - "Emulation.setIdleOverride": { params: SetIdleOverrideParams, result: SetIdleOverrideResult }, - "Emulation.clearIdleOverride": { params: ClearIdleOverrideParams, result: ClearIdleOverrideResult }, - "Emulation.setNavigatorOverrides": { params: SetNavigatorOverridesParams, result: SetNavigatorOverridesResult }, - "Emulation.setPageScaleFactor": { params: SetPageScaleFactorParams, result: SetPageScaleFactorResult }, - "Emulation.setScriptExecutionDisabled": { params: SetScriptExecutionDisabledParams, result: SetScriptExecutionDisabledResult }, - "Emulation.setTouchEmulationEnabled": { params: SetTouchEmulationEnabledParams, result: SetTouchEmulationEnabledResult }, - "Emulation.setVirtualTimePolicy": { params: SetVirtualTimePolicyParams, result: SetVirtualTimePolicyResult }, - "Emulation.setLocaleOverride": { params: SetLocaleOverrideParams, result: SetLocaleOverrideResult }, - "Emulation.setTimezoneOverride": { params: SetTimezoneOverrideParams, result: SetTimezoneOverrideResult }, - "Emulation.setVisibleSize": { params: SetVisibleSizeParams, result: SetVisibleSizeResult }, - "Emulation.setDisabledImageTypes": { params: SetDisabledImageTypesParams, result: SetDisabledImageTypesResult }, - "Emulation.setDataSaverOverride": { params: SetDataSaverOverrideParams, result: SetDataSaverOverrideResult }, - "Emulation.setHardwareConcurrencyOverride": { params: SetHardwareConcurrencyOverrideParams, result: SetHardwareConcurrencyOverrideResult }, - "Emulation.setUserAgentOverride": { params: SetUserAgentOverrideParams, result: SetUserAgentOverrideResult }, - "Emulation.setAutomationOverride": { params: SetAutomationOverrideParams, result: SetAutomationOverrideResult }, - "Emulation.setSmallViewportHeightDifferenceOverride": { params: SetSmallViewportHeightDifferenceOverrideParams, result: SetSmallViewportHeightDifferenceOverrideResult }, - "Emulation.getScreenInfos": { params: GetScreenInfosParams, result: GetScreenInfosResult }, - "Emulation.addScreen": { params: AddScreenParams, result: AddScreenResult }, - "Emulation.updateScreen": { params: UpdateScreenParams, result: UpdateScreenResult }, - "Emulation.removeScreen": { params: RemoveScreenParams, result: RemoveScreenResult }, - "Emulation.setPrimaryScreen": { params: SetPrimaryScreenParams, result: SetPrimaryScreenResult }, + "Emulation.canEmulate": CanEmulateCommand, + "Emulation.clearDeviceMetricsOverride": ClearDeviceMetricsOverrideCommand, + "Emulation.clearGeolocationOverride": ClearGeolocationOverrideCommand, + "Emulation.resetPageScaleFactor": ResetPageScaleFactorCommand, + "Emulation.setFocusEmulationEnabled": SetFocusEmulationEnabledCommand, + "Emulation.setAutoDarkModeOverride": SetAutoDarkModeOverrideCommand, + "Emulation.setCPUThrottlingRate": SetCPUThrottlingRateCommand, + "Emulation.setDefaultBackgroundColorOverride": SetDefaultBackgroundColorOverrideCommand, + "Emulation.setSafeAreaInsetsOverride": SetSafeAreaInsetsOverrideCommand, + "Emulation.setDeviceMetricsOverride": SetDeviceMetricsOverrideCommand, + "Emulation.setDevicePostureOverride": SetDevicePostureOverrideCommand, + "Emulation.clearDevicePostureOverride": ClearDevicePostureOverrideCommand, + "Emulation.setDisplayFeaturesOverride": SetDisplayFeaturesOverrideCommand, + "Emulation.clearDisplayFeaturesOverride": ClearDisplayFeaturesOverrideCommand, + "Emulation.setScrollbarsHidden": SetScrollbarsHiddenCommand, + "Emulation.setDocumentCookieDisabled": SetDocumentCookieDisabledCommand, + "Emulation.setEmitTouchEventsForMouse": SetEmitTouchEventsForMouseCommand, + "Emulation.setEmulatedMedia": SetEmulatedMediaCommand, + "Emulation.setEmulatedVisionDeficiency": SetEmulatedVisionDeficiencyCommand, + "Emulation.setEmulatedOSTextScale": SetEmulatedOSTextScaleCommand, + "Emulation.setGeolocationOverride": SetGeolocationOverrideCommand, + "Emulation.getOverriddenSensorInformation": GetOverriddenSensorInformationCommand, + "Emulation.setSensorOverrideEnabled": SetSensorOverrideEnabledCommand, + "Emulation.setSensorOverrideReadings": SetSensorOverrideReadingsCommand, + "Emulation.setPressureSourceOverrideEnabled": SetPressureSourceOverrideEnabledCommand, + "Emulation.setPressureStateOverride": SetPressureStateOverrideCommand, + "Emulation.setPressureDataOverride": SetPressureDataOverrideCommand, + "Emulation.setIdleOverride": SetIdleOverrideCommand, + "Emulation.clearIdleOverride": ClearIdleOverrideCommand, + "Emulation.setNavigatorOverrides": SetNavigatorOverridesCommand, + "Emulation.setPageScaleFactor": SetPageScaleFactorCommand, + "Emulation.setScriptExecutionDisabled": SetScriptExecutionDisabledCommand, + "Emulation.setTouchEmulationEnabled": SetTouchEmulationEnabledCommand, + "Emulation.setVirtualTimePolicy": SetVirtualTimePolicyCommand, + "Emulation.setLocaleOverride": SetLocaleOverrideCommand, + "Emulation.setTimezoneOverride": SetTimezoneOverrideCommand, + "Emulation.setVisibleSize": SetVisibleSizeCommand, + "Emulation.setDisabledImageTypes": SetDisabledImageTypesCommand, + "Emulation.setDataSaverOverride": SetDataSaverOverrideCommand, + "Emulation.setHardwareConcurrencyOverride": SetHardwareConcurrencyOverrideCommand, + "Emulation.setUserAgentOverride": SetUserAgentOverrideCommand, + "Emulation.setAutomationOverride": SetAutomationOverrideCommand, + "Emulation.setSmallViewportHeightDifferenceOverride": SetSmallViewportHeightDifferenceOverrideCommand, + "Emulation.getScreenInfos": GetScreenInfosCommand, + "Emulation.addScreen": AddScreenCommand, + "Emulation.updateScreen": UpdateScreenCommand, + "Emulation.removeScreen": RemoveScreenCommand, + "Emulation.setPrimaryScreen": SetPrimaryScreenCommand, } as const; export const events = { "Emulation.virtualTimeBudgetExpired": VirtualTimeBudgetExpiredEvent, diff --git a/js/src/types/generated/zod/EventBreakpoints.ts b/js/src/types/generated/zod/EventBreakpoints.ts index 6f9da151..88f03f05 100644 --- a/js/src/types/generated/zod/EventBreakpoints.ts +++ b/js/src/types/generated/zod/EventBreakpoints.ts @@ -1,14 +1,17 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const SetInstrumentationBreakpointParams = withCdpMeta(z.object({ "eventName": z.string() }).passthrough(), "EventBreakpoints.setInstrumentationBreakpoint.params", "commandParams", { method: "EventBreakpoints.setInstrumentationBreakpoint" }); export const SetInstrumentationBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "EventBreakpoints.setInstrumentationBreakpoint.result", "commandResult", { method: "EventBreakpoints.setInstrumentationBreakpoint" }); +export const SetInstrumentationBreakpointCommand = withCdpCommand("EventBreakpoints.setInstrumentationBreakpoint", SetInstrumentationBreakpointParams, SetInstrumentationBreakpointResult); export const RemoveInstrumentationBreakpointParams = withCdpMeta(z.object({ "eventName": z.string() }).passthrough(), "EventBreakpoints.removeInstrumentationBreakpoint.params", "commandParams", { method: "EventBreakpoints.removeInstrumentationBreakpoint" }); export const RemoveInstrumentationBreakpointResult = withCdpMeta(z.object({ }).passthrough(), "EventBreakpoints.removeInstrumentationBreakpoint.result", "commandResult", { method: "EventBreakpoints.removeInstrumentationBreakpoint" }); +export const RemoveInstrumentationBreakpointCommand = withCdpCommand("EventBreakpoints.removeInstrumentationBreakpoint", RemoveInstrumentationBreakpointParams, RemoveInstrumentationBreakpointResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "EventBreakpoints.disable.params", "commandParams", { method: "EventBreakpoints.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "EventBreakpoints.disable.result", "commandResult", { method: "EventBreakpoints.disable" }); +export const DisableCommand = withCdpCommand("EventBreakpoints.disable", DisableParams, DisableResult); export const zod = { SetInstrumentationBreakpointParams: SetInstrumentationBreakpointParams, @@ -19,9 +22,9 @@ export const zod = { DisableResult: DisableResult, } as const; export const commands = { - "EventBreakpoints.setInstrumentationBreakpoint": { params: SetInstrumentationBreakpointParams, result: SetInstrumentationBreakpointResult }, - "EventBreakpoints.removeInstrumentationBreakpoint": { params: RemoveInstrumentationBreakpointParams, result: RemoveInstrumentationBreakpointResult }, - "EventBreakpoints.disable": { params: DisableParams, result: DisableResult }, + "EventBreakpoints.setInstrumentationBreakpoint": SetInstrumentationBreakpointCommand, + "EventBreakpoints.removeInstrumentationBreakpoint": RemoveInstrumentationBreakpointCommand, + "EventBreakpoints.disable": DisableCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Extensions.ts b/js/src/types/generated/zod/Extensions.ts index 8cd2332e..95523206 100644 --- a/js/src/types/generated/zod/Extensions.ts +++ b/js/src/types/generated/zod/Extensions.ts @@ -1,26 +1,34 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const StorageArea = withCdpMeta(z.enum(["session", "local", "sync", "managed"]), "Extensions.StorageArea", "type"); export const ExtensionInfo = withCdpMeta(z.object({ "id": z.string(), "name": z.string(), "version": z.string(), "path": z.string(), "enabled": z.boolean() }).passthrough(), "Extensions.ExtensionInfo", "type"); export const TriggerActionParams = withCdpMeta(z.object({ "id": z.string(), "targetId": z.string() }).passthrough(), "Extensions.triggerAction.params", "commandParams", { method: "Extensions.triggerAction" }); export const TriggerActionResult = withCdpMeta(z.object({ }).passthrough(), "Extensions.triggerAction.result", "commandResult", { method: "Extensions.triggerAction" }); +export const TriggerActionCommand = withCdpCommand("Extensions.triggerAction", TriggerActionParams, TriggerActionResult); export const LoadUnpackedParams = withCdpMeta(z.object({ "path": z.string(), "enableInIncognito": z.boolean().optional() }).passthrough(), "Extensions.loadUnpacked.params", "commandParams", { method: "Extensions.loadUnpacked" }); export const LoadUnpackedResult = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Extensions.loadUnpacked.result", "commandResult", { method: "Extensions.loadUnpacked" }); +export const LoadUnpackedCommand = withCdpCommand("Extensions.loadUnpacked", LoadUnpackedParams, LoadUnpackedResult); export const GetExtensionsParams = withCdpMeta(z.object({ }).passthrough(), "Extensions.getExtensions.params", "commandParams", { method: "Extensions.getExtensions" }); export const GetExtensionsResult = withCdpMeta(z.object({ "extensions": z.array(z.lazy(() => ExtensionInfo)) }).passthrough(), "Extensions.getExtensions.result", "commandResult", { method: "Extensions.getExtensions" }); +export const GetExtensionsCommand = withCdpCommand("Extensions.getExtensions", GetExtensionsParams, GetExtensionsResult); export const UninstallParams = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Extensions.uninstall.params", "commandParams", { method: "Extensions.uninstall" }); export const UninstallResult = withCdpMeta(z.object({ }).passthrough(), "Extensions.uninstall.result", "commandResult", { method: "Extensions.uninstall" }); +export const UninstallCommand = withCdpCommand("Extensions.uninstall", UninstallParams, UninstallResult); export const GetStorageItemsParams = withCdpMeta(z.object({ "id": z.string(), "storageArea": z.lazy(() => StorageArea), "keys": z.array(z.string()).optional() }).passthrough(), "Extensions.getStorageItems.params", "commandParams", { method: "Extensions.getStorageItems" }); export const GetStorageItemsResult = withCdpMeta(z.object({ "data": z.record(z.string(), z.unknown()) }).passthrough(), "Extensions.getStorageItems.result", "commandResult", { method: "Extensions.getStorageItems" }); +export const GetStorageItemsCommand = withCdpCommand("Extensions.getStorageItems", GetStorageItemsParams, GetStorageItemsResult); export const RemoveStorageItemsParams = withCdpMeta(z.object({ "id": z.string(), "storageArea": z.lazy(() => StorageArea), "keys": z.array(z.string()) }).passthrough(), "Extensions.removeStorageItems.params", "commandParams", { method: "Extensions.removeStorageItems" }); export const RemoveStorageItemsResult = withCdpMeta(z.object({ }).passthrough(), "Extensions.removeStorageItems.result", "commandResult", { method: "Extensions.removeStorageItems" }); +export const RemoveStorageItemsCommand = withCdpCommand("Extensions.removeStorageItems", RemoveStorageItemsParams, RemoveStorageItemsResult); export const ClearStorageItemsParams = withCdpMeta(z.object({ "id": z.string(), "storageArea": z.lazy(() => StorageArea) }).passthrough(), "Extensions.clearStorageItems.params", "commandParams", { method: "Extensions.clearStorageItems" }); export const ClearStorageItemsResult = withCdpMeta(z.object({ }).passthrough(), "Extensions.clearStorageItems.result", "commandResult", { method: "Extensions.clearStorageItems" }); +export const ClearStorageItemsCommand = withCdpCommand("Extensions.clearStorageItems", ClearStorageItemsParams, ClearStorageItemsResult); export const SetStorageItemsParams = withCdpMeta(z.object({ "id": z.string(), "storageArea": z.lazy(() => StorageArea), "values": z.record(z.string(), z.unknown()) }).passthrough(), "Extensions.setStorageItems.params", "commandParams", { method: "Extensions.setStorageItems" }); export const SetStorageItemsResult = withCdpMeta(z.object({ }).passthrough(), "Extensions.setStorageItems.result", "commandResult", { method: "Extensions.setStorageItems" }); +export const SetStorageItemsCommand = withCdpCommand("Extensions.setStorageItems", SetStorageItemsParams, SetStorageItemsResult); export const zod = { StorageArea: StorageArea, @@ -43,14 +51,14 @@ export const zod = { SetStorageItemsResult: SetStorageItemsResult, } as const; export const commands = { - "Extensions.triggerAction": { params: TriggerActionParams, result: TriggerActionResult }, - "Extensions.loadUnpacked": { params: LoadUnpackedParams, result: LoadUnpackedResult }, - "Extensions.getExtensions": { params: GetExtensionsParams, result: GetExtensionsResult }, - "Extensions.uninstall": { params: UninstallParams, result: UninstallResult }, - "Extensions.getStorageItems": { params: GetStorageItemsParams, result: GetStorageItemsResult }, - "Extensions.removeStorageItems": { params: RemoveStorageItemsParams, result: RemoveStorageItemsResult }, - "Extensions.clearStorageItems": { params: ClearStorageItemsParams, result: ClearStorageItemsResult }, - "Extensions.setStorageItems": { params: SetStorageItemsParams, result: SetStorageItemsResult }, + "Extensions.triggerAction": TriggerActionCommand, + "Extensions.loadUnpacked": LoadUnpackedCommand, + "Extensions.getExtensions": GetExtensionsCommand, + "Extensions.uninstall": UninstallCommand, + "Extensions.getStorageItems": GetStorageItemsCommand, + "Extensions.removeStorageItems": RemoveStorageItemsCommand, + "Extensions.clearStorageItems": ClearStorageItemsCommand, + "Extensions.setStorageItems": SetStorageItemsCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/FedCm.ts b/js/src/types/generated/zod/FedCm.ts index a39f5496..daa5ecf8 100644 --- a/js/src/types/generated/zod/FedCm.ts +++ b/js/src/types/generated/zod/FedCm.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const LoginState = withCdpMeta(z.enum(["SignIn", "SignUp"]), "FedCm.LoginState", "type"); export const DialogType = withCdpMeta(z.enum(["AccountChooser", "AutoReauthn", "ConfirmIdpLogin", "Error"]), "FedCm.DialogType", "type"); @@ -10,18 +10,25 @@ export const AccountUrlType = withCdpMeta(z.enum(["TermsOfService", "PrivacyPoli export const Account = withCdpMeta(z.object({ "accountId": z.string(), "email": z.string(), "name": z.string(), "givenName": z.string(), "pictureUrl": z.string(), "idpConfigUrl": z.string(), "idpLoginUrl": z.string(), "loginState": z.lazy(() => LoginState), "termsOfServiceUrl": z.string().optional(), "privacyPolicyUrl": z.string().optional() }).passthrough(), "FedCm.Account", "type"); export const EnableParams = withCdpMeta(z.object({ "disableRejectionDelay": z.boolean().optional() }).passthrough(), "FedCm.enable.params", "commandParams", { method: "FedCm.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.enable.result", "commandResult", { method: "FedCm.enable" }); +export const EnableCommand = withCdpCommand("FedCm.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "FedCm.disable.params", "commandParams", { method: "FedCm.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.disable.result", "commandResult", { method: "FedCm.disable" }); +export const DisableCommand = withCdpCommand("FedCm.disable", DisableParams, DisableResult); export const SelectAccountParams = withCdpMeta(z.object({ "dialogId": z.string(), "accountIndex": z.number().int() }).passthrough(), "FedCm.selectAccount.params", "commandParams", { method: "FedCm.selectAccount" }); export const SelectAccountResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.selectAccount.result", "commandResult", { method: "FedCm.selectAccount" }); +export const SelectAccountCommand = withCdpCommand("FedCm.selectAccount", SelectAccountParams, SelectAccountResult); export const ClickDialogButtonParams = withCdpMeta(z.object({ "dialogId": z.string(), "dialogButton": z.lazy(() => DialogButton) }).passthrough(), "FedCm.clickDialogButton.params", "commandParams", { method: "FedCm.clickDialogButton" }); export const ClickDialogButtonResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.clickDialogButton.result", "commandResult", { method: "FedCm.clickDialogButton" }); +export const ClickDialogButtonCommand = withCdpCommand("FedCm.clickDialogButton", ClickDialogButtonParams, ClickDialogButtonResult); export const OpenUrlParams = withCdpMeta(z.object({ "dialogId": z.string(), "accountIndex": z.number().int(), "accountUrlType": z.lazy(() => AccountUrlType) }).passthrough(), "FedCm.openUrl.params", "commandParams", { method: "FedCm.openUrl" }); export const OpenUrlResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.openUrl.result", "commandResult", { method: "FedCm.openUrl" }); +export const OpenUrlCommand = withCdpCommand("FedCm.openUrl", OpenUrlParams, OpenUrlResult); export const DismissDialogParams = withCdpMeta(z.object({ "dialogId": z.string(), "triggerCooldown": z.boolean().optional() }).passthrough(), "FedCm.dismissDialog.params", "commandParams", { method: "FedCm.dismissDialog" }); export const DismissDialogResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.dismissDialog.result", "commandResult", { method: "FedCm.dismissDialog" }); +export const DismissDialogCommand = withCdpCommand("FedCm.dismissDialog", DismissDialogParams, DismissDialogResult); export const ResetCooldownParams = withCdpMeta(z.object({ }).passthrough(), "FedCm.resetCooldown.params", "commandParams", { method: "FedCm.resetCooldown" }); export const ResetCooldownResult = withCdpMeta(z.object({ }).passthrough(), "FedCm.resetCooldown.result", "commandResult", { method: "FedCm.resetCooldown" }); +export const ResetCooldownCommand = withCdpCommand("FedCm.resetCooldown", ResetCooldownParams, ResetCooldownResult); export const DialogShownEvent = withCdpMeta(z.object({ "dialogId": z.string(), "dialogType": z.lazy(() => DialogType), "accounts": z.array(z.lazy(() => Account)), "title": z.string(), "subtitle": z.string().optional() }).passthrough(), "FedCm.dialogShown", "event", { phase: "event" }); export const DialogClosedEvent = withCdpMeta(z.object({ "dialogId": z.string() }).passthrough(), "FedCm.dialogClosed", "event", { phase: "event" }); @@ -49,13 +56,13 @@ export const zod = { DialogClosedEvent: DialogClosedEvent, } as const; export const commands = { - "FedCm.enable": { params: EnableParams, result: EnableResult }, - "FedCm.disable": { params: DisableParams, result: DisableResult }, - "FedCm.selectAccount": { params: SelectAccountParams, result: SelectAccountResult }, - "FedCm.clickDialogButton": { params: ClickDialogButtonParams, result: ClickDialogButtonResult }, - "FedCm.openUrl": { params: OpenUrlParams, result: OpenUrlResult }, - "FedCm.dismissDialog": { params: DismissDialogParams, result: DismissDialogResult }, - "FedCm.resetCooldown": { params: ResetCooldownParams, result: ResetCooldownResult }, + "FedCm.enable": EnableCommand, + "FedCm.disable": DisableCommand, + "FedCm.selectAccount": SelectAccountCommand, + "FedCm.clickDialogButton": ClickDialogButtonCommand, + "FedCm.openUrl": OpenUrlCommand, + "FedCm.dismissDialog": DismissDialogCommand, + "FedCm.resetCooldown": ResetCooldownCommand, } as const; export const events = { "FedCm.dialogShown": DialogShownEvent, diff --git a/js/src/types/generated/zod/Fetch.ts b/js/src/types/generated/zod/Fetch.ts index f87b181b..29ee458d 100644 --- a/js/src/types/generated/zod/Fetch.ts +++ b/js/src/types/generated/zod/Fetch.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as IO from "./IO.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -14,22 +14,31 @@ export const AuthChallenge = withCdpMeta(z.object({ "source": z.enum(["Server", export const AuthChallengeResponse = withCdpMeta(z.object({ "response": z.enum(["Default", "CancelAuth", "ProvideCredentials"]), "username": z.string().optional(), "password": z.string().optional() }).passthrough(), "Fetch.AuthChallengeResponse", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Fetch.disable.params", "commandParams", { method: "Fetch.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.disable.result", "commandResult", { method: "Fetch.disable" }); +export const DisableCommand = withCdpCommand("Fetch.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ "patterns": z.array(z.lazy(() => RequestPattern)).optional(), "handleAuthRequests": z.boolean().optional() }).passthrough(), "Fetch.enable.params", "commandParams", { method: "Fetch.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.enable.result", "commandResult", { method: "Fetch.enable" }); +export const EnableCommand = withCdpCommand("Fetch.enable", EnableParams, EnableResult); export const FailRequestParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "errorReason": z.lazy(() => Network.ErrorReason) }).passthrough(), "Fetch.failRequest.params", "commandParams", { method: "Fetch.failRequest" }); export const FailRequestResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.failRequest.result", "commandResult", { method: "Fetch.failRequest" }); +export const FailRequestCommand = withCdpCommand("Fetch.failRequest", FailRequestParams, FailRequestResult); export const FulfillRequestParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "responseCode": z.number().int(), "responseHeaders": z.array(z.lazy(() => HeaderEntry)).optional(), "binaryResponseHeaders": z.string().optional(), "body": z.string().optional(), "responsePhrase": z.string().optional() }).passthrough(), "Fetch.fulfillRequest.params", "commandParams", { method: "Fetch.fulfillRequest" }); export const FulfillRequestResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.fulfillRequest.result", "commandResult", { method: "Fetch.fulfillRequest" }); +export const FulfillRequestCommand = withCdpCommand("Fetch.fulfillRequest", FulfillRequestParams, FulfillRequestResult); export const ContinueRequestParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "url": z.string().optional(), "method": z.string().optional(), "postData": z.string().optional(), "headers": z.array(z.lazy(() => HeaderEntry)).optional(), "interceptResponse": z.boolean().optional() }).passthrough(), "Fetch.continueRequest.params", "commandParams", { method: "Fetch.continueRequest" }); export const ContinueRequestResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.continueRequest.result", "commandResult", { method: "Fetch.continueRequest" }); +export const ContinueRequestCommand = withCdpCommand("Fetch.continueRequest", ContinueRequestParams, ContinueRequestResult); export const ContinueWithAuthParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "authChallengeResponse": z.lazy(() => AuthChallengeResponse) }).passthrough(), "Fetch.continueWithAuth.params", "commandParams", { method: "Fetch.continueWithAuth" }); export const ContinueWithAuthResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.continueWithAuth.result", "commandResult", { method: "Fetch.continueWithAuth" }); +export const ContinueWithAuthCommand = withCdpCommand("Fetch.continueWithAuth", ContinueWithAuthParams, ContinueWithAuthResult); export const ContinueResponseParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "responseCode": z.number().int().optional(), "responsePhrase": z.string().optional(), "responseHeaders": z.array(z.lazy(() => HeaderEntry)).optional(), "binaryResponseHeaders": z.string().optional() }).passthrough(), "Fetch.continueResponse.params", "commandParams", { method: "Fetch.continueResponse" }); export const ContinueResponseResult = withCdpMeta(z.object({ }).passthrough(), "Fetch.continueResponse.result", "commandResult", { method: "Fetch.continueResponse" }); +export const ContinueResponseCommand = withCdpCommand("Fetch.continueResponse", ContinueResponseParams, ContinueResponseResult); export const GetResponseBodyParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Fetch.getResponseBody.params", "commandParams", { method: "Fetch.getResponseBody" }); export const GetResponseBodyResult = withCdpMeta(z.object({ "body": z.string(), "base64Encoded": z.boolean() }).passthrough(), "Fetch.getResponseBody.result", "commandResult", { method: "Fetch.getResponseBody" }); +export const GetResponseBodyCommand = withCdpCommand("Fetch.getResponseBody", GetResponseBodyParams, GetResponseBodyResult); export const TakeResponseBodyAsStreamParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Fetch.takeResponseBodyAsStream.params", "commandParams", { method: "Fetch.takeResponseBodyAsStream" }); export const TakeResponseBodyAsStreamResult = withCdpMeta(z.object({ "stream": z.lazy(() => IO.StreamHandle) }).passthrough(), "Fetch.takeResponseBodyAsStream.result", "commandResult", { method: "Fetch.takeResponseBodyAsStream" }); +export const TakeResponseBodyAsStreamCommand = withCdpCommand("Fetch.takeResponseBodyAsStream", TakeResponseBodyAsStreamParams, TakeResponseBodyAsStreamResult); export const RequestPausedEvent = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "request": z.lazy(() => Network.Request), "frameId": z.lazy(() => Page.FrameId), "resourceType": z.lazy(() => Network.ResourceType), "responseErrorReason": z.lazy(() => Network.ErrorReason).optional(), "responseStatusCode": z.number().int().optional(), "responseStatusText": z.string().optional(), "responseHeaders": z.array(z.lazy(() => HeaderEntry)).optional(), "networkId": z.lazy(() => Network.RequestId).optional(), "redirectedRequestId": z.lazy(() => RequestId).optional() }).passthrough(), "Fetch.requestPaused", "event", { phase: "event" }); export const AuthRequiredEvent = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "request": z.lazy(() => Network.Request), "frameId": z.lazy(() => Page.FrameId), "resourceType": z.lazy(() => Network.ResourceType), "authChallenge": z.lazy(() => AuthChallenge) }).passthrough(), "Fetch.authRequired", "event", { phase: "event" }); @@ -62,15 +71,15 @@ export const zod = { AuthRequiredEvent: AuthRequiredEvent, } as const; export const commands = { - "Fetch.disable": { params: DisableParams, result: DisableResult }, - "Fetch.enable": { params: EnableParams, result: EnableResult }, - "Fetch.failRequest": { params: FailRequestParams, result: FailRequestResult }, - "Fetch.fulfillRequest": { params: FulfillRequestParams, result: FulfillRequestResult }, - "Fetch.continueRequest": { params: ContinueRequestParams, result: ContinueRequestResult }, - "Fetch.continueWithAuth": { params: ContinueWithAuthParams, result: ContinueWithAuthResult }, - "Fetch.continueResponse": { params: ContinueResponseParams, result: ContinueResponseResult }, - "Fetch.getResponseBody": { params: GetResponseBodyParams, result: GetResponseBodyResult }, - "Fetch.takeResponseBodyAsStream": { params: TakeResponseBodyAsStreamParams, result: TakeResponseBodyAsStreamResult }, + "Fetch.disable": DisableCommand, + "Fetch.enable": EnableCommand, + "Fetch.failRequest": FailRequestCommand, + "Fetch.fulfillRequest": FulfillRequestCommand, + "Fetch.continueRequest": ContinueRequestCommand, + "Fetch.continueWithAuth": ContinueWithAuthCommand, + "Fetch.continueResponse": ContinueResponseCommand, + "Fetch.getResponseBody": GetResponseBodyCommand, + "Fetch.takeResponseBodyAsStream": TakeResponseBodyAsStreamCommand, } as const; export const events = { "Fetch.requestPaused": RequestPausedEvent, diff --git a/js/src/types/generated/zod/FileSystem.ts b/js/src/types/generated/zod/FileSystem.ts index 9e15cc4f..21403f58 100644 --- a/js/src/types/generated/zod/FileSystem.ts +++ b/js/src/types/generated/zod/FileSystem.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Network from "./Network.js"; import * as Storage from "./Storage.js"; @@ -10,6 +10,7 @@ export const Directory = withCdpMeta(z.object({ "name": z.string(), "nestedDirec export const BucketFileSystemLocator = withCdpMeta(z.object({ "storageKey": z.lazy(() => Storage.SerializedStorageKey), "bucketName": z.string().optional(), "pathComponents": z.array(z.string()) }).passthrough(), "FileSystem.BucketFileSystemLocator", "type"); export const GetDirectoryParams = withCdpMeta(z.object({ "bucketFileSystemLocator": z.lazy(() => BucketFileSystemLocator) }).passthrough(), "FileSystem.getDirectory.params", "commandParams", { method: "FileSystem.getDirectory" }); export const GetDirectoryResult = withCdpMeta(z.object({ "directory": z.lazy(() => Directory) }).passthrough(), "FileSystem.getDirectory.result", "commandResult", { method: "FileSystem.getDirectory" }); +export const GetDirectoryCommand = withCdpCommand("FileSystem.getDirectory", GetDirectoryParams, GetDirectoryResult); export const zod = { File: File, @@ -19,7 +20,7 @@ export const zod = { GetDirectoryResult: GetDirectoryResult, } as const; export const commands = { - "FileSystem.getDirectory": { params: GetDirectoryParams, result: GetDirectoryResult }, + "FileSystem.getDirectory": GetDirectoryCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/HeadlessExperimental.ts b/js/src/types/generated/zod/HeadlessExperimental.ts index e0f017b7..4fc8a992 100644 --- a/js/src/types/generated/zod/HeadlessExperimental.ts +++ b/js/src/types/generated/zod/HeadlessExperimental.ts @@ -1,15 +1,18 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const ScreenshotParams = withCdpMeta(z.object({ "format": z.enum(["jpeg", "png", "webp"]).optional(), "quality": z.number().int().optional(), "optimizeForSpeed": z.boolean().optional() }).passthrough(), "HeadlessExperimental.ScreenshotParams", "type"); export const BeginFrameParams = withCdpMeta(z.object({ "frameTimeTicks": z.number().optional(), "interval": z.number().optional(), "noDisplayUpdates": z.boolean().optional(), "screenshot": z.lazy(() => ScreenshotParams).optional() }).passthrough(), "HeadlessExperimental.beginFrame.params", "commandParams", { method: "HeadlessExperimental.beginFrame" }); export const BeginFrameResult = withCdpMeta(z.object({ "hasDamage": z.boolean(), "screenshotData": z.string().optional() }).passthrough(), "HeadlessExperimental.beginFrame.result", "commandResult", { method: "HeadlessExperimental.beginFrame" }); +export const BeginFrameCommand = withCdpCommand("HeadlessExperimental.beginFrame", BeginFrameParams, BeginFrameResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "HeadlessExperimental.disable.params", "commandParams", { method: "HeadlessExperimental.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "HeadlessExperimental.disable.result", "commandResult", { method: "HeadlessExperimental.disable" }); +export const DisableCommand = withCdpCommand("HeadlessExperimental.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "HeadlessExperimental.enable.params", "commandParams", { method: "HeadlessExperimental.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "HeadlessExperimental.enable.result", "commandResult", { method: "HeadlessExperimental.enable" }); +export const EnableCommand = withCdpCommand("HeadlessExperimental.enable", EnableParams, EnableResult); export const zod = { ScreenshotParams: ScreenshotParams, @@ -21,9 +24,9 @@ export const zod = { EnableResult: EnableResult, } as const; export const commands = { - "HeadlessExperimental.beginFrame": { params: BeginFrameParams, result: BeginFrameResult }, - "HeadlessExperimental.disable": { params: DisableParams, result: DisableResult }, - "HeadlessExperimental.enable": { params: EnableParams, result: EnableResult }, + "HeadlessExperimental.beginFrame": BeginFrameCommand, + "HeadlessExperimental.disable": DisableCommand, + "HeadlessExperimental.enable": EnableCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/HeapProfiler.ts b/js/src/types/generated/zod/HeapProfiler.ts index a88e0187..027ae99c 100644 --- a/js/src/types/generated/zod/HeapProfiler.ts +++ b/js/src/types/generated/zod/HeapProfiler.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Runtime from "./Runtime.js"; export const HeapSnapshotObjectId = withCdpMeta(z.string(), "HeapProfiler.HeapSnapshotObjectId", "type"); @@ -10,28 +10,40 @@ export const SamplingHeapProfileSample = withCdpMeta(z.object({ "size": z.number export const SamplingHeapProfile = withCdpMeta(z.object({ "head": z.lazy(() => SamplingHeapProfileNode), "samples": z.array(z.lazy(() => SamplingHeapProfileSample)) }).passthrough(), "HeapProfiler.SamplingHeapProfile", "type"); export const AddInspectedHeapObjectParams = withCdpMeta(z.object({ "heapObjectId": z.lazy(() => HeapSnapshotObjectId) }).passthrough(), "HeapProfiler.addInspectedHeapObject.params", "commandParams", { method: "HeapProfiler.addInspectedHeapObject" }); export const AddInspectedHeapObjectResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.addInspectedHeapObject.result", "commandResult", { method: "HeapProfiler.addInspectedHeapObject" }); +export const AddInspectedHeapObjectCommand = withCdpCommand("HeapProfiler.addInspectedHeapObject", AddInspectedHeapObjectParams, AddInspectedHeapObjectResult); export const CollectGarbageParams = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.collectGarbage.params", "commandParams", { method: "HeapProfiler.collectGarbage" }); export const CollectGarbageResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.collectGarbage.result", "commandResult", { method: "HeapProfiler.collectGarbage" }); +export const CollectGarbageCommand = withCdpCommand("HeapProfiler.collectGarbage", CollectGarbageParams, CollectGarbageResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.disable.params", "commandParams", { method: "HeapProfiler.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.disable.result", "commandResult", { method: "HeapProfiler.disable" }); +export const DisableCommand = withCdpCommand("HeapProfiler.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.enable.params", "commandParams", { method: "HeapProfiler.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.enable.result", "commandResult", { method: "HeapProfiler.enable" }); +export const EnableCommand = withCdpCommand("HeapProfiler.enable", EnableParams, EnableResult); export const GetHeapObjectIdParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId) }).passthrough(), "HeapProfiler.getHeapObjectId.params", "commandParams", { method: "HeapProfiler.getHeapObjectId" }); export const GetHeapObjectIdResult = withCdpMeta(z.object({ "heapSnapshotObjectId": z.lazy(() => HeapSnapshotObjectId) }).passthrough(), "HeapProfiler.getHeapObjectId.result", "commandResult", { method: "HeapProfiler.getHeapObjectId" }); +export const GetHeapObjectIdCommand = withCdpCommand("HeapProfiler.getHeapObjectId", GetHeapObjectIdParams, GetHeapObjectIdResult); export const GetObjectByHeapObjectIdParams = withCdpMeta(z.object({ "objectId": z.lazy(() => HeapSnapshotObjectId), "objectGroup": z.string().optional() }).passthrough(), "HeapProfiler.getObjectByHeapObjectId.params", "commandParams", { method: "HeapProfiler.getObjectByHeapObjectId" }); export const GetObjectByHeapObjectIdResult = withCdpMeta(z.object({ "result": z.lazy(() => Runtime.RemoteObject) }).passthrough(), "HeapProfiler.getObjectByHeapObjectId.result", "commandResult", { method: "HeapProfiler.getObjectByHeapObjectId" }); +export const GetObjectByHeapObjectIdCommand = withCdpCommand("HeapProfiler.getObjectByHeapObjectId", GetObjectByHeapObjectIdParams, GetObjectByHeapObjectIdResult); export const GetSamplingProfileParams = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.getSamplingProfile.params", "commandParams", { method: "HeapProfiler.getSamplingProfile" }); export const GetSamplingProfileResult = withCdpMeta(z.object({ "profile": z.lazy(() => SamplingHeapProfile) }).passthrough(), "HeapProfiler.getSamplingProfile.result", "commandResult", { method: "HeapProfiler.getSamplingProfile" }); +export const GetSamplingProfileCommand = withCdpCommand("HeapProfiler.getSamplingProfile", GetSamplingProfileParams, GetSamplingProfileResult); export const StartSamplingParams = withCdpMeta(z.object({ "samplingInterval": z.number().optional(), "stackDepth": z.number().optional(), "includeObjectsCollectedByMajorGC": z.boolean().optional(), "includeObjectsCollectedByMinorGC": z.boolean().optional() }).passthrough(), "HeapProfiler.startSampling.params", "commandParams", { method: "HeapProfiler.startSampling" }); export const StartSamplingResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.startSampling.result", "commandResult", { method: "HeapProfiler.startSampling" }); +export const StartSamplingCommand = withCdpCommand("HeapProfiler.startSampling", StartSamplingParams, StartSamplingResult); export const StartTrackingHeapObjectsParams = withCdpMeta(z.object({ "trackAllocations": z.boolean().optional() }).passthrough(), "HeapProfiler.startTrackingHeapObjects.params", "commandParams", { method: "HeapProfiler.startTrackingHeapObjects" }); export const StartTrackingHeapObjectsResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.startTrackingHeapObjects.result", "commandResult", { method: "HeapProfiler.startTrackingHeapObjects" }); +export const StartTrackingHeapObjectsCommand = withCdpCommand("HeapProfiler.startTrackingHeapObjects", StartTrackingHeapObjectsParams, StartTrackingHeapObjectsResult); export const StopSamplingParams = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.stopSampling.params", "commandParams", { method: "HeapProfiler.stopSampling" }); export const StopSamplingResult = withCdpMeta(z.object({ "profile": z.lazy(() => SamplingHeapProfile) }).passthrough(), "HeapProfiler.stopSampling.result", "commandResult", { method: "HeapProfiler.stopSampling" }); +export const StopSamplingCommand = withCdpCommand("HeapProfiler.stopSampling", StopSamplingParams, StopSamplingResult); export const StopTrackingHeapObjectsParams = withCdpMeta(z.object({ "reportProgress": z.boolean().optional(), "treatGlobalObjectsAsRoots": z.boolean().optional(), "captureNumericValue": z.boolean().optional(), "exposeInternals": z.boolean().optional() }).passthrough(), "HeapProfiler.stopTrackingHeapObjects.params", "commandParams", { method: "HeapProfiler.stopTrackingHeapObjects" }); export const StopTrackingHeapObjectsResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.stopTrackingHeapObjects.result", "commandResult", { method: "HeapProfiler.stopTrackingHeapObjects" }); +export const StopTrackingHeapObjectsCommand = withCdpCommand("HeapProfiler.stopTrackingHeapObjects", StopTrackingHeapObjectsParams, StopTrackingHeapObjectsResult); export const TakeHeapSnapshotParams = withCdpMeta(z.object({ "reportProgress": z.boolean().optional(), "treatGlobalObjectsAsRoots": z.boolean().optional(), "captureNumericValue": z.boolean().optional(), "exposeInternals": z.boolean().optional() }).passthrough(), "HeapProfiler.takeHeapSnapshot.params", "commandParams", { method: "HeapProfiler.takeHeapSnapshot" }); export const TakeHeapSnapshotResult = withCdpMeta(z.object({ }).passthrough(), "HeapProfiler.takeHeapSnapshot.result", "commandResult", { method: "HeapProfiler.takeHeapSnapshot" }); +export const TakeHeapSnapshotCommand = withCdpCommand("HeapProfiler.takeHeapSnapshot", TakeHeapSnapshotParams, TakeHeapSnapshotResult); export const AddHeapSnapshotChunkEvent = withCdpMeta(z.object({ "chunk": z.string() }).passthrough(), "HeapProfiler.addHeapSnapshotChunk", "event", { phase: "event" }); export const HeapStatsUpdateEvent = withCdpMeta(z.object({ "statsUpdate": z.array(z.number().int()) }).passthrough(), "HeapProfiler.heapStatsUpdate", "event", { phase: "event" }); export const LastSeenObjectIdEvent = withCdpMeta(z.object({ "lastSeenObjectId": z.number().int(), "timestamp": z.number() }).passthrough(), "HeapProfiler.lastSeenObjectId", "event", { phase: "event" }); @@ -74,18 +86,18 @@ export const zod = { ResetProfilesEvent: ResetProfilesEvent, } as const; export const commands = { - "HeapProfiler.addInspectedHeapObject": { params: AddInspectedHeapObjectParams, result: AddInspectedHeapObjectResult }, - "HeapProfiler.collectGarbage": { params: CollectGarbageParams, result: CollectGarbageResult }, - "HeapProfiler.disable": { params: DisableParams, result: DisableResult }, - "HeapProfiler.enable": { params: EnableParams, result: EnableResult }, - "HeapProfiler.getHeapObjectId": { params: GetHeapObjectIdParams, result: GetHeapObjectIdResult }, - "HeapProfiler.getObjectByHeapObjectId": { params: GetObjectByHeapObjectIdParams, result: GetObjectByHeapObjectIdResult }, - "HeapProfiler.getSamplingProfile": { params: GetSamplingProfileParams, result: GetSamplingProfileResult }, - "HeapProfiler.startSampling": { params: StartSamplingParams, result: StartSamplingResult }, - "HeapProfiler.startTrackingHeapObjects": { params: StartTrackingHeapObjectsParams, result: StartTrackingHeapObjectsResult }, - "HeapProfiler.stopSampling": { params: StopSamplingParams, result: StopSamplingResult }, - "HeapProfiler.stopTrackingHeapObjects": { params: StopTrackingHeapObjectsParams, result: StopTrackingHeapObjectsResult }, - "HeapProfiler.takeHeapSnapshot": { params: TakeHeapSnapshotParams, result: TakeHeapSnapshotResult }, + "HeapProfiler.addInspectedHeapObject": AddInspectedHeapObjectCommand, + "HeapProfiler.collectGarbage": CollectGarbageCommand, + "HeapProfiler.disable": DisableCommand, + "HeapProfiler.enable": EnableCommand, + "HeapProfiler.getHeapObjectId": GetHeapObjectIdCommand, + "HeapProfiler.getObjectByHeapObjectId": GetObjectByHeapObjectIdCommand, + "HeapProfiler.getSamplingProfile": GetSamplingProfileCommand, + "HeapProfiler.startSampling": StartSamplingCommand, + "HeapProfiler.startTrackingHeapObjects": StartTrackingHeapObjectsCommand, + "HeapProfiler.stopSampling": StopSamplingCommand, + "HeapProfiler.stopTrackingHeapObjects": StopTrackingHeapObjectsCommand, + "HeapProfiler.takeHeapSnapshot": TakeHeapSnapshotCommand, } as const; export const events = { "HeapProfiler.addHeapSnapshotChunk": AddHeapSnapshotChunkEvent, diff --git a/js/src/types/generated/zod/IO.ts b/js/src/types/generated/zod/IO.ts index 90b40e12..48b25edd 100644 --- a/js/src/types/generated/zod/IO.ts +++ b/js/src/types/generated/zod/IO.ts @@ -1,16 +1,19 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Runtime from "./Runtime.js"; export const StreamHandle = withCdpMeta(z.string(), "IO.StreamHandle", "type"); export const CloseParams = withCdpMeta(z.object({ "handle": z.lazy(() => StreamHandle) }).passthrough(), "IO.close.params", "commandParams", { method: "IO.close" }); export const CloseResult = withCdpMeta(z.object({ }).passthrough(), "IO.close.result", "commandResult", { method: "IO.close" }); +export const CloseCommand = withCdpCommand("IO.close", CloseParams, CloseResult); export const ReadParams = withCdpMeta(z.object({ "handle": z.lazy(() => StreamHandle), "offset": z.number().int().optional(), "size": z.number().int().optional() }).passthrough(), "IO.read.params", "commandParams", { method: "IO.read" }); export const ReadResult = withCdpMeta(z.object({ "base64Encoded": z.boolean().optional(), "data": z.string(), "eof": z.boolean() }).passthrough(), "IO.read.result", "commandResult", { method: "IO.read" }); +export const ReadCommand = withCdpCommand("IO.read", ReadParams, ReadResult); export const ResolveBlobParams = withCdpMeta(z.object({ "objectId": z.lazy(() => Runtime.RemoteObjectId) }).passthrough(), "IO.resolveBlob.params", "commandParams", { method: "IO.resolveBlob" }); export const ResolveBlobResult = withCdpMeta(z.object({ "uuid": z.string() }).passthrough(), "IO.resolveBlob.result", "commandResult", { method: "IO.resolveBlob" }); +export const ResolveBlobCommand = withCdpCommand("IO.resolveBlob", ResolveBlobParams, ResolveBlobResult); export const zod = { StreamHandle: StreamHandle, @@ -22,9 +25,9 @@ export const zod = { ResolveBlobResult: ResolveBlobResult, } as const; export const commands = { - "IO.close": { params: CloseParams, result: CloseResult }, - "IO.read": { params: ReadParams, result: ReadResult }, - "IO.resolveBlob": { params: ResolveBlobParams, result: ResolveBlobResult }, + "IO.close": CloseCommand, + "IO.read": ReadCommand, + "IO.resolveBlob": ResolveBlobCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/IndexedDB.ts b/js/src/types/generated/zod/IndexedDB.ts index 3d484ce1..877eff79 100644 --- a/js/src/types/generated/zod/IndexedDB.ts +++ b/js/src/types/generated/zod/IndexedDB.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Runtime from "./Runtime.js"; import * as Storage from "./Storage.js"; @@ -14,22 +14,31 @@ export const DataEntry = withCdpMeta(z.object({ "key": z.lazy(() => Runtime.Remo export const KeyPath = withCdpMeta(z.object({ "type": z.enum(["null", "string", "array"]), "string": z.string().optional(), "array": z.array(z.string()).optional() }).passthrough(), "IndexedDB.KeyPath", "type"); export const ClearObjectStoreParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string(), "objectStoreName": z.string() }).passthrough(), "IndexedDB.clearObjectStore.params", "commandParams", { method: "IndexedDB.clearObjectStore" }); export const ClearObjectStoreResult = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.clearObjectStore.result", "commandResult", { method: "IndexedDB.clearObjectStore" }); +export const ClearObjectStoreCommand = withCdpCommand("IndexedDB.clearObjectStore", ClearObjectStoreParams, ClearObjectStoreResult); export const DeleteDatabaseParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string() }).passthrough(), "IndexedDB.deleteDatabase.params", "commandParams", { method: "IndexedDB.deleteDatabase" }); export const DeleteDatabaseResult = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.deleteDatabase.result", "commandResult", { method: "IndexedDB.deleteDatabase" }); +export const DeleteDatabaseCommand = withCdpCommand("IndexedDB.deleteDatabase", DeleteDatabaseParams, DeleteDatabaseResult); export const DeleteObjectStoreEntriesParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string(), "objectStoreName": z.string(), "keyRange": z.lazy(() => KeyRange) }).passthrough(), "IndexedDB.deleteObjectStoreEntries.params", "commandParams", { method: "IndexedDB.deleteObjectStoreEntries" }); export const DeleteObjectStoreEntriesResult = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.deleteObjectStoreEntries.result", "commandResult", { method: "IndexedDB.deleteObjectStoreEntries" }); +export const DeleteObjectStoreEntriesCommand = withCdpCommand("IndexedDB.deleteObjectStoreEntries", DeleteObjectStoreEntriesParams, DeleteObjectStoreEntriesResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.disable.params", "commandParams", { method: "IndexedDB.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.disable.result", "commandResult", { method: "IndexedDB.disable" }); +export const DisableCommand = withCdpCommand("IndexedDB.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.enable.params", "commandParams", { method: "IndexedDB.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "IndexedDB.enable.result", "commandResult", { method: "IndexedDB.enable" }); +export const EnableCommand = withCdpCommand("IndexedDB.enable", EnableParams, EnableResult); export const RequestDataParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string(), "objectStoreName": z.string(), "indexName": z.string().optional(), "skipCount": z.number().int(), "pageSize": z.number().int(), "keyRange": z.lazy(() => KeyRange).optional() }).passthrough(), "IndexedDB.requestData.params", "commandParams", { method: "IndexedDB.requestData" }); export const RequestDataResult = withCdpMeta(z.object({ "objectStoreDataEntries": z.array(z.lazy(() => DataEntry)), "hasMore": z.boolean() }).passthrough(), "IndexedDB.requestData.result", "commandResult", { method: "IndexedDB.requestData" }); +export const RequestDataCommand = withCdpCommand("IndexedDB.requestData", RequestDataParams, RequestDataResult); export const GetMetadataParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string(), "objectStoreName": z.string() }).passthrough(), "IndexedDB.getMetadata.params", "commandParams", { method: "IndexedDB.getMetadata" }); export const GetMetadataResult = withCdpMeta(z.object({ "entriesCount": z.number(), "keyGeneratorValue": z.number() }).passthrough(), "IndexedDB.getMetadata.result", "commandResult", { method: "IndexedDB.getMetadata" }); +export const GetMetadataCommand = withCdpCommand("IndexedDB.getMetadata", GetMetadataParams, GetMetadataResult); export const RequestDatabaseParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional(), "databaseName": z.string() }).passthrough(), "IndexedDB.requestDatabase.params", "commandParams", { method: "IndexedDB.requestDatabase" }); export const RequestDatabaseResult = withCdpMeta(z.object({ "databaseWithObjectStores": z.lazy(() => DatabaseWithObjectStores) }).passthrough(), "IndexedDB.requestDatabase.result", "commandResult", { method: "IndexedDB.requestDatabase" }); +export const RequestDatabaseCommand = withCdpCommand("IndexedDB.requestDatabase", RequestDatabaseParams, RequestDatabaseResult); export const RequestDatabaseNamesParams = withCdpMeta(z.object({ "securityOrigin": z.string().optional(), "storageKey": z.string().optional(), "storageBucket": z.lazy(() => Storage.StorageBucket).optional() }).passthrough(), "IndexedDB.requestDatabaseNames.params", "commandParams", { method: "IndexedDB.requestDatabaseNames" }); export const RequestDatabaseNamesResult = withCdpMeta(z.object({ "databaseNames": z.array(z.string()) }).passthrough(), "IndexedDB.requestDatabaseNames.result", "commandResult", { method: "IndexedDB.requestDatabaseNames" }); +export const RequestDatabaseNamesCommand = withCdpCommand("IndexedDB.requestDatabaseNames", RequestDatabaseNamesParams, RequestDatabaseNamesResult); export const zod = { DatabaseWithObjectStores: DatabaseWithObjectStores, @@ -59,15 +68,15 @@ export const zod = { RequestDatabaseNamesResult: RequestDatabaseNamesResult, } as const; export const commands = { - "IndexedDB.clearObjectStore": { params: ClearObjectStoreParams, result: ClearObjectStoreResult }, - "IndexedDB.deleteDatabase": { params: DeleteDatabaseParams, result: DeleteDatabaseResult }, - "IndexedDB.deleteObjectStoreEntries": { params: DeleteObjectStoreEntriesParams, result: DeleteObjectStoreEntriesResult }, - "IndexedDB.disable": { params: DisableParams, result: DisableResult }, - "IndexedDB.enable": { params: EnableParams, result: EnableResult }, - "IndexedDB.requestData": { params: RequestDataParams, result: RequestDataResult }, - "IndexedDB.getMetadata": { params: GetMetadataParams, result: GetMetadataResult }, - "IndexedDB.requestDatabase": { params: RequestDatabaseParams, result: RequestDatabaseResult }, - "IndexedDB.requestDatabaseNames": { params: RequestDatabaseNamesParams, result: RequestDatabaseNamesResult }, + "IndexedDB.clearObjectStore": ClearObjectStoreCommand, + "IndexedDB.deleteDatabase": DeleteDatabaseCommand, + "IndexedDB.deleteObjectStoreEntries": DeleteObjectStoreEntriesCommand, + "IndexedDB.disable": DisableCommand, + "IndexedDB.enable": EnableCommand, + "IndexedDB.requestData": RequestDataCommand, + "IndexedDB.getMetadata": GetMetadataCommand, + "IndexedDB.requestDatabase": RequestDatabaseCommand, + "IndexedDB.requestDatabaseNames": RequestDatabaseNamesCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Input.ts b/js/src/types/generated/zod/Input.ts index e751355b..ec1174d9 100644 --- a/js/src/types/generated/zod/Input.ts +++ b/js/src/types/generated/zod/Input.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const TouchPoint = withCdpMeta(z.object({ "x": z.number(), "y": z.number(), "radiusX": z.number().optional(), "radiusY": z.number().optional(), "rotationAngle": z.number().optional(), "force": z.number().optional(), "tangentialPressure": z.number().optional(), "tiltX": z.number().optional(), "tiltY": z.number().optional(), "twist": z.number().int().optional(), "id": z.number().optional() }).passthrough(), "Input.TouchPoint", "type"); export const GestureSourceType = withCdpMeta(z.enum(["default", "touch", "mouse"]), "Input.GestureSourceType", "type"); @@ -11,30 +11,43 @@ export const DragDataItem = withCdpMeta(z.object({ "mimeType": z.string(), "data export const DragData = withCdpMeta(z.object({ "items": z.array(z.lazy(() => DragDataItem)), "files": z.array(z.string()).optional(), "dragOperationsMask": z.number().int() }).passthrough(), "Input.DragData", "type"); export const DispatchDragEventParams = withCdpMeta(z.object({ "type": z.enum(["dragEnter", "dragOver", "drop", "dragCancel"]), "x": z.number(), "y": z.number(), "data": z.lazy(() => DragData), "modifiers": z.number().int().optional() }).passthrough(), "Input.dispatchDragEvent.params", "commandParams", { method: "Input.dispatchDragEvent" }); export const DispatchDragEventResult = withCdpMeta(z.object({ }).passthrough(), "Input.dispatchDragEvent.result", "commandResult", { method: "Input.dispatchDragEvent" }); +export const DispatchDragEventCommand = withCdpCommand("Input.dispatchDragEvent", DispatchDragEventParams, DispatchDragEventResult); export const DispatchKeyEventParams = withCdpMeta(z.object({ "type": z.enum(["keyDown", "keyUp", "rawKeyDown", "char"]), "modifiers": z.number().int().optional(), "timestamp": z.lazy(() => TimeSinceEpoch).optional(), "text": z.string().optional(), "unmodifiedText": z.string().optional(), "keyIdentifier": z.string().optional(), "code": z.string().optional(), "key": z.string().optional(), "windowsVirtualKeyCode": z.number().int().optional(), "nativeVirtualKeyCode": z.number().int().optional(), "autoRepeat": z.boolean().optional(), "isKeypad": z.boolean().optional(), "isSystemKey": z.boolean().optional(), "location": z.number().int().optional(), "commands": z.array(z.string()).optional() }).passthrough(), "Input.dispatchKeyEvent.params", "commandParams", { method: "Input.dispatchKeyEvent" }); export const DispatchKeyEventResult = withCdpMeta(z.object({ }).passthrough(), "Input.dispatchKeyEvent.result", "commandResult", { method: "Input.dispatchKeyEvent" }); +export const DispatchKeyEventCommand = withCdpCommand("Input.dispatchKeyEvent", DispatchKeyEventParams, DispatchKeyEventResult); export const InsertTextParams = withCdpMeta(z.object({ "text": z.string() }).passthrough(), "Input.insertText.params", "commandParams", { method: "Input.insertText" }); export const InsertTextResult = withCdpMeta(z.object({ }).passthrough(), "Input.insertText.result", "commandResult", { method: "Input.insertText" }); +export const InsertTextCommand = withCdpCommand("Input.insertText", InsertTextParams, InsertTextResult); export const ImeSetCompositionParams = withCdpMeta(z.object({ "text": z.string(), "selectionStart": z.number().int(), "selectionEnd": z.number().int(), "replacementStart": z.number().int().optional(), "replacementEnd": z.number().int().optional() }).passthrough(), "Input.imeSetComposition.params", "commandParams", { method: "Input.imeSetComposition" }); export const ImeSetCompositionResult = withCdpMeta(z.object({ }).passthrough(), "Input.imeSetComposition.result", "commandResult", { method: "Input.imeSetComposition" }); +export const ImeSetCompositionCommand = withCdpCommand("Input.imeSetComposition", ImeSetCompositionParams, ImeSetCompositionResult); export const DispatchMouseEventParams = withCdpMeta(z.object({ "type": z.enum(["mousePressed", "mouseReleased", "mouseMoved", "mouseWheel"]), "x": z.number(), "y": z.number(), "modifiers": z.number().int().optional(), "timestamp": z.lazy(() => TimeSinceEpoch).optional(), "button": z.lazy(() => MouseButton).optional(), "buttons": z.number().int().optional(), "clickCount": z.number().int().optional(), "force": z.number().optional(), "tangentialPressure": z.number().optional(), "tiltX": z.number().optional(), "tiltY": z.number().optional(), "twist": z.number().int().optional(), "deltaX": z.number().optional(), "deltaY": z.number().optional(), "pointerType": z.enum(["mouse", "pen"]).optional() }).passthrough(), "Input.dispatchMouseEvent.params", "commandParams", { method: "Input.dispatchMouseEvent" }); export const DispatchMouseEventResult = withCdpMeta(z.object({ }).passthrough(), "Input.dispatchMouseEvent.result", "commandResult", { method: "Input.dispatchMouseEvent" }); +export const DispatchMouseEventCommand = withCdpCommand("Input.dispatchMouseEvent", DispatchMouseEventParams, DispatchMouseEventResult); export const DispatchTouchEventParams = withCdpMeta(z.object({ "type": z.enum(["touchStart", "touchEnd", "touchMove", "touchCancel"]), "touchPoints": z.array(z.lazy(() => TouchPoint)), "modifiers": z.number().int().optional(), "timestamp": z.lazy(() => TimeSinceEpoch).optional() }).passthrough(), "Input.dispatchTouchEvent.params", "commandParams", { method: "Input.dispatchTouchEvent" }); export const DispatchTouchEventResult = withCdpMeta(z.object({ }).passthrough(), "Input.dispatchTouchEvent.result", "commandResult", { method: "Input.dispatchTouchEvent" }); +export const DispatchTouchEventCommand = withCdpCommand("Input.dispatchTouchEvent", DispatchTouchEventParams, DispatchTouchEventResult); export const CancelDraggingParams = withCdpMeta(z.object({ }).passthrough(), "Input.cancelDragging.params", "commandParams", { method: "Input.cancelDragging" }); export const CancelDraggingResult = withCdpMeta(z.object({ }).passthrough(), "Input.cancelDragging.result", "commandResult", { method: "Input.cancelDragging" }); +export const CancelDraggingCommand = withCdpCommand("Input.cancelDragging", CancelDraggingParams, CancelDraggingResult); export const EmulateTouchFromMouseEventParams = withCdpMeta(z.object({ "type": z.enum(["mousePressed", "mouseReleased", "mouseMoved", "mouseWheel"]), "x": z.number().int(), "y": z.number().int(), "button": z.lazy(() => MouseButton), "timestamp": z.lazy(() => TimeSinceEpoch).optional(), "deltaX": z.number().optional(), "deltaY": z.number().optional(), "modifiers": z.number().int().optional(), "clickCount": z.number().int().optional() }).passthrough(), "Input.emulateTouchFromMouseEvent.params", "commandParams", { method: "Input.emulateTouchFromMouseEvent" }); export const EmulateTouchFromMouseEventResult = withCdpMeta(z.object({ }).passthrough(), "Input.emulateTouchFromMouseEvent.result", "commandResult", { method: "Input.emulateTouchFromMouseEvent" }); +export const EmulateTouchFromMouseEventCommand = withCdpCommand("Input.emulateTouchFromMouseEvent", EmulateTouchFromMouseEventParams, EmulateTouchFromMouseEventResult); export const SetIgnoreInputEventsParams = withCdpMeta(z.object({ "ignore": z.boolean() }).passthrough(), "Input.setIgnoreInputEvents.params", "commandParams", { method: "Input.setIgnoreInputEvents" }); export const SetIgnoreInputEventsResult = withCdpMeta(z.object({ }).passthrough(), "Input.setIgnoreInputEvents.result", "commandResult", { method: "Input.setIgnoreInputEvents" }); +export const SetIgnoreInputEventsCommand = withCdpCommand("Input.setIgnoreInputEvents", SetIgnoreInputEventsParams, SetIgnoreInputEventsResult); export const SetInterceptDragsParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Input.setInterceptDrags.params", "commandParams", { method: "Input.setInterceptDrags" }); export const SetInterceptDragsResult = withCdpMeta(z.object({ }).passthrough(), "Input.setInterceptDrags.result", "commandResult", { method: "Input.setInterceptDrags" }); +export const SetInterceptDragsCommand = withCdpCommand("Input.setInterceptDrags", SetInterceptDragsParams, SetInterceptDragsResult); export const SynthesizePinchGestureParams = withCdpMeta(z.object({ "x": z.number(), "y": z.number(), "scaleFactor": z.number(), "relativeSpeed": z.number().int().optional(), "gestureSourceType": z.lazy(() => GestureSourceType).optional() }).passthrough(), "Input.synthesizePinchGesture.params", "commandParams", { method: "Input.synthesizePinchGesture" }); export const SynthesizePinchGestureResult = withCdpMeta(z.object({ }).passthrough(), "Input.synthesizePinchGesture.result", "commandResult", { method: "Input.synthesizePinchGesture" }); +export const SynthesizePinchGestureCommand = withCdpCommand("Input.synthesizePinchGesture", SynthesizePinchGestureParams, SynthesizePinchGestureResult); export const SynthesizeScrollGestureParams = withCdpMeta(z.object({ "x": z.number(), "y": z.number(), "xDistance": z.number().optional(), "yDistance": z.number().optional(), "xOverscroll": z.number().optional(), "yOverscroll": z.number().optional(), "preventFling": z.boolean().optional(), "speed": z.number().int().optional(), "gestureSourceType": z.lazy(() => GestureSourceType).optional(), "repeatCount": z.number().int().optional(), "repeatDelayMs": z.number().int().optional(), "interactionMarkerName": z.string().optional() }).passthrough(), "Input.synthesizeScrollGesture.params", "commandParams", { method: "Input.synthesizeScrollGesture" }); export const SynthesizeScrollGestureResult = withCdpMeta(z.object({ }).passthrough(), "Input.synthesizeScrollGesture.result", "commandResult", { method: "Input.synthesizeScrollGesture" }); +export const SynthesizeScrollGestureCommand = withCdpCommand("Input.synthesizeScrollGesture", SynthesizeScrollGestureParams, SynthesizeScrollGestureResult); export const SynthesizeTapGestureParams = withCdpMeta(z.object({ "x": z.number(), "y": z.number(), "duration": z.number().int().optional(), "tapCount": z.number().int().optional(), "gestureSourceType": z.lazy(() => GestureSourceType).optional() }).passthrough(), "Input.synthesizeTapGesture.params", "commandParams", { method: "Input.synthesizeTapGesture" }); export const SynthesizeTapGestureResult = withCdpMeta(z.object({ }).passthrough(), "Input.synthesizeTapGesture.result", "commandResult", { method: "Input.synthesizeTapGesture" }); +export const SynthesizeTapGestureCommand = withCdpCommand("Input.synthesizeTapGesture", SynthesizeTapGestureParams, SynthesizeTapGestureResult); export const DragInterceptedEvent = withCdpMeta(z.object({ "data": z.lazy(() => DragData) }).passthrough(), "Input.dragIntercepted", "event", { phase: "event" }); export const zod = { @@ -73,19 +86,19 @@ export const zod = { DragInterceptedEvent: DragInterceptedEvent, } as const; export const commands = { - "Input.dispatchDragEvent": { params: DispatchDragEventParams, result: DispatchDragEventResult }, - "Input.dispatchKeyEvent": { params: DispatchKeyEventParams, result: DispatchKeyEventResult }, - "Input.insertText": { params: InsertTextParams, result: InsertTextResult }, - "Input.imeSetComposition": { params: ImeSetCompositionParams, result: ImeSetCompositionResult }, - "Input.dispatchMouseEvent": { params: DispatchMouseEventParams, result: DispatchMouseEventResult }, - "Input.dispatchTouchEvent": { params: DispatchTouchEventParams, result: DispatchTouchEventResult }, - "Input.cancelDragging": { params: CancelDraggingParams, result: CancelDraggingResult }, - "Input.emulateTouchFromMouseEvent": { params: EmulateTouchFromMouseEventParams, result: EmulateTouchFromMouseEventResult }, - "Input.setIgnoreInputEvents": { params: SetIgnoreInputEventsParams, result: SetIgnoreInputEventsResult }, - "Input.setInterceptDrags": { params: SetInterceptDragsParams, result: SetInterceptDragsResult }, - "Input.synthesizePinchGesture": { params: SynthesizePinchGestureParams, result: SynthesizePinchGestureResult }, - "Input.synthesizeScrollGesture": { params: SynthesizeScrollGestureParams, result: SynthesizeScrollGestureResult }, - "Input.synthesizeTapGesture": { params: SynthesizeTapGestureParams, result: SynthesizeTapGestureResult }, + "Input.dispatchDragEvent": DispatchDragEventCommand, + "Input.dispatchKeyEvent": DispatchKeyEventCommand, + "Input.insertText": InsertTextCommand, + "Input.imeSetComposition": ImeSetCompositionCommand, + "Input.dispatchMouseEvent": DispatchMouseEventCommand, + "Input.dispatchTouchEvent": DispatchTouchEventCommand, + "Input.cancelDragging": CancelDraggingCommand, + "Input.emulateTouchFromMouseEvent": EmulateTouchFromMouseEventCommand, + "Input.setIgnoreInputEvents": SetIgnoreInputEventsCommand, + "Input.setInterceptDrags": SetInterceptDragsCommand, + "Input.synthesizePinchGesture": SynthesizePinchGestureCommand, + "Input.synthesizeScrollGesture": SynthesizeScrollGestureCommand, + "Input.synthesizeTapGesture": SynthesizeTapGestureCommand, } as const; export const events = { "Input.dragIntercepted": DragInterceptedEvent, diff --git a/js/src/types/generated/zod/Inspector.ts b/js/src/types/generated/zod/Inspector.ts index 821520e0..0e5dbeda 100644 --- a/js/src/types/generated/zod/Inspector.ts +++ b/js/src/types/generated/zod/Inspector.ts @@ -1,12 +1,14 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Inspector.disable.params", "commandParams", { method: "Inspector.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Inspector.disable.result", "commandResult", { method: "Inspector.disable" }); +export const DisableCommand = withCdpCommand("Inspector.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Inspector.enable.params", "commandParams", { method: "Inspector.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Inspector.enable.result", "commandResult", { method: "Inspector.enable" }); +export const EnableCommand = withCdpCommand("Inspector.enable", EnableParams, EnableResult); export const DetachedEvent = withCdpMeta(z.object({ "reason": z.string() }).passthrough(), "Inspector.detached", "event", { phase: "event" }); export const TargetCrashedEvent = withCdpMeta(z.object({ }).passthrough(), "Inspector.targetCrashed", "event", { phase: "event" }); export const TargetReloadedAfterCrashEvent = withCdpMeta(z.object({ }).passthrough(), "Inspector.targetReloadedAfterCrash", "event", { phase: "event" }); @@ -23,8 +25,8 @@ export const zod = { WorkerScriptLoadedEvent: WorkerScriptLoadedEvent, } as const; export const commands = { - "Inspector.disable": { params: DisableParams, result: DisableResult }, - "Inspector.enable": { params: EnableParams, result: EnableResult }, + "Inspector.disable": DisableCommand, + "Inspector.enable": EnableCommand, } as const; export const events = { "Inspector.detached": DetachedEvent, diff --git a/js/src/types/generated/zod/LayerTree.ts b/js/src/types/generated/zod/LayerTree.ts index 4bd592c8..a748807c 100644 --- a/js/src/types/generated/zod/LayerTree.ts +++ b/js/src/types/generated/zod/LayerTree.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; export const LayerId = withCdpMeta(z.string(), "LayerTree.LayerId", "type"); @@ -13,22 +13,31 @@ export const Layer = withCdpMeta(z.object({ "layerId": z.lazy(() => LayerId), "p export const PaintProfile = withCdpMeta(z.array(z.number()), "LayerTree.PaintProfile", "type"); export const CompositingReasonsParams = withCdpMeta(z.object({ "layerId": z.lazy(() => LayerId) }).passthrough(), "LayerTree.compositingReasons.params", "commandParams", { method: "LayerTree.compositingReasons" }); export const CompositingReasonsResult = withCdpMeta(z.object({ "compositingReasons": z.array(z.string()), "compositingReasonIds": z.array(z.string()) }).passthrough(), "LayerTree.compositingReasons.result", "commandResult", { method: "LayerTree.compositingReasons" }); +export const CompositingReasonsCommand = withCdpCommand("LayerTree.compositingReasons", CompositingReasonsParams, CompositingReasonsResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "LayerTree.disable.params", "commandParams", { method: "LayerTree.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "LayerTree.disable.result", "commandResult", { method: "LayerTree.disable" }); +export const DisableCommand = withCdpCommand("LayerTree.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "LayerTree.enable.params", "commandParams", { method: "LayerTree.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "LayerTree.enable.result", "commandResult", { method: "LayerTree.enable" }); +export const EnableCommand = withCdpCommand("LayerTree.enable", EnableParams, EnableResult); export const LoadSnapshotParams = withCdpMeta(z.object({ "tiles": z.array(z.lazy(() => PictureTile)) }).passthrough(), "LayerTree.loadSnapshot.params", "commandParams", { method: "LayerTree.loadSnapshot" }); export const LoadSnapshotResult = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId) }).passthrough(), "LayerTree.loadSnapshot.result", "commandResult", { method: "LayerTree.loadSnapshot" }); +export const LoadSnapshotCommand = withCdpCommand("LayerTree.loadSnapshot", LoadSnapshotParams, LoadSnapshotResult); export const MakeSnapshotParams = withCdpMeta(z.object({ "layerId": z.lazy(() => LayerId) }).passthrough(), "LayerTree.makeSnapshot.params", "commandParams", { method: "LayerTree.makeSnapshot" }); export const MakeSnapshotResult = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId) }).passthrough(), "LayerTree.makeSnapshot.result", "commandResult", { method: "LayerTree.makeSnapshot" }); +export const MakeSnapshotCommand = withCdpCommand("LayerTree.makeSnapshot", MakeSnapshotParams, MakeSnapshotResult); export const ProfileSnapshotParams = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId), "minRepeatCount": z.number().int().optional(), "minDuration": z.number().optional(), "clipRect": z.lazy(() => DOM.Rect).optional() }).passthrough(), "LayerTree.profileSnapshot.params", "commandParams", { method: "LayerTree.profileSnapshot" }); export const ProfileSnapshotResult = withCdpMeta(z.object({ "timings": z.array(z.lazy(() => PaintProfile)) }).passthrough(), "LayerTree.profileSnapshot.result", "commandResult", { method: "LayerTree.profileSnapshot" }); +export const ProfileSnapshotCommand = withCdpCommand("LayerTree.profileSnapshot", ProfileSnapshotParams, ProfileSnapshotResult); export const ReleaseSnapshotParams = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId) }).passthrough(), "LayerTree.releaseSnapshot.params", "commandParams", { method: "LayerTree.releaseSnapshot" }); export const ReleaseSnapshotResult = withCdpMeta(z.object({ }).passthrough(), "LayerTree.releaseSnapshot.result", "commandResult", { method: "LayerTree.releaseSnapshot" }); +export const ReleaseSnapshotCommand = withCdpCommand("LayerTree.releaseSnapshot", ReleaseSnapshotParams, ReleaseSnapshotResult); export const ReplaySnapshotParams = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId), "fromStep": z.number().int().optional(), "toStep": z.number().int().optional(), "scale": z.number().optional() }).passthrough(), "LayerTree.replaySnapshot.params", "commandParams", { method: "LayerTree.replaySnapshot" }); export const ReplaySnapshotResult = withCdpMeta(z.object({ "dataURL": z.string() }).passthrough(), "LayerTree.replaySnapshot.result", "commandResult", { method: "LayerTree.replaySnapshot" }); +export const ReplaySnapshotCommand = withCdpCommand("LayerTree.replaySnapshot", ReplaySnapshotParams, ReplaySnapshotResult); export const SnapshotCommandLogParams = withCdpMeta(z.object({ "snapshotId": z.lazy(() => SnapshotId) }).passthrough(), "LayerTree.snapshotCommandLog.params", "commandParams", { method: "LayerTree.snapshotCommandLog" }); export const SnapshotCommandLogResult = withCdpMeta(z.object({ "commandLog": z.array(z.record(z.string(), z.unknown())) }).passthrough(), "LayerTree.snapshotCommandLog.result", "commandResult", { method: "LayerTree.snapshotCommandLog" }); +export const SnapshotCommandLogCommand = withCdpCommand("LayerTree.snapshotCommandLog", SnapshotCommandLogParams, SnapshotCommandLogResult); export const LayerPaintedEvent = withCdpMeta(z.object({ "layerId": z.lazy(() => LayerId), "clip": z.lazy(() => DOM.Rect) }).passthrough(), "LayerTree.layerPainted", "event", { phase: "event" }); export const LayerTreeDidChangeEvent = withCdpMeta(z.object({ "layers": z.array(z.lazy(() => Layer)).optional() }).passthrough(), "LayerTree.layerTreeDidChange", "event", { phase: "event" }); @@ -62,15 +71,15 @@ export const zod = { LayerTreeDidChangeEvent: LayerTreeDidChangeEvent, } as const; export const commands = { - "LayerTree.compositingReasons": { params: CompositingReasonsParams, result: CompositingReasonsResult }, - "LayerTree.disable": { params: DisableParams, result: DisableResult }, - "LayerTree.enable": { params: EnableParams, result: EnableResult }, - "LayerTree.loadSnapshot": { params: LoadSnapshotParams, result: LoadSnapshotResult }, - "LayerTree.makeSnapshot": { params: MakeSnapshotParams, result: MakeSnapshotResult }, - "LayerTree.profileSnapshot": { params: ProfileSnapshotParams, result: ProfileSnapshotResult }, - "LayerTree.releaseSnapshot": { params: ReleaseSnapshotParams, result: ReleaseSnapshotResult }, - "LayerTree.replaySnapshot": { params: ReplaySnapshotParams, result: ReplaySnapshotResult }, - "LayerTree.snapshotCommandLog": { params: SnapshotCommandLogParams, result: SnapshotCommandLogResult }, + "LayerTree.compositingReasons": CompositingReasonsCommand, + "LayerTree.disable": DisableCommand, + "LayerTree.enable": EnableCommand, + "LayerTree.loadSnapshot": LoadSnapshotCommand, + "LayerTree.makeSnapshot": MakeSnapshotCommand, + "LayerTree.profileSnapshot": ProfileSnapshotCommand, + "LayerTree.releaseSnapshot": ReleaseSnapshotCommand, + "LayerTree.replaySnapshot": ReplaySnapshotCommand, + "LayerTree.snapshotCommandLog": SnapshotCommandLogCommand, } as const; export const events = { "LayerTree.layerPainted": LayerPaintedEvent, diff --git a/js/src/types/generated/zod/Log.ts b/js/src/types/generated/zod/Log.ts index e8131fe3..ac8726ed 100644 --- a/js/src/types/generated/zod/Log.ts +++ b/js/src/types/generated/zod/Log.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Network from "./Network.js"; import * as Runtime from "./Runtime.js"; @@ -9,14 +9,19 @@ export const LogEntry = withCdpMeta(z.object({ "source": z.enum(["xml", "javascr export const ViolationSetting = withCdpMeta(z.object({ "name": z.enum(["longTask", "longLayout", "blockedEvent", "blockedParser", "discouragedAPIUse", "handler", "recurringHandler"]), "threshold": z.number() }).passthrough(), "Log.ViolationSetting", "type"); export const ClearParams = withCdpMeta(z.object({ }).passthrough(), "Log.clear.params", "commandParams", { method: "Log.clear" }); export const ClearResult = withCdpMeta(z.object({ }).passthrough(), "Log.clear.result", "commandResult", { method: "Log.clear" }); +export const ClearCommand = withCdpCommand("Log.clear", ClearParams, ClearResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Log.disable.params", "commandParams", { method: "Log.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Log.disable.result", "commandResult", { method: "Log.disable" }); +export const DisableCommand = withCdpCommand("Log.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Log.enable.params", "commandParams", { method: "Log.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Log.enable.result", "commandResult", { method: "Log.enable" }); +export const EnableCommand = withCdpCommand("Log.enable", EnableParams, EnableResult); export const StartViolationsReportParams = withCdpMeta(z.object({ "config": z.array(z.lazy(() => ViolationSetting)) }).passthrough(), "Log.startViolationsReport.params", "commandParams", { method: "Log.startViolationsReport" }); export const StartViolationsReportResult = withCdpMeta(z.object({ }).passthrough(), "Log.startViolationsReport.result", "commandResult", { method: "Log.startViolationsReport" }); +export const StartViolationsReportCommand = withCdpCommand("Log.startViolationsReport", StartViolationsReportParams, StartViolationsReportResult); export const StopViolationsReportParams = withCdpMeta(z.object({ }).passthrough(), "Log.stopViolationsReport.params", "commandParams", { method: "Log.stopViolationsReport" }); export const StopViolationsReportResult = withCdpMeta(z.object({ }).passthrough(), "Log.stopViolationsReport.result", "commandResult", { method: "Log.stopViolationsReport" }); +export const StopViolationsReportCommand = withCdpCommand("Log.stopViolationsReport", StopViolationsReportParams, StopViolationsReportResult); export const EntryAddedEvent = withCdpMeta(z.object({ "entry": z.lazy(() => LogEntry) }).passthrough(), "Log.entryAdded", "event", { phase: "event" }); export const zod = { @@ -35,11 +40,11 @@ export const zod = { EntryAddedEvent: EntryAddedEvent, } as const; export const commands = { - "Log.clear": { params: ClearParams, result: ClearResult }, - "Log.disable": { params: DisableParams, result: DisableResult }, - "Log.enable": { params: EnableParams, result: EnableResult }, - "Log.startViolationsReport": { params: StartViolationsReportParams, result: StartViolationsReportResult }, - "Log.stopViolationsReport": { params: StopViolationsReportParams, result: StopViolationsReportResult }, + "Log.clear": ClearCommand, + "Log.disable": DisableCommand, + "Log.enable": EnableCommand, + "Log.startViolationsReport": StartViolationsReportCommand, + "Log.stopViolationsReport": StopViolationsReportCommand, } as const; export const events = { "Log.entryAdded": EntryAddedEvent, diff --git a/js/src/types/generated/zod/Media.ts b/js/src/types/generated/zod/Media.ts index 05841be9..d3a6a357 100644 --- a/js/src/types/generated/zod/Media.ts +++ b/js/src/types/generated/zod/Media.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; export const PlayerId = withCdpMeta(z.string(), "Media.PlayerId", "type"); @@ -14,8 +14,10 @@ export const PlayerError = withCdpMeta(z.object({ "errorType": z.string(), "code export const Player = withCdpMeta(z.object({ "playerId": z.lazy(() => PlayerId), "domNodeId": z.lazy(() => DOM.BackendNodeId).optional() }).passthrough(), "Media.Player", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Media.enable.params", "commandParams", { method: "Media.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Media.enable.result", "commandResult", { method: "Media.enable" }); +export const EnableCommand = withCdpCommand("Media.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Media.disable.params", "commandParams", { method: "Media.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Media.disable.result", "commandResult", { method: "Media.disable" }); +export const DisableCommand = withCdpCommand("Media.disable", DisableParams, DisableResult); export const PlayerPropertiesChangedEvent = withCdpMeta(z.object({ "playerId": z.lazy(() => PlayerId), "properties": z.array(z.lazy(() => PlayerProperty)) }).passthrough(), "Media.playerPropertiesChanged", "event", { phase: "event" }); export const PlayerEventsAddedEvent = withCdpMeta(z.object({ "playerId": z.lazy(() => PlayerId), "events": z.array(z.lazy(() => PlayerEvent)) }).passthrough(), "Media.playerEventsAdded", "event", { phase: "event" }); export const PlayerMessagesLoggedEvent = withCdpMeta(z.object({ "playerId": z.lazy(() => PlayerId), "messages": z.array(z.lazy(() => PlayerMessage)) }).passthrough(), "Media.playerMessagesLogged", "event", { phase: "event" }); @@ -42,8 +44,8 @@ export const zod = { PlayerCreatedEvent: PlayerCreatedEvent, } as const; export const commands = { - "Media.enable": { params: EnableParams, result: EnableResult }, - "Media.disable": { params: DisableParams, result: DisableResult }, + "Media.enable": EnableCommand, + "Media.disable": DisableCommand, } as const; export const events = { "Media.playerPropertiesChanged": PlayerPropertiesChangedEvent, diff --git a/js/src/types/generated/zod/Memory.ts b/js/src/types/generated/zod/Memory.ts index 8a7e0973..8acb014f 100644 --- a/js/src/types/generated/zod/Memory.ts +++ b/js/src/types/generated/zod/Memory.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const PressureLevel = withCdpMeta(z.enum(["moderate", "critical"]), "Memory.PressureLevel", "type"); export const SamplingProfileNode = withCdpMeta(z.object({ "size": z.number(), "total": z.number(), "stack": z.array(z.string()) }).passthrough(), "Memory.SamplingProfileNode", "type"); @@ -10,26 +10,37 @@ export const Module = withCdpMeta(z.object({ "name": z.string(), "uuid": z.strin export const DOMCounter = withCdpMeta(z.object({ "name": z.string(), "count": z.number().int() }).passthrough(), "Memory.DOMCounter", "type"); export const GetDOMCountersParams = withCdpMeta(z.object({ }).passthrough(), "Memory.getDOMCounters.params", "commandParams", { method: "Memory.getDOMCounters" }); export const GetDOMCountersResult = withCdpMeta(z.object({ "documents": z.number().int(), "nodes": z.number().int(), "jsEventListeners": z.number().int() }).passthrough(), "Memory.getDOMCounters.result", "commandResult", { method: "Memory.getDOMCounters" }); +export const GetDOMCountersCommand = withCdpCommand("Memory.getDOMCounters", GetDOMCountersParams, GetDOMCountersResult); export const GetDOMCountersForLeakDetectionParams = withCdpMeta(z.object({ }).passthrough(), "Memory.getDOMCountersForLeakDetection.params", "commandParams", { method: "Memory.getDOMCountersForLeakDetection" }); export const GetDOMCountersForLeakDetectionResult = withCdpMeta(z.object({ "counters": z.array(z.lazy(() => DOMCounter)) }).passthrough(), "Memory.getDOMCountersForLeakDetection.result", "commandResult", { method: "Memory.getDOMCountersForLeakDetection" }); +export const GetDOMCountersForLeakDetectionCommand = withCdpCommand("Memory.getDOMCountersForLeakDetection", GetDOMCountersForLeakDetectionParams, GetDOMCountersForLeakDetectionResult); export const PrepareForLeakDetectionParams = withCdpMeta(z.object({ }).passthrough(), "Memory.prepareForLeakDetection.params", "commandParams", { method: "Memory.prepareForLeakDetection" }); export const PrepareForLeakDetectionResult = withCdpMeta(z.object({ }).passthrough(), "Memory.prepareForLeakDetection.result", "commandResult", { method: "Memory.prepareForLeakDetection" }); +export const PrepareForLeakDetectionCommand = withCdpCommand("Memory.prepareForLeakDetection", PrepareForLeakDetectionParams, PrepareForLeakDetectionResult); export const ForciblyPurgeJavaScriptMemoryParams = withCdpMeta(z.object({ }).passthrough(), "Memory.forciblyPurgeJavaScriptMemory.params", "commandParams", { method: "Memory.forciblyPurgeJavaScriptMemory" }); export const ForciblyPurgeJavaScriptMemoryResult = withCdpMeta(z.object({ }).passthrough(), "Memory.forciblyPurgeJavaScriptMemory.result", "commandResult", { method: "Memory.forciblyPurgeJavaScriptMemory" }); +export const ForciblyPurgeJavaScriptMemoryCommand = withCdpCommand("Memory.forciblyPurgeJavaScriptMemory", ForciblyPurgeJavaScriptMemoryParams, ForciblyPurgeJavaScriptMemoryResult); export const SetPressureNotificationsSuppressedParams = withCdpMeta(z.object({ "suppressed": z.boolean() }).passthrough(), "Memory.setPressureNotificationsSuppressed.params", "commandParams", { method: "Memory.setPressureNotificationsSuppressed" }); export const SetPressureNotificationsSuppressedResult = withCdpMeta(z.object({ }).passthrough(), "Memory.setPressureNotificationsSuppressed.result", "commandResult", { method: "Memory.setPressureNotificationsSuppressed" }); +export const SetPressureNotificationsSuppressedCommand = withCdpCommand("Memory.setPressureNotificationsSuppressed", SetPressureNotificationsSuppressedParams, SetPressureNotificationsSuppressedResult); export const SimulatePressureNotificationParams = withCdpMeta(z.object({ "level": z.lazy(() => PressureLevel) }).passthrough(), "Memory.simulatePressureNotification.params", "commandParams", { method: "Memory.simulatePressureNotification" }); export const SimulatePressureNotificationResult = withCdpMeta(z.object({ }).passthrough(), "Memory.simulatePressureNotification.result", "commandResult", { method: "Memory.simulatePressureNotification" }); +export const SimulatePressureNotificationCommand = withCdpCommand("Memory.simulatePressureNotification", SimulatePressureNotificationParams, SimulatePressureNotificationResult); export const StartSamplingParams = withCdpMeta(z.object({ "samplingInterval": z.number().int().optional(), "suppressRandomness": z.boolean().optional() }).passthrough(), "Memory.startSampling.params", "commandParams", { method: "Memory.startSampling" }); export const StartSamplingResult = withCdpMeta(z.object({ }).passthrough(), "Memory.startSampling.result", "commandResult", { method: "Memory.startSampling" }); +export const StartSamplingCommand = withCdpCommand("Memory.startSampling", StartSamplingParams, StartSamplingResult); export const StopSamplingParams = withCdpMeta(z.object({ }).passthrough(), "Memory.stopSampling.params", "commandParams", { method: "Memory.stopSampling" }); export const StopSamplingResult = withCdpMeta(z.object({ }).passthrough(), "Memory.stopSampling.result", "commandResult", { method: "Memory.stopSampling" }); +export const StopSamplingCommand = withCdpCommand("Memory.stopSampling", StopSamplingParams, StopSamplingResult); export const GetAllTimeSamplingProfileParams = withCdpMeta(z.object({ }).passthrough(), "Memory.getAllTimeSamplingProfile.params", "commandParams", { method: "Memory.getAllTimeSamplingProfile" }); export const GetAllTimeSamplingProfileResult = withCdpMeta(z.object({ "profile": z.lazy(() => SamplingProfile) }).passthrough(), "Memory.getAllTimeSamplingProfile.result", "commandResult", { method: "Memory.getAllTimeSamplingProfile" }); +export const GetAllTimeSamplingProfileCommand = withCdpCommand("Memory.getAllTimeSamplingProfile", GetAllTimeSamplingProfileParams, GetAllTimeSamplingProfileResult); export const GetBrowserSamplingProfileParams = withCdpMeta(z.object({ }).passthrough(), "Memory.getBrowserSamplingProfile.params", "commandParams", { method: "Memory.getBrowserSamplingProfile" }); export const GetBrowserSamplingProfileResult = withCdpMeta(z.object({ "profile": z.lazy(() => SamplingProfile) }).passthrough(), "Memory.getBrowserSamplingProfile.result", "commandResult", { method: "Memory.getBrowserSamplingProfile" }); +export const GetBrowserSamplingProfileCommand = withCdpCommand("Memory.getBrowserSamplingProfile", GetBrowserSamplingProfileParams, GetBrowserSamplingProfileResult); export const GetSamplingProfileParams = withCdpMeta(z.object({ }).passthrough(), "Memory.getSamplingProfile.params", "commandParams", { method: "Memory.getSamplingProfile" }); export const GetSamplingProfileResult = withCdpMeta(z.object({ "profile": z.lazy(() => SamplingProfile) }).passthrough(), "Memory.getSamplingProfile.result", "commandResult", { method: "Memory.getSamplingProfile" }); +export const GetSamplingProfileCommand = withCdpCommand("Memory.getSamplingProfile", GetSamplingProfileParams, GetSamplingProfileResult); export const zod = { PressureLevel: PressureLevel, @@ -61,17 +72,17 @@ export const zod = { GetSamplingProfileResult: GetSamplingProfileResult, } as const; export const commands = { - "Memory.getDOMCounters": { params: GetDOMCountersParams, result: GetDOMCountersResult }, - "Memory.getDOMCountersForLeakDetection": { params: GetDOMCountersForLeakDetectionParams, result: GetDOMCountersForLeakDetectionResult }, - "Memory.prepareForLeakDetection": { params: PrepareForLeakDetectionParams, result: PrepareForLeakDetectionResult }, - "Memory.forciblyPurgeJavaScriptMemory": { params: ForciblyPurgeJavaScriptMemoryParams, result: ForciblyPurgeJavaScriptMemoryResult }, - "Memory.setPressureNotificationsSuppressed": { params: SetPressureNotificationsSuppressedParams, result: SetPressureNotificationsSuppressedResult }, - "Memory.simulatePressureNotification": { params: SimulatePressureNotificationParams, result: SimulatePressureNotificationResult }, - "Memory.startSampling": { params: StartSamplingParams, result: StartSamplingResult }, - "Memory.stopSampling": { params: StopSamplingParams, result: StopSamplingResult }, - "Memory.getAllTimeSamplingProfile": { params: GetAllTimeSamplingProfileParams, result: GetAllTimeSamplingProfileResult }, - "Memory.getBrowserSamplingProfile": { params: GetBrowserSamplingProfileParams, result: GetBrowserSamplingProfileResult }, - "Memory.getSamplingProfile": { params: GetSamplingProfileParams, result: GetSamplingProfileResult }, + "Memory.getDOMCounters": GetDOMCountersCommand, + "Memory.getDOMCountersForLeakDetection": GetDOMCountersForLeakDetectionCommand, + "Memory.prepareForLeakDetection": PrepareForLeakDetectionCommand, + "Memory.forciblyPurgeJavaScriptMemory": ForciblyPurgeJavaScriptMemoryCommand, + "Memory.setPressureNotificationsSuppressed": SetPressureNotificationsSuppressedCommand, + "Memory.simulatePressureNotification": SimulatePressureNotificationCommand, + "Memory.startSampling": StartSamplingCommand, + "Memory.stopSampling": StopSamplingCommand, + "Memory.getAllTimeSamplingProfile": GetAllTimeSamplingProfileCommand, + "Memory.getBrowserSamplingProfile": GetBrowserSamplingProfileCommand, + "Memory.getSamplingProfile": GetSamplingProfileCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Network.ts b/js/src/types/generated/zod/Network.ts index 249626a5..e9c2f878 100644 --- a/js/src/types/generated/zod/Network.ts +++ b/js/src/types/generated/zod/Network.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Debugger from "./Debugger.js"; import * as Emulation from "./Emulation.js"; import * as IO from "./IO.js"; @@ -104,86 +104,127 @@ export const LoadNetworkResourcePageResult = withCdpMeta(z.object({ "success": z export const LoadNetworkResourceOptions = withCdpMeta(z.object({ "disableCache": z.boolean(), "includeCredentials": z.boolean() }).passthrough(), "Network.LoadNetworkResourceOptions", "type"); export const SetAcceptedEncodingsParams = withCdpMeta(z.object({ "encodings": z.array(z.lazy(() => ContentEncoding)) }).passthrough(), "Network.setAcceptedEncodings.params", "commandParams", { method: "Network.setAcceptedEncodings" }); export const SetAcceptedEncodingsResult = withCdpMeta(z.object({ }).passthrough(), "Network.setAcceptedEncodings.result", "commandResult", { method: "Network.setAcceptedEncodings" }); +export const SetAcceptedEncodingsCommand = withCdpCommand("Network.setAcceptedEncodings", SetAcceptedEncodingsParams, SetAcceptedEncodingsResult); export const ClearAcceptedEncodingsOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Network.clearAcceptedEncodingsOverride.params", "commandParams", { method: "Network.clearAcceptedEncodingsOverride" }); export const ClearAcceptedEncodingsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Network.clearAcceptedEncodingsOverride.result", "commandResult", { method: "Network.clearAcceptedEncodingsOverride" }); +export const ClearAcceptedEncodingsOverrideCommand = withCdpCommand("Network.clearAcceptedEncodingsOverride", ClearAcceptedEncodingsOverrideParams, ClearAcceptedEncodingsOverrideResult); export const CanClearBrowserCacheParams = withCdpMeta(z.object({ }).passthrough(), "Network.canClearBrowserCache.params", "commandParams", { method: "Network.canClearBrowserCache" }); export const CanClearBrowserCacheResult = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Network.canClearBrowserCache.result", "commandResult", { method: "Network.canClearBrowserCache" }); +export const CanClearBrowserCacheCommand = withCdpCommand("Network.canClearBrowserCache", CanClearBrowserCacheParams, CanClearBrowserCacheResult); export const CanClearBrowserCookiesParams = withCdpMeta(z.object({ }).passthrough(), "Network.canClearBrowserCookies.params", "commandParams", { method: "Network.canClearBrowserCookies" }); export const CanClearBrowserCookiesResult = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Network.canClearBrowserCookies.result", "commandResult", { method: "Network.canClearBrowserCookies" }); +export const CanClearBrowserCookiesCommand = withCdpCommand("Network.canClearBrowserCookies", CanClearBrowserCookiesParams, CanClearBrowserCookiesResult); export const CanEmulateNetworkConditionsParams = withCdpMeta(z.object({ }).passthrough(), "Network.canEmulateNetworkConditions.params", "commandParams", { method: "Network.canEmulateNetworkConditions" }); export const CanEmulateNetworkConditionsResult = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Network.canEmulateNetworkConditions.result", "commandResult", { method: "Network.canEmulateNetworkConditions" }); +export const CanEmulateNetworkConditionsCommand = withCdpCommand("Network.canEmulateNetworkConditions", CanEmulateNetworkConditionsParams, CanEmulateNetworkConditionsResult); export const ClearBrowserCacheParams = withCdpMeta(z.object({ }).passthrough(), "Network.clearBrowserCache.params", "commandParams", { method: "Network.clearBrowserCache" }); export const ClearBrowserCacheResult = withCdpMeta(z.object({ }).passthrough(), "Network.clearBrowserCache.result", "commandResult", { method: "Network.clearBrowserCache" }); +export const ClearBrowserCacheCommand = withCdpCommand("Network.clearBrowserCache", ClearBrowserCacheParams, ClearBrowserCacheResult); export const ClearBrowserCookiesParams = withCdpMeta(z.object({ }).passthrough(), "Network.clearBrowserCookies.params", "commandParams", { method: "Network.clearBrowserCookies" }); export const ClearBrowserCookiesResult = withCdpMeta(z.object({ }).passthrough(), "Network.clearBrowserCookies.result", "commandResult", { method: "Network.clearBrowserCookies" }); +export const ClearBrowserCookiesCommand = withCdpCommand("Network.clearBrowserCookies", ClearBrowserCookiesParams, ClearBrowserCookiesResult); export const ContinueInterceptedRequestParams = withCdpMeta(z.object({ "interceptionId": z.lazy(() => InterceptionId), "errorReason": z.lazy(() => ErrorReason).optional(), "rawResponse": z.string().optional(), "url": z.string().optional(), "method": z.string().optional(), "postData": z.string().optional(), "headers": z.lazy(() => Headers).optional(), "authChallengeResponse": z.lazy(() => AuthChallengeResponse).optional() }).passthrough(), "Network.continueInterceptedRequest.params", "commandParams", { method: "Network.continueInterceptedRequest" }); export const ContinueInterceptedRequestResult = withCdpMeta(z.object({ }).passthrough(), "Network.continueInterceptedRequest.result", "commandResult", { method: "Network.continueInterceptedRequest" }); +export const ContinueInterceptedRequestCommand = withCdpCommand("Network.continueInterceptedRequest", ContinueInterceptedRequestParams, ContinueInterceptedRequestResult); export const DeleteCookiesParams = withCdpMeta(z.object({ "name": z.string(), "url": z.string().optional(), "domain": z.string().optional(), "path": z.string().optional(), "partitionKey": z.lazy(() => CookiePartitionKey).optional() }).passthrough(), "Network.deleteCookies.params", "commandParams", { method: "Network.deleteCookies" }); export const DeleteCookiesResult = withCdpMeta(z.object({ }).passthrough(), "Network.deleteCookies.result", "commandResult", { method: "Network.deleteCookies" }); +export const DeleteCookiesCommand = withCdpCommand("Network.deleteCookies", DeleteCookiesParams, DeleteCookiesResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Network.disable.params", "commandParams", { method: "Network.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Network.disable.result", "commandResult", { method: "Network.disable" }); +export const DisableCommand = withCdpCommand("Network.disable", DisableParams, DisableResult); export const EmulateNetworkConditionsParams = withCdpMeta(z.object({ "offline": z.boolean(), "latency": z.number(), "downloadThroughput": z.number(), "uploadThroughput": z.number(), "connectionType": z.lazy(() => ConnectionType).optional(), "packetLoss": z.number().optional(), "packetQueueLength": z.number().int().optional(), "packetReordering": z.boolean().optional() }).passthrough(), "Network.emulateNetworkConditions.params", "commandParams", { method: "Network.emulateNetworkConditions" }); export const EmulateNetworkConditionsResult = withCdpMeta(z.object({ }).passthrough(), "Network.emulateNetworkConditions.result", "commandResult", { method: "Network.emulateNetworkConditions" }); +export const EmulateNetworkConditionsCommand = withCdpCommand("Network.emulateNetworkConditions", EmulateNetworkConditionsParams, EmulateNetworkConditionsResult); export const EmulateNetworkConditionsByRuleParams = withCdpMeta(z.object({ "offline": z.boolean().optional(), "emulateOfflineServiceWorker": z.boolean().optional(), "matchedNetworkConditions": z.array(z.lazy(() => NetworkConditions)) }).passthrough(), "Network.emulateNetworkConditionsByRule.params", "commandParams", { method: "Network.emulateNetworkConditionsByRule" }); export const EmulateNetworkConditionsByRuleResult = withCdpMeta(z.object({ "ruleIds": z.array(z.string()) }).passthrough(), "Network.emulateNetworkConditionsByRule.result", "commandResult", { method: "Network.emulateNetworkConditionsByRule" }); +export const EmulateNetworkConditionsByRuleCommand = withCdpCommand("Network.emulateNetworkConditionsByRule", EmulateNetworkConditionsByRuleParams, EmulateNetworkConditionsByRuleResult); export const OverrideNetworkStateParams = withCdpMeta(z.object({ "offline": z.boolean(), "latency": z.number(), "downloadThroughput": z.number(), "uploadThroughput": z.number(), "connectionType": z.lazy(() => ConnectionType).optional() }).passthrough(), "Network.overrideNetworkState.params", "commandParams", { method: "Network.overrideNetworkState" }); export const OverrideNetworkStateResult = withCdpMeta(z.object({ }).passthrough(), "Network.overrideNetworkState.result", "commandResult", { method: "Network.overrideNetworkState" }); +export const OverrideNetworkStateCommand = withCdpCommand("Network.overrideNetworkState", OverrideNetworkStateParams, OverrideNetworkStateResult); export const EnableParams = withCdpMeta(z.object({ "maxTotalBufferSize": z.number().int().optional(), "maxResourceBufferSize": z.number().int().optional(), "maxPostDataSize": z.number().int().optional(), "reportDirectSocketTraffic": z.boolean().optional(), "enableDurableMessages": z.boolean().optional() }).passthrough(), "Network.enable.params", "commandParams", { method: "Network.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Network.enable.result", "commandResult", { method: "Network.enable" }); +export const EnableCommand = withCdpCommand("Network.enable", EnableParams, EnableResult); export const ConfigureDurableMessagesParams = withCdpMeta(z.object({ "maxTotalBufferSize": z.number().int().optional(), "maxResourceBufferSize": z.number().int().optional() }).passthrough(), "Network.configureDurableMessages.params", "commandParams", { method: "Network.configureDurableMessages" }); export const ConfigureDurableMessagesResult = withCdpMeta(z.object({ }).passthrough(), "Network.configureDurableMessages.result", "commandResult", { method: "Network.configureDurableMessages" }); +export const ConfigureDurableMessagesCommand = withCdpCommand("Network.configureDurableMessages", ConfigureDurableMessagesParams, ConfigureDurableMessagesResult); export const GetAllCookiesParams = withCdpMeta(z.object({ }).passthrough(), "Network.getAllCookies.params", "commandParams", { method: "Network.getAllCookies" }); export const GetAllCookiesResult = withCdpMeta(z.object({ "cookies": z.array(z.lazy(() => Cookie)) }).passthrough(), "Network.getAllCookies.result", "commandResult", { method: "Network.getAllCookies" }); +export const GetAllCookiesCommand = withCdpCommand("Network.getAllCookies", GetAllCookiesParams, GetAllCookiesResult); export const GetCertificateParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Network.getCertificate.params", "commandParams", { method: "Network.getCertificate" }); export const GetCertificateResult = withCdpMeta(z.object({ "tableNames": z.array(z.string()) }).passthrough(), "Network.getCertificate.result", "commandResult", { method: "Network.getCertificate" }); +export const GetCertificateCommand = withCdpCommand("Network.getCertificate", GetCertificateParams, GetCertificateResult); export const GetCookiesParams = withCdpMeta(z.object({ "urls": z.array(z.string()).optional() }).passthrough(), "Network.getCookies.params", "commandParams", { method: "Network.getCookies" }); export const GetCookiesResult = withCdpMeta(z.object({ "cookies": z.array(z.lazy(() => Cookie)) }).passthrough(), "Network.getCookies.result", "commandResult", { method: "Network.getCookies" }); +export const GetCookiesCommand = withCdpCommand("Network.getCookies", GetCookiesParams, GetCookiesResult); export const GetResponseBodyParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Network.getResponseBody.params", "commandParams", { method: "Network.getResponseBody" }); export const GetResponseBodyResult = withCdpMeta(z.object({ "body": z.string(), "base64Encoded": z.boolean() }).passthrough(), "Network.getResponseBody.result", "commandResult", { method: "Network.getResponseBody" }); +export const GetResponseBodyCommand = withCdpCommand("Network.getResponseBody", GetResponseBodyParams, GetResponseBodyResult); export const GetRequestPostDataParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Network.getRequestPostData.params", "commandParams", { method: "Network.getRequestPostData" }); export const GetRequestPostDataResult = withCdpMeta(z.object({ "postData": z.string(), "base64Encoded": z.boolean() }).passthrough(), "Network.getRequestPostData.result", "commandResult", { method: "Network.getRequestPostData" }); +export const GetRequestPostDataCommand = withCdpCommand("Network.getRequestPostData", GetRequestPostDataParams, GetRequestPostDataResult); export const GetResponseBodyForInterceptionParams = withCdpMeta(z.object({ "interceptionId": z.lazy(() => InterceptionId) }).passthrough(), "Network.getResponseBodyForInterception.params", "commandParams", { method: "Network.getResponseBodyForInterception" }); export const GetResponseBodyForInterceptionResult = withCdpMeta(z.object({ "body": z.string(), "base64Encoded": z.boolean() }).passthrough(), "Network.getResponseBodyForInterception.result", "commandResult", { method: "Network.getResponseBodyForInterception" }); +export const GetResponseBodyForInterceptionCommand = withCdpCommand("Network.getResponseBodyForInterception", GetResponseBodyForInterceptionParams, GetResponseBodyForInterceptionResult); export const TakeResponseBodyForInterceptionAsStreamParams = withCdpMeta(z.object({ "interceptionId": z.lazy(() => InterceptionId) }).passthrough(), "Network.takeResponseBodyForInterceptionAsStream.params", "commandParams", { method: "Network.takeResponseBodyForInterceptionAsStream" }); export const TakeResponseBodyForInterceptionAsStreamResult = withCdpMeta(z.object({ "stream": z.lazy(() => IO.StreamHandle) }).passthrough(), "Network.takeResponseBodyForInterceptionAsStream.result", "commandResult", { method: "Network.takeResponseBodyForInterceptionAsStream" }); +export const TakeResponseBodyForInterceptionAsStreamCommand = withCdpCommand("Network.takeResponseBodyForInterceptionAsStream", TakeResponseBodyForInterceptionAsStreamParams, TakeResponseBodyForInterceptionAsStreamResult); export const ReplayXHRParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Network.replayXHR.params", "commandParams", { method: "Network.replayXHR" }); export const ReplayXHRResult = withCdpMeta(z.object({ }).passthrough(), "Network.replayXHR.result", "commandResult", { method: "Network.replayXHR" }); +export const ReplayXHRCommand = withCdpCommand("Network.replayXHR", ReplayXHRParams, ReplayXHRResult); export const SearchInResponseBodyParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "query": z.string(), "caseSensitive": z.boolean().optional(), "isRegex": z.boolean().optional() }).passthrough(), "Network.searchInResponseBody.params", "commandParams", { method: "Network.searchInResponseBody" }); export const SearchInResponseBodyResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => Debugger.SearchMatch)) }).passthrough(), "Network.searchInResponseBody.result", "commandResult", { method: "Network.searchInResponseBody" }); +export const SearchInResponseBodyCommand = withCdpCommand("Network.searchInResponseBody", SearchInResponseBodyParams, SearchInResponseBodyResult); export const SetBlockedURLsParams = withCdpMeta(z.object({ "urlPatterns": z.array(z.lazy(() => BlockPattern)).optional(), "urls": z.array(z.string()).optional() }).passthrough(), "Network.setBlockedURLs.params", "commandParams", { method: "Network.setBlockedURLs" }); export const SetBlockedURLsResult = withCdpMeta(z.object({ }).passthrough(), "Network.setBlockedURLs.result", "commandResult", { method: "Network.setBlockedURLs" }); +export const SetBlockedURLsCommand = withCdpCommand("Network.setBlockedURLs", SetBlockedURLsParams, SetBlockedURLsResult); export const SetBypassServiceWorkerParams = withCdpMeta(z.object({ "bypass": z.boolean() }).passthrough(), "Network.setBypassServiceWorker.params", "commandParams", { method: "Network.setBypassServiceWorker" }); export const SetBypassServiceWorkerResult = withCdpMeta(z.object({ }).passthrough(), "Network.setBypassServiceWorker.result", "commandResult", { method: "Network.setBypassServiceWorker" }); +export const SetBypassServiceWorkerCommand = withCdpCommand("Network.setBypassServiceWorker", SetBypassServiceWorkerParams, SetBypassServiceWorkerResult); export const SetCacheDisabledParams = withCdpMeta(z.object({ "cacheDisabled": z.boolean() }).passthrough(), "Network.setCacheDisabled.params", "commandParams", { method: "Network.setCacheDisabled" }); export const SetCacheDisabledResult = withCdpMeta(z.object({ }).passthrough(), "Network.setCacheDisabled.result", "commandResult", { method: "Network.setCacheDisabled" }); +export const SetCacheDisabledCommand = withCdpCommand("Network.setCacheDisabled", SetCacheDisabledParams, SetCacheDisabledResult); export const SetCookieParams = withCdpMeta(z.object({ "name": z.string(), "value": z.string(), "url": z.string().optional(), "domain": z.string().optional(), "path": z.string().optional(), "secure": z.boolean().optional(), "httpOnly": z.boolean().optional(), "sameSite": z.lazy(() => CookieSameSite).optional(), "expires": z.lazy(() => TimeSinceEpoch).optional(), "priority": z.lazy(() => CookiePriority).optional(), "sourceScheme": z.lazy(() => CookieSourceScheme).optional(), "sourcePort": z.number().int().optional(), "partitionKey": z.lazy(() => CookiePartitionKey).optional() }).passthrough(), "Network.setCookie.params", "commandParams", { method: "Network.setCookie" }); export const SetCookieResult = withCdpMeta(z.object({ "success": z.boolean() }).passthrough(), "Network.setCookie.result", "commandResult", { method: "Network.setCookie" }); +export const SetCookieCommand = withCdpCommand("Network.setCookie", SetCookieParams, SetCookieResult); export const SetCookiesParams = withCdpMeta(z.object({ "cookies": z.array(z.lazy(() => CookieParam)) }).passthrough(), "Network.setCookies.params", "commandParams", { method: "Network.setCookies" }); export const SetCookiesResult = withCdpMeta(z.object({ }).passthrough(), "Network.setCookies.result", "commandResult", { method: "Network.setCookies" }); +export const SetCookiesCommand = withCdpCommand("Network.setCookies", SetCookiesParams, SetCookiesResult); export const SetExtraHTTPHeadersParams = withCdpMeta(z.object({ "headers": z.lazy(() => Headers) }).passthrough(), "Network.setExtraHTTPHeaders.params", "commandParams", { method: "Network.setExtraHTTPHeaders" }); export const SetExtraHTTPHeadersResult = withCdpMeta(z.object({ }).passthrough(), "Network.setExtraHTTPHeaders.result", "commandResult", { method: "Network.setExtraHTTPHeaders" }); +export const SetExtraHTTPHeadersCommand = withCdpCommand("Network.setExtraHTTPHeaders", SetExtraHTTPHeadersParams, SetExtraHTTPHeadersResult); export const SetAttachDebugStackParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Network.setAttachDebugStack.params", "commandParams", { method: "Network.setAttachDebugStack" }); export const SetAttachDebugStackResult = withCdpMeta(z.object({ }).passthrough(), "Network.setAttachDebugStack.result", "commandResult", { method: "Network.setAttachDebugStack" }); +export const SetAttachDebugStackCommand = withCdpCommand("Network.setAttachDebugStack", SetAttachDebugStackParams, SetAttachDebugStackResult); export const SetRequestInterceptionParams = withCdpMeta(z.object({ "patterns": z.array(z.lazy(() => RequestPattern)) }).passthrough(), "Network.setRequestInterception.params", "commandParams", { method: "Network.setRequestInterception" }); export const SetRequestInterceptionResult = withCdpMeta(z.object({ }).passthrough(), "Network.setRequestInterception.result", "commandResult", { method: "Network.setRequestInterception" }); +export const SetRequestInterceptionCommand = withCdpCommand("Network.setRequestInterception", SetRequestInterceptionParams, SetRequestInterceptionResult); export const SetUserAgentOverrideParams = withCdpMeta(z.object({ "userAgent": z.string(), "acceptLanguage": z.string().optional(), "platform": z.string().optional(), "userAgentMetadata": z.lazy(() => Emulation.UserAgentMetadata).optional() }).passthrough(), "Network.setUserAgentOverride.params", "commandParams", { method: "Network.setUserAgentOverride" }); export const SetUserAgentOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Network.setUserAgentOverride.result", "commandResult", { method: "Network.setUserAgentOverride" }); +export const SetUserAgentOverrideCommand = withCdpCommand("Network.setUserAgentOverride", SetUserAgentOverrideParams, SetUserAgentOverrideResult); export const StreamResourceContentParams = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId) }).passthrough(), "Network.streamResourceContent.params", "commandParams", { method: "Network.streamResourceContent" }); export const StreamResourceContentResult = withCdpMeta(z.object({ "bufferedData": z.string() }).passthrough(), "Network.streamResourceContent.result", "commandResult", { method: "Network.streamResourceContent" }); +export const StreamResourceContentCommand = withCdpCommand("Network.streamResourceContent", StreamResourceContentParams, StreamResourceContentResult); export const GetSecurityIsolationStatusParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Network.getSecurityIsolationStatus.params", "commandParams", { method: "Network.getSecurityIsolationStatus" }); export const GetSecurityIsolationStatusResult = withCdpMeta(z.object({ "status": z.lazy(() => SecurityIsolationStatus) }).passthrough(), "Network.getSecurityIsolationStatus.result", "commandResult", { method: "Network.getSecurityIsolationStatus" }); +export const GetSecurityIsolationStatusCommand = withCdpCommand("Network.getSecurityIsolationStatus", GetSecurityIsolationStatusParams, GetSecurityIsolationStatusResult); export const EnableReportingApiParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "Network.enableReportingApi.params", "commandParams", { method: "Network.enableReportingApi" }); export const EnableReportingApiResult = withCdpMeta(z.object({ }).passthrough(), "Network.enableReportingApi.result", "commandResult", { method: "Network.enableReportingApi" }); +export const EnableReportingApiCommand = withCdpCommand("Network.enableReportingApi", EnableReportingApiParams, EnableReportingApiResult); export const EnableDeviceBoundSessionsParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "Network.enableDeviceBoundSessions.params", "commandParams", { method: "Network.enableDeviceBoundSessions" }); export const EnableDeviceBoundSessionsResult = withCdpMeta(z.object({ }).passthrough(), "Network.enableDeviceBoundSessions.result", "commandResult", { method: "Network.enableDeviceBoundSessions" }); +export const EnableDeviceBoundSessionsCommand = withCdpCommand("Network.enableDeviceBoundSessions", EnableDeviceBoundSessionsParams, EnableDeviceBoundSessionsResult); export const DeleteDeviceBoundSessionParams = withCdpMeta(z.object({ "key": z.lazy(() => DeviceBoundSessionKey) }).passthrough(), "Network.deleteDeviceBoundSession.params", "commandParams", { method: "Network.deleteDeviceBoundSession" }); export const DeleteDeviceBoundSessionResult = withCdpMeta(z.object({ }).passthrough(), "Network.deleteDeviceBoundSession.result", "commandResult", { method: "Network.deleteDeviceBoundSession" }); +export const DeleteDeviceBoundSessionCommand = withCdpCommand("Network.deleteDeviceBoundSession", DeleteDeviceBoundSessionParams, DeleteDeviceBoundSessionResult); export const FetchSchemefulSiteParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Network.fetchSchemefulSite.params", "commandParams", { method: "Network.fetchSchemefulSite" }); export const FetchSchemefulSiteResult = withCdpMeta(z.object({ "schemefulSite": z.string() }).passthrough(), "Network.fetchSchemefulSite.result", "commandResult", { method: "Network.fetchSchemefulSite" }); +export const FetchSchemefulSiteCommand = withCdpCommand("Network.fetchSchemefulSite", FetchSchemefulSiteParams, FetchSchemefulSiteResult); export const LoadNetworkResourceParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId).optional(), "url": z.string(), "options": z.lazy(() => LoadNetworkResourceOptions) }).passthrough(), "Network.loadNetworkResource.params", "commandParams", { method: "Network.loadNetworkResource" }); export const LoadNetworkResourceResult = withCdpMeta(z.object({ "resource": z.lazy(() => LoadNetworkResourcePageResult) }).passthrough(), "Network.loadNetworkResource.result", "commandResult", { method: "Network.loadNetworkResource" }); +export const LoadNetworkResourceCommand = withCdpCommand("Network.loadNetworkResource", LoadNetworkResourceParams, LoadNetworkResourceResult); export const SetCookieControlsParams = withCdpMeta(z.object({ "enableThirdPartyCookieRestriction": z.boolean() }).passthrough(), "Network.setCookieControls.params", "commandParams", { method: "Network.setCookieControls" }); export const SetCookieControlsResult = withCdpMeta(z.object({ }).passthrough(), "Network.setCookieControls.result", "commandResult", { method: "Network.setCookieControls" }); +export const SetCookieControlsCommand = withCdpCommand("Network.setCookieControls", SetCookieControlsParams, SetCookieControlsResult); export const DataReceivedEvent = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "timestamp": z.lazy(() => MonotonicTime), "dataLength": z.number().int(), "encodedDataLength": z.number().int(), "data": z.string().optional() }).passthrough(), "Network.dataReceived", "event", { phase: "event" }); export const EventSourceMessageReceivedEvent = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "timestamp": z.lazy(() => MonotonicTime), "eventName": z.string(), "eventId": z.string(), "data": z.string() }).passthrough(), "Network.eventSourceMessageReceived", "event", { phase: "event" }); export const LoadingFailedEvent = withCdpMeta(z.object({ "requestId": z.lazy(() => RequestId), "timestamp": z.lazy(() => MonotonicTime), "type": z.lazy(() => ResourceType), "errorText": z.string(), "canceled": z.boolean().optional(), "blockedReason": z.lazy(() => BlockedReason).optional(), "corsErrorStatus": z.lazy(() => CorsErrorStatus).optional() }).passthrough(), "Network.loadingFailed", "event", { phase: "event" }); @@ -451,47 +492,47 @@ export const zod = { DeviceBoundSessionEventOccurredEvent: DeviceBoundSessionEventOccurredEvent, } as const; export const commands = { - "Network.setAcceptedEncodings": { params: SetAcceptedEncodingsParams, result: SetAcceptedEncodingsResult }, - "Network.clearAcceptedEncodingsOverride": { params: ClearAcceptedEncodingsOverrideParams, result: ClearAcceptedEncodingsOverrideResult }, - "Network.canClearBrowserCache": { params: CanClearBrowserCacheParams, result: CanClearBrowserCacheResult }, - "Network.canClearBrowserCookies": { params: CanClearBrowserCookiesParams, result: CanClearBrowserCookiesResult }, - "Network.canEmulateNetworkConditions": { params: CanEmulateNetworkConditionsParams, result: CanEmulateNetworkConditionsResult }, - "Network.clearBrowserCache": { params: ClearBrowserCacheParams, result: ClearBrowserCacheResult }, - "Network.clearBrowserCookies": { params: ClearBrowserCookiesParams, result: ClearBrowserCookiesResult }, - "Network.continueInterceptedRequest": { params: ContinueInterceptedRequestParams, result: ContinueInterceptedRequestResult }, - "Network.deleteCookies": { params: DeleteCookiesParams, result: DeleteCookiesResult }, - "Network.disable": { params: DisableParams, result: DisableResult }, - "Network.emulateNetworkConditions": { params: EmulateNetworkConditionsParams, result: EmulateNetworkConditionsResult }, - "Network.emulateNetworkConditionsByRule": { params: EmulateNetworkConditionsByRuleParams, result: EmulateNetworkConditionsByRuleResult }, - "Network.overrideNetworkState": { params: OverrideNetworkStateParams, result: OverrideNetworkStateResult }, - "Network.enable": { params: EnableParams, result: EnableResult }, - "Network.configureDurableMessages": { params: ConfigureDurableMessagesParams, result: ConfigureDurableMessagesResult }, - "Network.getAllCookies": { params: GetAllCookiesParams, result: GetAllCookiesResult }, - "Network.getCertificate": { params: GetCertificateParams, result: GetCertificateResult }, - "Network.getCookies": { params: GetCookiesParams, result: GetCookiesResult }, - "Network.getResponseBody": { params: GetResponseBodyParams, result: GetResponseBodyResult }, - "Network.getRequestPostData": { params: GetRequestPostDataParams, result: GetRequestPostDataResult }, - "Network.getResponseBodyForInterception": { params: GetResponseBodyForInterceptionParams, result: GetResponseBodyForInterceptionResult }, - "Network.takeResponseBodyForInterceptionAsStream": { params: TakeResponseBodyForInterceptionAsStreamParams, result: TakeResponseBodyForInterceptionAsStreamResult }, - "Network.replayXHR": { params: ReplayXHRParams, result: ReplayXHRResult }, - "Network.searchInResponseBody": { params: SearchInResponseBodyParams, result: SearchInResponseBodyResult }, - "Network.setBlockedURLs": { params: SetBlockedURLsParams, result: SetBlockedURLsResult }, - "Network.setBypassServiceWorker": { params: SetBypassServiceWorkerParams, result: SetBypassServiceWorkerResult }, - "Network.setCacheDisabled": { params: SetCacheDisabledParams, result: SetCacheDisabledResult }, - "Network.setCookie": { params: SetCookieParams, result: SetCookieResult }, - "Network.setCookies": { params: SetCookiesParams, result: SetCookiesResult }, - "Network.setExtraHTTPHeaders": { params: SetExtraHTTPHeadersParams, result: SetExtraHTTPHeadersResult }, - "Network.setAttachDebugStack": { params: SetAttachDebugStackParams, result: SetAttachDebugStackResult }, - "Network.setRequestInterception": { params: SetRequestInterceptionParams, result: SetRequestInterceptionResult }, - "Network.setUserAgentOverride": { params: SetUserAgentOverrideParams, result: SetUserAgentOverrideResult }, - "Network.streamResourceContent": { params: StreamResourceContentParams, result: StreamResourceContentResult }, - "Network.getSecurityIsolationStatus": { params: GetSecurityIsolationStatusParams, result: GetSecurityIsolationStatusResult }, - "Network.enableReportingApi": { params: EnableReportingApiParams, result: EnableReportingApiResult }, - "Network.enableDeviceBoundSessions": { params: EnableDeviceBoundSessionsParams, result: EnableDeviceBoundSessionsResult }, - "Network.deleteDeviceBoundSession": { params: DeleteDeviceBoundSessionParams, result: DeleteDeviceBoundSessionResult }, - "Network.fetchSchemefulSite": { params: FetchSchemefulSiteParams, result: FetchSchemefulSiteResult }, - "Network.loadNetworkResource": { params: LoadNetworkResourceParams, result: LoadNetworkResourceResult }, - "Network.setCookieControls": { params: SetCookieControlsParams, result: SetCookieControlsResult }, + "Network.setAcceptedEncodings": SetAcceptedEncodingsCommand, + "Network.clearAcceptedEncodingsOverride": ClearAcceptedEncodingsOverrideCommand, + "Network.canClearBrowserCache": CanClearBrowserCacheCommand, + "Network.canClearBrowserCookies": CanClearBrowserCookiesCommand, + "Network.canEmulateNetworkConditions": CanEmulateNetworkConditionsCommand, + "Network.clearBrowserCache": ClearBrowserCacheCommand, + "Network.clearBrowserCookies": ClearBrowserCookiesCommand, + "Network.continueInterceptedRequest": ContinueInterceptedRequestCommand, + "Network.deleteCookies": DeleteCookiesCommand, + "Network.disable": DisableCommand, + "Network.emulateNetworkConditions": EmulateNetworkConditionsCommand, + "Network.emulateNetworkConditionsByRule": EmulateNetworkConditionsByRuleCommand, + "Network.overrideNetworkState": OverrideNetworkStateCommand, + "Network.enable": EnableCommand, + "Network.configureDurableMessages": ConfigureDurableMessagesCommand, + "Network.getAllCookies": GetAllCookiesCommand, + "Network.getCertificate": GetCertificateCommand, + "Network.getCookies": GetCookiesCommand, + "Network.getResponseBody": GetResponseBodyCommand, + "Network.getRequestPostData": GetRequestPostDataCommand, + "Network.getResponseBodyForInterception": GetResponseBodyForInterceptionCommand, + "Network.takeResponseBodyForInterceptionAsStream": TakeResponseBodyForInterceptionAsStreamCommand, + "Network.replayXHR": ReplayXHRCommand, + "Network.searchInResponseBody": SearchInResponseBodyCommand, + "Network.setBlockedURLs": SetBlockedURLsCommand, + "Network.setBypassServiceWorker": SetBypassServiceWorkerCommand, + "Network.setCacheDisabled": SetCacheDisabledCommand, + "Network.setCookie": SetCookieCommand, + "Network.setCookies": SetCookiesCommand, + "Network.setExtraHTTPHeaders": SetExtraHTTPHeadersCommand, + "Network.setAttachDebugStack": SetAttachDebugStackCommand, + "Network.setRequestInterception": SetRequestInterceptionCommand, + "Network.setUserAgentOverride": SetUserAgentOverrideCommand, + "Network.streamResourceContent": StreamResourceContentCommand, + "Network.getSecurityIsolationStatus": GetSecurityIsolationStatusCommand, + "Network.enableReportingApi": EnableReportingApiCommand, + "Network.enableDeviceBoundSessions": EnableDeviceBoundSessionsCommand, + "Network.deleteDeviceBoundSession": DeleteDeviceBoundSessionCommand, + "Network.fetchSchemefulSite": FetchSchemefulSiteCommand, + "Network.loadNetworkResource": LoadNetworkResourceCommand, + "Network.setCookieControls": SetCookieControlsCommand, } as const; export const events = { "Network.dataReceived": DataReceivedEvent, diff --git a/js/src/types/generated/zod/Overlay.ts b/js/src/types/generated/zod/Overlay.ts index 74eb92b9..abbad661 100644 --- a/js/src/types/generated/zod/Overlay.ts +++ b/js/src/types/generated/zod/Overlay.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Page from "./Page.js"; import * as Runtime from "./Runtime.js"; @@ -29,64 +29,94 @@ export const InspectMode = withCdpMeta(z.enum(["searchForNode", "searchForUAShad export const InspectedElementAnchorConfig = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional() }).passthrough(), "Overlay.InspectedElementAnchorConfig", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Overlay.disable.params", "commandParams", { method: "Overlay.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.disable.result", "commandResult", { method: "Overlay.disable" }); +export const DisableCommand = withCdpCommand("Overlay.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Overlay.enable.params", "commandParams", { method: "Overlay.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.enable.result", "commandResult", { method: "Overlay.enable" }); +export const EnableCommand = withCdpCommand("Overlay.enable", EnableParams, EnableResult); export const GetHighlightObjectForTestParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId), "includeDistance": z.boolean().optional(), "includeStyle": z.boolean().optional(), "colorFormat": z.lazy(() => ColorFormat).optional(), "showAccessibilityInfo": z.boolean().optional() }).passthrough(), "Overlay.getHighlightObjectForTest.params", "commandParams", { method: "Overlay.getHighlightObjectForTest" }); export const GetHighlightObjectForTestResult = withCdpMeta(z.object({ "highlight": z.record(z.string(), z.unknown()) }).passthrough(), "Overlay.getHighlightObjectForTest.result", "commandResult", { method: "Overlay.getHighlightObjectForTest" }); +export const GetHighlightObjectForTestCommand = withCdpCommand("Overlay.getHighlightObjectForTest", GetHighlightObjectForTestParams, GetHighlightObjectForTestResult); export const GetGridHighlightObjectsForTestParams = withCdpMeta(z.object({ "nodeIds": z.array(z.lazy(() => DOM.NodeId)) }).passthrough(), "Overlay.getGridHighlightObjectsForTest.params", "commandParams", { method: "Overlay.getGridHighlightObjectsForTest" }); export const GetGridHighlightObjectsForTestResult = withCdpMeta(z.object({ "highlights": z.record(z.string(), z.unknown()) }).passthrough(), "Overlay.getGridHighlightObjectsForTest.result", "commandResult", { method: "Overlay.getGridHighlightObjectsForTest" }); +export const GetGridHighlightObjectsForTestCommand = withCdpCommand("Overlay.getGridHighlightObjectsForTest", GetGridHighlightObjectsForTestParams, GetGridHighlightObjectsForTestResult); export const GetSourceOrderHighlightObjectForTestParams = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "Overlay.getSourceOrderHighlightObjectForTest.params", "commandParams", { method: "Overlay.getSourceOrderHighlightObjectForTest" }); export const GetSourceOrderHighlightObjectForTestResult = withCdpMeta(z.object({ "highlight": z.record(z.string(), z.unknown()) }).passthrough(), "Overlay.getSourceOrderHighlightObjectForTest.result", "commandResult", { method: "Overlay.getSourceOrderHighlightObjectForTest" }); +export const GetSourceOrderHighlightObjectForTestCommand = withCdpCommand("Overlay.getSourceOrderHighlightObjectForTest", GetSourceOrderHighlightObjectForTestParams, GetSourceOrderHighlightObjectForTestResult); export const HideHighlightParams = withCdpMeta(z.object({ }).passthrough(), "Overlay.hideHighlight.params", "commandParams", { method: "Overlay.hideHighlight" }); export const HideHighlightResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.hideHighlight.result", "commandResult", { method: "Overlay.hideHighlight" }); +export const HideHighlightCommand = withCdpCommand("Overlay.hideHighlight", HideHighlightParams, HideHighlightResult); export const HighlightFrameParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId), "contentColor": z.lazy(() => DOM.RGBA).optional(), "contentOutlineColor": z.lazy(() => DOM.RGBA).optional() }).passthrough(), "Overlay.highlightFrame.params", "commandParams", { method: "Overlay.highlightFrame" }); export const HighlightFrameResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.highlightFrame.result", "commandResult", { method: "Overlay.highlightFrame" }); +export const HighlightFrameCommand = withCdpCommand("Overlay.highlightFrame", HighlightFrameParams, HighlightFrameResult); export const HighlightNodeParams = withCdpMeta(z.object({ "highlightConfig": z.lazy(() => HighlightConfig), "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional(), "selector": z.string().optional() }).passthrough(), "Overlay.highlightNode.params", "commandParams", { method: "Overlay.highlightNode" }); export const HighlightNodeResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.highlightNode.result", "commandResult", { method: "Overlay.highlightNode" }); +export const HighlightNodeCommand = withCdpCommand("Overlay.highlightNode", HighlightNodeParams, HighlightNodeResult); export const HighlightQuadParams = withCdpMeta(z.object({ "quad": z.lazy(() => DOM.Quad), "color": z.lazy(() => DOM.RGBA).optional(), "outlineColor": z.lazy(() => DOM.RGBA).optional() }).passthrough(), "Overlay.highlightQuad.params", "commandParams", { method: "Overlay.highlightQuad" }); export const HighlightQuadResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.highlightQuad.result", "commandResult", { method: "Overlay.highlightQuad" }); +export const HighlightQuadCommand = withCdpCommand("Overlay.highlightQuad", HighlightQuadParams, HighlightQuadResult); export const HighlightRectParams = withCdpMeta(z.object({ "x": z.number().int(), "y": z.number().int(), "width": z.number().int(), "height": z.number().int(), "color": z.lazy(() => DOM.RGBA).optional(), "outlineColor": z.lazy(() => DOM.RGBA).optional() }).passthrough(), "Overlay.highlightRect.params", "commandParams", { method: "Overlay.highlightRect" }); export const HighlightRectResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.highlightRect.result", "commandResult", { method: "Overlay.highlightRect" }); +export const HighlightRectCommand = withCdpCommand("Overlay.highlightRect", HighlightRectParams, HighlightRectResult); export const HighlightSourceOrderParams = withCdpMeta(z.object({ "sourceOrderConfig": z.lazy(() => SourceOrderConfig), "nodeId": z.lazy(() => DOM.NodeId).optional(), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional(), "objectId": z.lazy(() => Runtime.RemoteObjectId).optional() }).passthrough(), "Overlay.highlightSourceOrder.params", "commandParams", { method: "Overlay.highlightSourceOrder" }); export const HighlightSourceOrderResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.highlightSourceOrder.result", "commandResult", { method: "Overlay.highlightSourceOrder" }); +export const HighlightSourceOrderCommand = withCdpCommand("Overlay.highlightSourceOrder", HighlightSourceOrderParams, HighlightSourceOrderResult); export const SetInspectModeParams = withCdpMeta(z.object({ "mode": z.lazy(() => InspectMode), "highlightConfig": z.lazy(() => HighlightConfig).optional() }).passthrough(), "Overlay.setInspectMode.params", "commandParams", { method: "Overlay.setInspectMode" }); export const SetInspectModeResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setInspectMode.result", "commandResult", { method: "Overlay.setInspectMode" }); +export const SetInspectModeCommand = withCdpCommand("Overlay.setInspectMode", SetInspectModeParams, SetInspectModeResult); export const SetShowAdHighlightsParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowAdHighlights.params", "commandParams", { method: "Overlay.setShowAdHighlights" }); export const SetShowAdHighlightsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowAdHighlights.result", "commandResult", { method: "Overlay.setShowAdHighlights" }); +export const SetShowAdHighlightsCommand = withCdpCommand("Overlay.setShowAdHighlights", SetShowAdHighlightsParams, SetShowAdHighlightsResult); export const SetPausedInDebuggerMessageParams = withCdpMeta(z.object({ "message": z.string().optional() }).passthrough(), "Overlay.setPausedInDebuggerMessage.params", "commandParams", { method: "Overlay.setPausedInDebuggerMessage" }); export const SetPausedInDebuggerMessageResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setPausedInDebuggerMessage.result", "commandResult", { method: "Overlay.setPausedInDebuggerMessage" }); +export const SetPausedInDebuggerMessageCommand = withCdpCommand("Overlay.setPausedInDebuggerMessage", SetPausedInDebuggerMessageParams, SetPausedInDebuggerMessageResult); export const SetShowDebugBordersParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowDebugBorders.params", "commandParams", { method: "Overlay.setShowDebugBorders" }); export const SetShowDebugBordersResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowDebugBorders.result", "commandResult", { method: "Overlay.setShowDebugBorders" }); +export const SetShowDebugBordersCommand = withCdpCommand("Overlay.setShowDebugBorders", SetShowDebugBordersParams, SetShowDebugBordersResult); export const SetShowFPSCounterParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowFPSCounter.params", "commandParams", { method: "Overlay.setShowFPSCounter" }); export const SetShowFPSCounterResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowFPSCounter.result", "commandResult", { method: "Overlay.setShowFPSCounter" }); +export const SetShowFPSCounterCommand = withCdpCommand("Overlay.setShowFPSCounter", SetShowFPSCounterParams, SetShowFPSCounterResult); export const SetShowGridOverlaysParams = withCdpMeta(z.object({ "gridNodeHighlightConfigs": z.array(z.lazy(() => GridNodeHighlightConfig)) }).passthrough(), "Overlay.setShowGridOverlays.params", "commandParams", { method: "Overlay.setShowGridOverlays" }); export const SetShowGridOverlaysResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowGridOverlays.result", "commandResult", { method: "Overlay.setShowGridOverlays" }); +export const SetShowGridOverlaysCommand = withCdpCommand("Overlay.setShowGridOverlays", SetShowGridOverlaysParams, SetShowGridOverlaysResult); export const SetShowFlexOverlaysParams = withCdpMeta(z.object({ "flexNodeHighlightConfigs": z.array(z.lazy(() => FlexNodeHighlightConfig)) }).passthrough(), "Overlay.setShowFlexOverlays.params", "commandParams", { method: "Overlay.setShowFlexOverlays" }); export const SetShowFlexOverlaysResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowFlexOverlays.result", "commandResult", { method: "Overlay.setShowFlexOverlays" }); +export const SetShowFlexOverlaysCommand = withCdpCommand("Overlay.setShowFlexOverlays", SetShowFlexOverlaysParams, SetShowFlexOverlaysResult); export const SetShowScrollSnapOverlaysParams = withCdpMeta(z.object({ "scrollSnapHighlightConfigs": z.array(z.lazy(() => ScrollSnapHighlightConfig)) }).passthrough(), "Overlay.setShowScrollSnapOverlays.params", "commandParams", { method: "Overlay.setShowScrollSnapOverlays" }); export const SetShowScrollSnapOverlaysResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowScrollSnapOverlays.result", "commandResult", { method: "Overlay.setShowScrollSnapOverlays" }); +export const SetShowScrollSnapOverlaysCommand = withCdpCommand("Overlay.setShowScrollSnapOverlays", SetShowScrollSnapOverlaysParams, SetShowScrollSnapOverlaysResult); export const SetShowContainerQueryOverlaysParams = withCdpMeta(z.object({ "containerQueryHighlightConfigs": z.array(z.lazy(() => ContainerQueryHighlightConfig)) }).passthrough(), "Overlay.setShowContainerQueryOverlays.params", "commandParams", { method: "Overlay.setShowContainerQueryOverlays" }); export const SetShowContainerQueryOverlaysResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowContainerQueryOverlays.result", "commandResult", { method: "Overlay.setShowContainerQueryOverlays" }); +export const SetShowContainerQueryOverlaysCommand = withCdpCommand("Overlay.setShowContainerQueryOverlays", SetShowContainerQueryOverlaysParams, SetShowContainerQueryOverlaysResult); export const SetShowInspectedElementAnchorParams = withCdpMeta(z.object({ "inspectedElementAnchorConfig": z.lazy(() => InspectedElementAnchorConfig) }).passthrough(), "Overlay.setShowInspectedElementAnchor.params", "commandParams", { method: "Overlay.setShowInspectedElementAnchor" }); export const SetShowInspectedElementAnchorResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowInspectedElementAnchor.result", "commandResult", { method: "Overlay.setShowInspectedElementAnchor" }); +export const SetShowInspectedElementAnchorCommand = withCdpCommand("Overlay.setShowInspectedElementAnchor", SetShowInspectedElementAnchorParams, SetShowInspectedElementAnchorResult); export const SetShowPaintRectsParams = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Overlay.setShowPaintRects.params", "commandParams", { method: "Overlay.setShowPaintRects" }); export const SetShowPaintRectsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowPaintRects.result", "commandResult", { method: "Overlay.setShowPaintRects" }); +export const SetShowPaintRectsCommand = withCdpCommand("Overlay.setShowPaintRects", SetShowPaintRectsParams, SetShowPaintRectsResult); export const SetShowLayoutShiftRegionsParams = withCdpMeta(z.object({ "result": z.boolean() }).passthrough(), "Overlay.setShowLayoutShiftRegions.params", "commandParams", { method: "Overlay.setShowLayoutShiftRegions" }); export const SetShowLayoutShiftRegionsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowLayoutShiftRegions.result", "commandResult", { method: "Overlay.setShowLayoutShiftRegions" }); +export const SetShowLayoutShiftRegionsCommand = withCdpCommand("Overlay.setShowLayoutShiftRegions", SetShowLayoutShiftRegionsParams, SetShowLayoutShiftRegionsResult); export const SetShowScrollBottleneckRectsParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowScrollBottleneckRects.params", "commandParams", { method: "Overlay.setShowScrollBottleneckRects" }); export const SetShowScrollBottleneckRectsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowScrollBottleneckRects.result", "commandResult", { method: "Overlay.setShowScrollBottleneckRects" }); +export const SetShowScrollBottleneckRectsCommand = withCdpCommand("Overlay.setShowScrollBottleneckRects", SetShowScrollBottleneckRectsParams, SetShowScrollBottleneckRectsResult); export const SetShowHitTestBordersParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowHitTestBorders.params", "commandParams", { method: "Overlay.setShowHitTestBorders" }); export const SetShowHitTestBordersResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowHitTestBorders.result", "commandResult", { method: "Overlay.setShowHitTestBorders" }); +export const SetShowHitTestBordersCommand = withCdpCommand("Overlay.setShowHitTestBorders", SetShowHitTestBordersParams, SetShowHitTestBordersResult); export const SetShowWebVitalsParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowWebVitals.params", "commandParams", { method: "Overlay.setShowWebVitals" }); export const SetShowWebVitalsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowWebVitals.result", "commandResult", { method: "Overlay.setShowWebVitals" }); +export const SetShowWebVitalsCommand = withCdpCommand("Overlay.setShowWebVitals", SetShowWebVitalsParams, SetShowWebVitalsResult); export const SetShowViewportSizeOnResizeParams = withCdpMeta(z.object({ "show": z.boolean() }).passthrough(), "Overlay.setShowViewportSizeOnResize.params", "commandParams", { method: "Overlay.setShowViewportSizeOnResize" }); export const SetShowViewportSizeOnResizeResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowViewportSizeOnResize.result", "commandResult", { method: "Overlay.setShowViewportSizeOnResize" }); +export const SetShowViewportSizeOnResizeCommand = withCdpCommand("Overlay.setShowViewportSizeOnResize", SetShowViewportSizeOnResizeParams, SetShowViewportSizeOnResizeResult); export const SetShowHingeParams = withCdpMeta(z.object({ "hingeConfig": z.lazy(() => HingeConfig).optional() }).passthrough(), "Overlay.setShowHinge.params", "commandParams", { method: "Overlay.setShowHinge" }); export const SetShowHingeResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowHinge.result", "commandResult", { method: "Overlay.setShowHinge" }); +export const SetShowHingeCommand = withCdpCommand("Overlay.setShowHinge", SetShowHingeParams, SetShowHingeResult); export const SetShowIsolatedElementsParams = withCdpMeta(z.object({ "isolatedElementHighlightConfigs": z.array(z.lazy(() => IsolatedElementHighlightConfig)) }).passthrough(), "Overlay.setShowIsolatedElements.params", "commandParams", { method: "Overlay.setShowIsolatedElements" }); export const SetShowIsolatedElementsResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowIsolatedElements.result", "commandResult", { method: "Overlay.setShowIsolatedElements" }); +export const SetShowIsolatedElementsCommand = withCdpCommand("Overlay.setShowIsolatedElements", SetShowIsolatedElementsParams, SetShowIsolatedElementsResult); export const SetShowWindowControlsOverlayParams = withCdpMeta(z.object({ "windowControlsOverlayConfig": z.lazy(() => WindowControlsOverlayConfig).optional() }).passthrough(), "Overlay.setShowWindowControlsOverlay.params", "commandParams", { method: "Overlay.setShowWindowControlsOverlay" }); export const SetShowWindowControlsOverlayResult = withCdpMeta(z.object({ }).passthrough(), "Overlay.setShowWindowControlsOverlay.result", "commandResult", { method: "Overlay.setShowWindowControlsOverlay" }); +export const SetShowWindowControlsOverlayCommand = withCdpCommand("Overlay.setShowWindowControlsOverlay", SetShowWindowControlsOverlayParams, SetShowWindowControlsOverlayResult); export const InspectNodeRequestedEvent = withCdpMeta(z.object({ "backendNodeId": z.lazy(() => DOM.BackendNodeId) }).passthrough(), "Overlay.inspectNodeRequested", "event", { phase: "event" }); export const NodeHighlightRequestedEvent = withCdpMeta(z.object({ "nodeId": z.lazy(() => DOM.NodeId) }).passthrough(), "Overlay.nodeHighlightRequested", "event", { phase: "event" }); export const ScreenshotRequestedEvent = withCdpMeta(z.object({ "viewport": z.lazy(() => Page.Viewport) }).passthrough(), "Overlay.screenshotRequested", "event", { phase: "event" }); @@ -184,36 +214,36 @@ export const zod = { InspectModeCanceledEvent: InspectModeCanceledEvent, } as const; export const commands = { - "Overlay.disable": { params: DisableParams, result: DisableResult }, - "Overlay.enable": { params: EnableParams, result: EnableResult }, - "Overlay.getHighlightObjectForTest": { params: GetHighlightObjectForTestParams, result: GetHighlightObjectForTestResult }, - "Overlay.getGridHighlightObjectsForTest": { params: GetGridHighlightObjectsForTestParams, result: GetGridHighlightObjectsForTestResult }, - "Overlay.getSourceOrderHighlightObjectForTest": { params: GetSourceOrderHighlightObjectForTestParams, result: GetSourceOrderHighlightObjectForTestResult }, - "Overlay.hideHighlight": { params: HideHighlightParams, result: HideHighlightResult }, - "Overlay.highlightFrame": { params: HighlightFrameParams, result: HighlightFrameResult }, - "Overlay.highlightNode": { params: HighlightNodeParams, result: HighlightNodeResult }, - "Overlay.highlightQuad": { params: HighlightQuadParams, result: HighlightQuadResult }, - "Overlay.highlightRect": { params: HighlightRectParams, result: HighlightRectResult }, - "Overlay.highlightSourceOrder": { params: HighlightSourceOrderParams, result: HighlightSourceOrderResult }, - "Overlay.setInspectMode": { params: SetInspectModeParams, result: SetInspectModeResult }, - "Overlay.setShowAdHighlights": { params: SetShowAdHighlightsParams, result: SetShowAdHighlightsResult }, - "Overlay.setPausedInDebuggerMessage": { params: SetPausedInDebuggerMessageParams, result: SetPausedInDebuggerMessageResult }, - "Overlay.setShowDebugBorders": { params: SetShowDebugBordersParams, result: SetShowDebugBordersResult }, - "Overlay.setShowFPSCounter": { params: SetShowFPSCounterParams, result: SetShowFPSCounterResult }, - "Overlay.setShowGridOverlays": { params: SetShowGridOverlaysParams, result: SetShowGridOverlaysResult }, - "Overlay.setShowFlexOverlays": { params: SetShowFlexOverlaysParams, result: SetShowFlexOverlaysResult }, - "Overlay.setShowScrollSnapOverlays": { params: SetShowScrollSnapOverlaysParams, result: SetShowScrollSnapOverlaysResult }, - "Overlay.setShowContainerQueryOverlays": { params: SetShowContainerQueryOverlaysParams, result: SetShowContainerQueryOverlaysResult }, - "Overlay.setShowInspectedElementAnchor": { params: SetShowInspectedElementAnchorParams, result: SetShowInspectedElementAnchorResult }, - "Overlay.setShowPaintRects": { params: SetShowPaintRectsParams, result: SetShowPaintRectsResult }, - "Overlay.setShowLayoutShiftRegions": { params: SetShowLayoutShiftRegionsParams, result: SetShowLayoutShiftRegionsResult }, - "Overlay.setShowScrollBottleneckRects": { params: SetShowScrollBottleneckRectsParams, result: SetShowScrollBottleneckRectsResult }, - "Overlay.setShowHitTestBorders": { params: SetShowHitTestBordersParams, result: SetShowHitTestBordersResult }, - "Overlay.setShowWebVitals": { params: SetShowWebVitalsParams, result: SetShowWebVitalsResult }, - "Overlay.setShowViewportSizeOnResize": { params: SetShowViewportSizeOnResizeParams, result: SetShowViewportSizeOnResizeResult }, - "Overlay.setShowHinge": { params: SetShowHingeParams, result: SetShowHingeResult }, - "Overlay.setShowIsolatedElements": { params: SetShowIsolatedElementsParams, result: SetShowIsolatedElementsResult }, - "Overlay.setShowWindowControlsOverlay": { params: SetShowWindowControlsOverlayParams, result: SetShowWindowControlsOverlayResult }, + "Overlay.disable": DisableCommand, + "Overlay.enable": EnableCommand, + "Overlay.getHighlightObjectForTest": GetHighlightObjectForTestCommand, + "Overlay.getGridHighlightObjectsForTest": GetGridHighlightObjectsForTestCommand, + "Overlay.getSourceOrderHighlightObjectForTest": GetSourceOrderHighlightObjectForTestCommand, + "Overlay.hideHighlight": HideHighlightCommand, + "Overlay.highlightFrame": HighlightFrameCommand, + "Overlay.highlightNode": HighlightNodeCommand, + "Overlay.highlightQuad": HighlightQuadCommand, + "Overlay.highlightRect": HighlightRectCommand, + "Overlay.highlightSourceOrder": HighlightSourceOrderCommand, + "Overlay.setInspectMode": SetInspectModeCommand, + "Overlay.setShowAdHighlights": SetShowAdHighlightsCommand, + "Overlay.setPausedInDebuggerMessage": SetPausedInDebuggerMessageCommand, + "Overlay.setShowDebugBorders": SetShowDebugBordersCommand, + "Overlay.setShowFPSCounter": SetShowFPSCounterCommand, + "Overlay.setShowGridOverlays": SetShowGridOverlaysCommand, + "Overlay.setShowFlexOverlays": SetShowFlexOverlaysCommand, + "Overlay.setShowScrollSnapOverlays": SetShowScrollSnapOverlaysCommand, + "Overlay.setShowContainerQueryOverlays": SetShowContainerQueryOverlaysCommand, + "Overlay.setShowInspectedElementAnchor": SetShowInspectedElementAnchorCommand, + "Overlay.setShowPaintRects": SetShowPaintRectsCommand, + "Overlay.setShowLayoutShiftRegions": SetShowLayoutShiftRegionsCommand, + "Overlay.setShowScrollBottleneckRects": SetShowScrollBottleneckRectsCommand, + "Overlay.setShowHitTestBorders": SetShowHitTestBordersCommand, + "Overlay.setShowWebVitals": SetShowWebVitalsCommand, + "Overlay.setShowViewportSizeOnResize": SetShowViewportSizeOnResizeCommand, + "Overlay.setShowHinge": SetShowHingeCommand, + "Overlay.setShowIsolatedElements": SetShowIsolatedElementsCommand, + "Overlay.setShowWindowControlsOverlay": SetShowWindowControlsOverlayCommand, } as const; export const events = { "Overlay.inspectNodeRequested": InspectNodeRequestedEvent, diff --git a/js/src/types/generated/zod/PWA.ts b/js/src/types/generated/zod/PWA.ts index 7723159d..73f87e80 100644 --- a/js/src/types/generated/zod/PWA.ts +++ b/js/src/types/generated/zod/PWA.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Target from "./Target.js"; export const FileHandlerAccept = withCdpMeta(z.object({ "mediaType": z.string(), "fileExtensions": z.array(z.string()) }).passthrough(), "PWA.FileHandlerAccept", "type"); @@ -9,18 +9,25 @@ export const FileHandler = withCdpMeta(z.object({ "action": z.string(), "accepts export const DisplayMode = withCdpMeta(z.enum(["standalone", "browser"]), "PWA.DisplayMode", "type"); export const GetOsAppStateParams = withCdpMeta(z.object({ "manifestId": z.string() }).passthrough(), "PWA.getOsAppState.params", "commandParams", { method: "PWA.getOsAppState" }); export const GetOsAppStateResult = withCdpMeta(z.object({ "badgeCount": z.number().int(), "fileHandlers": z.array(z.lazy(() => FileHandler)) }).passthrough(), "PWA.getOsAppState.result", "commandResult", { method: "PWA.getOsAppState" }); +export const GetOsAppStateCommand = withCdpCommand("PWA.getOsAppState", GetOsAppStateParams, GetOsAppStateResult); export const InstallParams = withCdpMeta(z.object({ "manifestId": z.string(), "installUrlOrBundleUrl": z.string().optional() }).passthrough(), "PWA.install.params", "commandParams", { method: "PWA.install" }); export const InstallResult = withCdpMeta(z.object({ }).passthrough(), "PWA.install.result", "commandResult", { method: "PWA.install" }); +export const InstallCommand = withCdpCommand("PWA.install", InstallParams, InstallResult); export const UninstallParams = withCdpMeta(z.object({ "manifestId": z.string() }).passthrough(), "PWA.uninstall.params", "commandParams", { method: "PWA.uninstall" }); export const UninstallResult = withCdpMeta(z.object({ }).passthrough(), "PWA.uninstall.result", "commandResult", { method: "PWA.uninstall" }); +export const UninstallCommand = withCdpCommand("PWA.uninstall", UninstallParams, UninstallResult); export const LaunchParams = withCdpMeta(z.object({ "manifestId": z.string(), "url": z.string().optional() }).passthrough(), "PWA.launch.params", "commandParams", { method: "PWA.launch" }); export const LaunchResult = withCdpMeta(z.object({ "targetId": z.lazy(() => Target.TargetID) }).passthrough(), "PWA.launch.result", "commandResult", { method: "PWA.launch" }); +export const LaunchCommand = withCdpCommand("PWA.launch", LaunchParams, LaunchResult); export const LaunchFilesInAppParams = withCdpMeta(z.object({ "manifestId": z.string(), "files": z.array(z.string()) }).passthrough(), "PWA.launchFilesInApp.params", "commandParams", { method: "PWA.launchFilesInApp" }); export const LaunchFilesInAppResult = withCdpMeta(z.object({ "targetIds": z.array(z.lazy(() => Target.TargetID)) }).passthrough(), "PWA.launchFilesInApp.result", "commandResult", { method: "PWA.launchFilesInApp" }); +export const LaunchFilesInAppCommand = withCdpCommand("PWA.launchFilesInApp", LaunchFilesInAppParams, LaunchFilesInAppResult); export const OpenCurrentPageInAppParams = withCdpMeta(z.object({ "manifestId": z.string() }).passthrough(), "PWA.openCurrentPageInApp.params", "commandParams", { method: "PWA.openCurrentPageInApp" }); export const OpenCurrentPageInAppResult = withCdpMeta(z.object({ }).passthrough(), "PWA.openCurrentPageInApp.result", "commandResult", { method: "PWA.openCurrentPageInApp" }); +export const OpenCurrentPageInAppCommand = withCdpCommand("PWA.openCurrentPageInApp", OpenCurrentPageInAppParams, OpenCurrentPageInAppResult); export const ChangeAppUserSettingsParams = withCdpMeta(z.object({ "manifestId": z.string(), "linkCapturing": z.boolean().optional(), "displayMode": z.lazy(() => DisplayMode).optional() }).passthrough(), "PWA.changeAppUserSettings.params", "commandParams", { method: "PWA.changeAppUserSettings" }); export const ChangeAppUserSettingsResult = withCdpMeta(z.object({ }).passthrough(), "PWA.changeAppUserSettings.result", "commandResult", { method: "PWA.changeAppUserSettings" }); +export const ChangeAppUserSettingsCommand = withCdpCommand("PWA.changeAppUserSettings", ChangeAppUserSettingsParams, ChangeAppUserSettingsResult); export const zod = { FileHandlerAccept: FileHandlerAccept, @@ -42,13 +49,13 @@ export const zod = { ChangeAppUserSettingsResult: ChangeAppUserSettingsResult, } as const; export const commands = { - "PWA.getOsAppState": { params: GetOsAppStateParams, result: GetOsAppStateResult }, - "PWA.install": { params: InstallParams, result: InstallResult }, - "PWA.uninstall": { params: UninstallParams, result: UninstallResult }, - "PWA.launch": { params: LaunchParams, result: LaunchResult }, - "PWA.launchFilesInApp": { params: LaunchFilesInAppParams, result: LaunchFilesInAppResult }, - "PWA.openCurrentPageInApp": { params: OpenCurrentPageInAppParams, result: OpenCurrentPageInAppResult }, - "PWA.changeAppUserSettings": { params: ChangeAppUserSettingsParams, result: ChangeAppUserSettingsResult }, + "PWA.getOsAppState": GetOsAppStateCommand, + "PWA.install": InstallCommand, + "PWA.uninstall": UninstallCommand, + "PWA.launch": LaunchCommand, + "PWA.launchFilesInApp": LaunchFilesInAppCommand, + "PWA.openCurrentPageInApp": OpenCurrentPageInAppCommand, + "PWA.changeAppUserSettings": ChangeAppUserSettingsCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Page.ts b/js/src/types/generated/zod/Page.ts index 75d3bf4b..3a4facf3 100644 --- a/js/src/types/generated/zod/Page.ts +++ b/js/src/types/generated/zod/Page.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Debugger from "./Debugger.js"; import * as Emulation from "./Emulation.js"; @@ -69,126 +69,187 @@ export const BackForwardCacheNotRestoredExplanation = withCdpMeta(z.object({ "ty export const BackForwardCacheNotRestoredExplanationTree = withCdpMeta(z.object({ "url": z.string(), "explanations": z.array(z.lazy(() => BackForwardCacheNotRestoredExplanation)), "children": z.array(z.lazy(() => BackForwardCacheNotRestoredExplanationTree)) }).passthrough(), "Page.BackForwardCacheNotRestoredExplanationTree", "type"); export const AddScriptToEvaluateOnLoadParams = withCdpMeta(z.object({ "scriptSource": z.string() }).passthrough(), "Page.addScriptToEvaluateOnLoad.params", "commandParams", { method: "Page.addScriptToEvaluateOnLoad" }); export const AddScriptToEvaluateOnLoadResult = withCdpMeta(z.object({ "identifier": z.lazy(() => ScriptIdentifier) }).passthrough(), "Page.addScriptToEvaluateOnLoad.result", "commandResult", { method: "Page.addScriptToEvaluateOnLoad" }); +export const AddScriptToEvaluateOnLoadCommand = withCdpCommand("Page.addScriptToEvaluateOnLoad", AddScriptToEvaluateOnLoadParams, AddScriptToEvaluateOnLoadResult); export const AddScriptToEvaluateOnNewDocumentParams = withCdpMeta(z.object({ "source": z.string(), "worldName": z.string().optional(), "includeCommandLineAPI": z.boolean().optional(), "runImmediately": z.boolean().optional() }).passthrough(), "Page.addScriptToEvaluateOnNewDocument.params", "commandParams", { method: "Page.addScriptToEvaluateOnNewDocument" }); export const AddScriptToEvaluateOnNewDocumentResult = withCdpMeta(z.object({ "identifier": z.lazy(() => ScriptIdentifier) }).passthrough(), "Page.addScriptToEvaluateOnNewDocument.result", "commandResult", { method: "Page.addScriptToEvaluateOnNewDocument" }); +export const AddScriptToEvaluateOnNewDocumentCommand = withCdpCommand("Page.addScriptToEvaluateOnNewDocument", AddScriptToEvaluateOnNewDocumentParams, AddScriptToEvaluateOnNewDocumentResult); export const BringToFrontParams = withCdpMeta(z.object({ }).passthrough(), "Page.bringToFront.params", "commandParams", { method: "Page.bringToFront" }); export const BringToFrontResult = withCdpMeta(z.object({ }).passthrough(), "Page.bringToFront.result", "commandResult", { method: "Page.bringToFront" }); +export const BringToFrontCommand = withCdpCommand("Page.bringToFront", BringToFrontParams, BringToFrontResult); export const CaptureScreenshotParams = withCdpMeta(z.object({ "format": z.enum(["jpeg", "png", "webp"]).optional(), "quality": z.number().int().optional(), "clip": z.lazy(() => Viewport).optional(), "fromSurface": z.boolean().optional(), "captureBeyondViewport": z.boolean().optional(), "optimizeForSpeed": z.boolean().optional() }).passthrough(), "Page.captureScreenshot.params", "commandParams", { method: "Page.captureScreenshot" }); export const CaptureScreenshotResult = withCdpMeta(z.object({ "data": z.string() }).passthrough(), "Page.captureScreenshot.result", "commandResult", { method: "Page.captureScreenshot" }); +export const CaptureScreenshotCommand = withCdpCommand("Page.captureScreenshot", CaptureScreenshotParams, CaptureScreenshotResult); export const CaptureSnapshotParams = withCdpMeta(z.object({ "format": z.enum(["mhtml"]).optional() }).passthrough(), "Page.captureSnapshot.params", "commandParams", { method: "Page.captureSnapshot" }); export const CaptureSnapshotResult = withCdpMeta(z.object({ "data": z.string() }).passthrough(), "Page.captureSnapshot.result", "commandResult", { method: "Page.captureSnapshot" }); +export const CaptureSnapshotCommand = withCdpCommand("Page.captureSnapshot", CaptureSnapshotParams, CaptureSnapshotResult); export const ClearDeviceMetricsOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Page.clearDeviceMetricsOverride.params", "commandParams", { method: "Page.clearDeviceMetricsOverride" }); export const ClearDeviceMetricsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.clearDeviceMetricsOverride.result", "commandResult", { method: "Page.clearDeviceMetricsOverride" }); +export const ClearDeviceMetricsOverrideCommand = withCdpCommand("Page.clearDeviceMetricsOverride", ClearDeviceMetricsOverrideParams, ClearDeviceMetricsOverrideResult); export const ClearDeviceOrientationOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Page.clearDeviceOrientationOverride.params", "commandParams", { method: "Page.clearDeviceOrientationOverride" }); export const ClearDeviceOrientationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.clearDeviceOrientationOverride.result", "commandResult", { method: "Page.clearDeviceOrientationOverride" }); +export const ClearDeviceOrientationOverrideCommand = withCdpCommand("Page.clearDeviceOrientationOverride", ClearDeviceOrientationOverrideParams, ClearDeviceOrientationOverrideResult); export const ClearGeolocationOverrideParams = withCdpMeta(z.object({ }).passthrough(), "Page.clearGeolocationOverride.params", "commandParams", { method: "Page.clearGeolocationOverride" }); export const ClearGeolocationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.clearGeolocationOverride.result", "commandResult", { method: "Page.clearGeolocationOverride" }); +export const ClearGeolocationOverrideCommand = withCdpCommand("Page.clearGeolocationOverride", ClearGeolocationOverrideParams, ClearGeolocationOverrideResult); export const CreateIsolatedWorldParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "worldName": z.string().optional(), "grantUniveralAccess": z.boolean().optional() }).passthrough(), "Page.createIsolatedWorld.params", "commandParams", { method: "Page.createIsolatedWorld" }); export const CreateIsolatedWorldResult = withCdpMeta(z.object({ "executionContextId": z.lazy(() => Runtime.ExecutionContextId) }).passthrough(), "Page.createIsolatedWorld.result", "commandResult", { method: "Page.createIsolatedWorld" }); +export const CreateIsolatedWorldCommand = withCdpCommand("Page.createIsolatedWorld", CreateIsolatedWorldParams, CreateIsolatedWorldResult); export const DeleteCookieParams = withCdpMeta(z.object({ "cookieName": z.string(), "url": z.string() }).passthrough(), "Page.deleteCookie.params", "commandParams", { method: "Page.deleteCookie" }); export const DeleteCookieResult = withCdpMeta(z.object({ }).passthrough(), "Page.deleteCookie.result", "commandResult", { method: "Page.deleteCookie" }); +export const DeleteCookieCommand = withCdpCommand("Page.deleteCookie", DeleteCookieParams, DeleteCookieResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Page.disable.params", "commandParams", { method: "Page.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Page.disable.result", "commandResult", { method: "Page.disable" }); +export const DisableCommand = withCdpCommand("Page.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ "enableFileChooserOpenedEvent": z.boolean().optional() }).passthrough(), "Page.enable.params", "commandParams", { method: "Page.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Page.enable.result", "commandResult", { method: "Page.enable" }); +export const EnableCommand = withCdpCommand("Page.enable", EnableParams, EnableResult); export const GetAppManifestParams = withCdpMeta(z.object({ "manifestId": z.string().optional() }).passthrough(), "Page.getAppManifest.params", "commandParams", { method: "Page.getAppManifest" }); export const GetAppManifestResult = withCdpMeta(z.object({ "url": z.string(), "errors": z.array(z.lazy(() => AppManifestError)), "data": z.string().optional(), "parsed": z.lazy(() => AppManifestParsedProperties).optional(), "manifest": z.lazy(() => WebAppManifest) }).passthrough(), "Page.getAppManifest.result", "commandResult", { method: "Page.getAppManifest" }); +export const GetAppManifestCommand = withCdpCommand("Page.getAppManifest", GetAppManifestParams, GetAppManifestResult); export const GetInstallabilityErrorsParams = withCdpMeta(z.object({ }).passthrough(), "Page.getInstallabilityErrors.params", "commandParams", { method: "Page.getInstallabilityErrors" }); export const GetInstallabilityErrorsResult = withCdpMeta(z.object({ "installabilityErrors": z.array(z.lazy(() => InstallabilityError)) }).passthrough(), "Page.getInstallabilityErrors.result", "commandResult", { method: "Page.getInstallabilityErrors" }); +export const GetInstallabilityErrorsCommand = withCdpCommand("Page.getInstallabilityErrors", GetInstallabilityErrorsParams, GetInstallabilityErrorsResult); export const GetManifestIconsParams = withCdpMeta(z.object({ }).passthrough(), "Page.getManifestIcons.params", "commandParams", { method: "Page.getManifestIcons" }); export const GetManifestIconsResult = withCdpMeta(z.object({ "primaryIcon": z.string().optional() }).passthrough(), "Page.getManifestIcons.result", "commandResult", { method: "Page.getManifestIcons" }); +export const GetManifestIconsCommand = withCdpCommand("Page.getManifestIcons", GetManifestIconsParams, GetManifestIconsResult); export const GetAppIdParams = withCdpMeta(z.object({ }).passthrough(), "Page.getAppId.params", "commandParams", { method: "Page.getAppId" }); export const GetAppIdResult = withCdpMeta(z.object({ "appId": z.string().optional(), "recommendedId": z.string().optional() }).passthrough(), "Page.getAppId.result", "commandResult", { method: "Page.getAppId" }); +export const GetAppIdCommand = withCdpCommand("Page.getAppId", GetAppIdParams, GetAppIdResult); export const GetAdScriptAncestryParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId) }).passthrough(), "Page.getAdScriptAncestry.params", "commandParams", { method: "Page.getAdScriptAncestry" }); export const GetAdScriptAncestryResult = withCdpMeta(z.object({ "adScriptAncestry": z.lazy(() => Network.AdAncestry).optional() }).passthrough(), "Page.getAdScriptAncestry.result", "commandResult", { method: "Page.getAdScriptAncestry" }); +export const GetAdScriptAncestryCommand = withCdpCommand("Page.getAdScriptAncestry", GetAdScriptAncestryParams, GetAdScriptAncestryResult); export const GetFrameTreeParams = withCdpMeta(z.object({ }).passthrough(), "Page.getFrameTree.params", "commandParams", { method: "Page.getFrameTree" }); export const GetFrameTreeResult = withCdpMeta(z.object({ "frameTree": z.lazy(() => FrameTree) }).passthrough(), "Page.getFrameTree.result", "commandResult", { method: "Page.getFrameTree" }); +export const GetFrameTreeCommand = withCdpCommand("Page.getFrameTree", GetFrameTreeParams, GetFrameTreeResult); export const GetLayoutMetricsParams = withCdpMeta(z.object({ }).passthrough(), "Page.getLayoutMetrics.params", "commandParams", { method: "Page.getLayoutMetrics" }); export const GetLayoutMetricsResult = withCdpMeta(z.object({ "layoutViewport": z.lazy(() => LayoutViewport), "visualViewport": z.lazy(() => VisualViewport), "contentSize": z.lazy(() => DOM.Rect), "cssLayoutViewport": z.lazy(() => LayoutViewport), "cssVisualViewport": z.lazy(() => VisualViewport), "cssContentSize": z.lazy(() => DOM.Rect) }).passthrough(), "Page.getLayoutMetrics.result", "commandResult", { method: "Page.getLayoutMetrics" }); +export const GetLayoutMetricsCommand = withCdpCommand("Page.getLayoutMetrics", GetLayoutMetricsParams, GetLayoutMetricsResult); export const GetNavigationHistoryParams = withCdpMeta(z.object({ }).passthrough(), "Page.getNavigationHistory.params", "commandParams", { method: "Page.getNavigationHistory" }); export const GetNavigationHistoryResult = withCdpMeta(z.object({ "currentIndex": z.number().int(), "entries": z.array(z.lazy(() => NavigationEntry)) }).passthrough(), "Page.getNavigationHistory.result", "commandResult", { method: "Page.getNavigationHistory" }); +export const GetNavigationHistoryCommand = withCdpCommand("Page.getNavigationHistory", GetNavigationHistoryParams, GetNavigationHistoryResult); export const ResetNavigationHistoryParams = withCdpMeta(z.object({ }).passthrough(), "Page.resetNavigationHistory.params", "commandParams", { method: "Page.resetNavigationHistory" }); export const ResetNavigationHistoryResult = withCdpMeta(z.object({ }).passthrough(), "Page.resetNavigationHistory.result", "commandResult", { method: "Page.resetNavigationHistory" }); +export const ResetNavigationHistoryCommand = withCdpCommand("Page.resetNavigationHistory", ResetNavigationHistoryParams, ResetNavigationHistoryResult); export const GetResourceContentParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "url": z.string() }).passthrough(), "Page.getResourceContent.params", "commandParams", { method: "Page.getResourceContent" }); export const GetResourceContentResult = withCdpMeta(z.object({ "content": z.string(), "base64Encoded": z.boolean() }).passthrough(), "Page.getResourceContent.result", "commandResult", { method: "Page.getResourceContent" }); +export const GetResourceContentCommand = withCdpCommand("Page.getResourceContent", GetResourceContentParams, GetResourceContentResult); export const GetResourceTreeParams = withCdpMeta(z.object({ }).passthrough(), "Page.getResourceTree.params", "commandParams", { method: "Page.getResourceTree" }); export const GetResourceTreeResult = withCdpMeta(z.object({ "frameTree": z.lazy(() => FrameResourceTree) }).passthrough(), "Page.getResourceTree.result", "commandResult", { method: "Page.getResourceTree" }); +export const GetResourceTreeCommand = withCdpCommand("Page.getResourceTree", GetResourceTreeParams, GetResourceTreeResult); export const HandleJavaScriptDialogParams = withCdpMeta(z.object({ "accept": z.boolean(), "promptText": z.string().optional() }).passthrough(), "Page.handleJavaScriptDialog.params", "commandParams", { method: "Page.handleJavaScriptDialog" }); export const HandleJavaScriptDialogResult = withCdpMeta(z.object({ }).passthrough(), "Page.handleJavaScriptDialog.result", "commandResult", { method: "Page.handleJavaScriptDialog" }); +export const HandleJavaScriptDialogCommand = withCdpCommand("Page.handleJavaScriptDialog", HandleJavaScriptDialogParams, HandleJavaScriptDialogResult); export const NavigateParams = withCdpMeta(z.object({ "url": z.string(), "referrer": z.string().optional(), "transitionType": z.lazy(() => TransitionType).optional(), "frameId": z.lazy(() => FrameId).optional(), "referrerPolicy": z.lazy(() => ReferrerPolicy).optional() }).passthrough(), "Page.navigate.params", "commandParams", { method: "Page.navigate" }); export const NavigateResult = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "loaderId": z.lazy(() => Network.LoaderId).optional(), "errorText": z.string().optional(), "isDownload": z.boolean().optional() }).passthrough(), "Page.navigate.result", "commandResult", { method: "Page.navigate" }); +export const NavigateCommand = withCdpCommand("Page.navigate", NavigateParams, NavigateResult); export const NavigateToHistoryEntryParams = withCdpMeta(z.object({ "entryId": z.number().int() }).passthrough(), "Page.navigateToHistoryEntry.params", "commandParams", { method: "Page.navigateToHistoryEntry" }); export const NavigateToHistoryEntryResult = withCdpMeta(z.object({ }).passthrough(), "Page.navigateToHistoryEntry.result", "commandResult", { method: "Page.navigateToHistoryEntry" }); +export const NavigateToHistoryEntryCommand = withCdpCommand("Page.navigateToHistoryEntry", NavigateToHistoryEntryParams, NavigateToHistoryEntryResult); export const PrintToPDFParams = withCdpMeta(z.object({ "landscape": z.boolean().optional(), "displayHeaderFooter": z.boolean().optional(), "printBackground": z.boolean().optional(), "scale": z.number().optional(), "paperWidth": z.number().optional(), "paperHeight": z.number().optional(), "marginTop": z.number().optional(), "marginBottom": z.number().optional(), "marginLeft": z.number().optional(), "marginRight": z.number().optional(), "pageRanges": z.string().optional(), "headerTemplate": z.string().optional(), "footerTemplate": z.string().optional(), "preferCSSPageSize": z.boolean().optional(), "transferMode": z.enum(["ReturnAsBase64", "ReturnAsStream"]).optional(), "generateTaggedPDF": z.boolean().optional(), "generateDocumentOutline": z.boolean().optional() }).passthrough(), "Page.printToPDF.params", "commandParams", { method: "Page.printToPDF" }); export const PrintToPDFResult = withCdpMeta(z.object({ "data": z.string(), "stream": z.lazy(() => IO.StreamHandle).optional() }).passthrough(), "Page.printToPDF.result", "commandResult", { method: "Page.printToPDF" }); +export const PrintToPDFCommand = withCdpCommand("Page.printToPDF", PrintToPDFParams, PrintToPDFResult); export const ReloadParams = withCdpMeta(z.object({ "ignoreCache": z.boolean().optional(), "scriptToEvaluateOnLoad": z.string().optional(), "loaderId": z.lazy(() => Network.LoaderId).optional() }).passthrough(), "Page.reload.params", "commandParams", { method: "Page.reload" }); export const ReloadResult = withCdpMeta(z.object({ }).passthrough(), "Page.reload.result", "commandResult", { method: "Page.reload" }); +export const ReloadCommand = withCdpCommand("Page.reload", ReloadParams, ReloadResult); export const RemoveScriptToEvaluateOnLoadParams = withCdpMeta(z.object({ "identifier": z.lazy(() => ScriptIdentifier) }).passthrough(), "Page.removeScriptToEvaluateOnLoad.params", "commandParams", { method: "Page.removeScriptToEvaluateOnLoad" }); export const RemoveScriptToEvaluateOnLoadResult = withCdpMeta(z.object({ }).passthrough(), "Page.removeScriptToEvaluateOnLoad.result", "commandResult", { method: "Page.removeScriptToEvaluateOnLoad" }); +export const RemoveScriptToEvaluateOnLoadCommand = withCdpCommand("Page.removeScriptToEvaluateOnLoad", RemoveScriptToEvaluateOnLoadParams, RemoveScriptToEvaluateOnLoadResult); export const RemoveScriptToEvaluateOnNewDocumentParams = withCdpMeta(z.object({ "identifier": z.lazy(() => ScriptIdentifier) }).passthrough(), "Page.removeScriptToEvaluateOnNewDocument.params", "commandParams", { method: "Page.removeScriptToEvaluateOnNewDocument" }); export const RemoveScriptToEvaluateOnNewDocumentResult = withCdpMeta(z.object({ }).passthrough(), "Page.removeScriptToEvaluateOnNewDocument.result", "commandResult", { method: "Page.removeScriptToEvaluateOnNewDocument" }); +export const RemoveScriptToEvaluateOnNewDocumentCommand = withCdpCommand("Page.removeScriptToEvaluateOnNewDocument", RemoveScriptToEvaluateOnNewDocumentParams, RemoveScriptToEvaluateOnNewDocumentResult); export const ScreencastFrameAckParams = withCdpMeta(z.object({ "sessionId": z.number().int() }).passthrough(), "Page.screencastFrameAck.params", "commandParams", { method: "Page.screencastFrameAck" }); export const ScreencastFrameAckResult = withCdpMeta(z.object({ }).passthrough(), "Page.screencastFrameAck.result", "commandResult", { method: "Page.screencastFrameAck" }); +export const ScreencastFrameAckCommand = withCdpCommand("Page.screencastFrameAck", ScreencastFrameAckParams, ScreencastFrameAckResult); export const SearchInResourceParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "url": z.string(), "query": z.string(), "caseSensitive": z.boolean().optional(), "isRegex": z.boolean().optional() }).passthrough(), "Page.searchInResource.params", "commandParams", { method: "Page.searchInResource" }); export const SearchInResourceResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => Debugger.SearchMatch)) }).passthrough(), "Page.searchInResource.result", "commandResult", { method: "Page.searchInResource" }); +export const SearchInResourceCommand = withCdpCommand("Page.searchInResource", SearchInResourceParams, SearchInResourceResult); export const SetAdBlockingEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Page.setAdBlockingEnabled.params", "commandParams", { method: "Page.setAdBlockingEnabled" }); export const SetAdBlockingEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Page.setAdBlockingEnabled.result", "commandResult", { method: "Page.setAdBlockingEnabled" }); +export const SetAdBlockingEnabledCommand = withCdpCommand("Page.setAdBlockingEnabled", SetAdBlockingEnabledParams, SetAdBlockingEnabledResult); export const SetBypassCSPParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Page.setBypassCSP.params", "commandParams", { method: "Page.setBypassCSP" }); export const SetBypassCSPResult = withCdpMeta(z.object({ }).passthrough(), "Page.setBypassCSP.result", "commandResult", { method: "Page.setBypassCSP" }); +export const SetBypassCSPCommand = withCdpCommand("Page.setBypassCSP", SetBypassCSPParams, SetBypassCSPResult); export const GetPermissionsPolicyStateParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId) }).passthrough(), "Page.getPermissionsPolicyState.params", "commandParams", { method: "Page.getPermissionsPolicyState" }); export const GetPermissionsPolicyStateResult = withCdpMeta(z.object({ "states": z.array(z.lazy(() => PermissionsPolicyFeatureState)) }).passthrough(), "Page.getPermissionsPolicyState.result", "commandResult", { method: "Page.getPermissionsPolicyState" }); +export const GetPermissionsPolicyStateCommand = withCdpCommand("Page.getPermissionsPolicyState", GetPermissionsPolicyStateParams, GetPermissionsPolicyStateResult); export const GetOriginTrialsParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId) }).passthrough(), "Page.getOriginTrials.params", "commandParams", { method: "Page.getOriginTrials" }); export const GetOriginTrialsResult = withCdpMeta(z.object({ "originTrials": z.array(z.lazy(() => OriginTrial)) }).passthrough(), "Page.getOriginTrials.result", "commandResult", { method: "Page.getOriginTrials" }); +export const GetOriginTrialsCommand = withCdpCommand("Page.getOriginTrials", GetOriginTrialsParams, GetOriginTrialsResult); export const SetDeviceMetricsOverrideParams = withCdpMeta(z.object({ "width": z.number().int(), "height": z.number().int(), "deviceScaleFactor": z.number(), "mobile": z.boolean(), "scale": z.number().optional(), "screenWidth": z.number().int().optional(), "screenHeight": z.number().int().optional(), "positionX": z.number().int().optional(), "positionY": z.number().int().optional(), "dontSetVisibleSize": z.boolean().optional(), "screenOrientation": z.lazy(() => Emulation.ScreenOrientation).optional(), "viewport": z.lazy(() => Viewport).optional() }).passthrough(), "Page.setDeviceMetricsOverride.params", "commandParams", { method: "Page.setDeviceMetricsOverride" }); export const SetDeviceMetricsOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.setDeviceMetricsOverride.result", "commandResult", { method: "Page.setDeviceMetricsOverride" }); +export const SetDeviceMetricsOverrideCommand = withCdpCommand("Page.setDeviceMetricsOverride", SetDeviceMetricsOverrideParams, SetDeviceMetricsOverrideResult); export const SetDeviceOrientationOverrideParams = withCdpMeta(z.object({ "alpha": z.number(), "beta": z.number(), "gamma": z.number() }).passthrough(), "Page.setDeviceOrientationOverride.params", "commandParams", { method: "Page.setDeviceOrientationOverride" }); export const SetDeviceOrientationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.setDeviceOrientationOverride.result", "commandResult", { method: "Page.setDeviceOrientationOverride" }); +export const SetDeviceOrientationOverrideCommand = withCdpCommand("Page.setDeviceOrientationOverride", SetDeviceOrientationOverrideParams, SetDeviceOrientationOverrideResult); export const SetFontFamiliesParams = withCdpMeta(z.object({ "fontFamilies": z.lazy(() => FontFamilies), "forScripts": z.array(z.lazy(() => ScriptFontFamilies)).optional() }).passthrough(), "Page.setFontFamilies.params", "commandParams", { method: "Page.setFontFamilies" }); export const SetFontFamiliesResult = withCdpMeta(z.object({ }).passthrough(), "Page.setFontFamilies.result", "commandResult", { method: "Page.setFontFamilies" }); +export const SetFontFamiliesCommand = withCdpCommand("Page.setFontFamilies", SetFontFamiliesParams, SetFontFamiliesResult); export const SetFontSizesParams = withCdpMeta(z.object({ "fontSizes": z.lazy(() => FontSizes) }).passthrough(), "Page.setFontSizes.params", "commandParams", { method: "Page.setFontSizes" }); export const SetFontSizesResult = withCdpMeta(z.object({ }).passthrough(), "Page.setFontSizes.result", "commandResult", { method: "Page.setFontSizes" }); +export const SetFontSizesCommand = withCdpCommand("Page.setFontSizes", SetFontSizesParams, SetFontSizesResult); export const SetDocumentContentParams = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "html": z.string() }).passthrough(), "Page.setDocumentContent.params", "commandParams", { method: "Page.setDocumentContent" }); export const SetDocumentContentResult = withCdpMeta(z.object({ }).passthrough(), "Page.setDocumentContent.result", "commandResult", { method: "Page.setDocumentContent" }); +export const SetDocumentContentCommand = withCdpCommand("Page.setDocumentContent", SetDocumentContentParams, SetDocumentContentResult); export const SetDownloadBehaviorParams = withCdpMeta(z.object({ "behavior": z.enum(["deny", "allow", "default"]), "downloadPath": z.string().optional() }).passthrough(), "Page.setDownloadBehavior.params", "commandParams", { method: "Page.setDownloadBehavior" }); export const SetDownloadBehaviorResult = withCdpMeta(z.object({ }).passthrough(), "Page.setDownloadBehavior.result", "commandResult", { method: "Page.setDownloadBehavior" }); +export const SetDownloadBehaviorCommand = withCdpCommand("Page.setDownloadBehavior", SetDownloadBehaviorParams, SetDownloadBehaviorResult); export const SetGeolocationOverrideParams = withCdpMeta(z.object({ "latitude": z.number().optional(), "longitude": z.number().optional(), "accuracy": z.number().optional() }).passthrough(), "Page.setGeolocationOverride.params", "commandParams", { method: "Page.setGeolocationOverride" }); export const SetGeolocationOverrideResult = withCdpMeta(z.object({ }).passthrough(), "Page.setGeolocationOverride.result", "commandResult", { method: "Page.setGeolocationOverride" }); +export const SetGeolocationOverrideCommand = withCdpCommand("Page.setGeolocationOverride", SetGeolocationOverrideParams, SetGeolocationOverrideResult); export const SetLifecycleEventsEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Page.setLifecycleEventsEnabled.params", "commandParams", { method: "Page.setLifecycleEventsEnabled" }); export const SetLifecycleEventsEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Page.setLifecycleEventsEnabled.result", "commandResult", { method: "Page.setLifecycleEventsEnabled" }); +export const SetLifecycleEventsEnabledCommand = withCdpCommand("Page.setLifecycleEventsEnabled", SetLifecycleEventsEnabledParams, SetLifecycleEventsEnabledResult); export const SetTouchEmulationEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean(), "configuration": z.enum(["mobile", "desktop"]).optional() }).passthrough(), "Page.setTouchEmulationEnabled.params", "commandParams", { method: "Page.setTouchEmulationEnabled" }); export const SetTouchEmulationEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Page.setTouchEmulationEnabled.result", "commandResult", { method: "Page.setTouchEmulationEnabled" }); +export const SetTouchEmulationEnabledCommand = withCdpCommand("Page.setTouchEmulationEnabled", SetTouchEmulationEnabledParams, SetTouchEmulationEnabledResult); export const StartScreencastParams = withCdpMeta(z.object({ "format": z.enum(["jpeg", "png"]).optional(), "quality": z.number().int().optional(), "maxWidth": z.number().int().optional(), "maxHeight": z.number().int().optional(), "everyNthFrame": z.number().int().optional() }).passthrough(), "Page.startScreencast.params", "commandParams", { method: "Page.startScreencast" }); export const StartScreencastResult = withCdpMeta(z.object({ }).passthrough(), "Page.startScreencast.result", "commandResult", { method: "Page.startScreencast" }); +export const StartScreencastCommand = withCdpCommand("Page.startScreencast", StartScreencastParams, StartScreencastResult); export const StopLoadingParams = withCdpMeta(z.object({ }).passthrough(), "Page.stopLoading.params", "commandParams", { method: "Page.stopLoading" }); export const StopLoadingResult = withCdpMeta(z.object({ }).passthrough(), "Page.stopLoading.result", "commandResult", { method: "Page.stopLoading" }); +export const StopLoadingCommand = withCdpCommand("Page.stopLoading", StopLoadingParams, StopLoadingResult); export const CrashParams = withCdpMeta(z.object({ }).passthrough(), "Page.crash.params", "commandParams", { method: "Page.crash" }); export const CrashResult = withCdpMeta(z.object({ }).passthrough(), "Page.crash.result", "commandResult", { method: "Page.crash" }); +export const CrashCommand = withCdpCommand("Page.crash", CrashParams, CrashResult); export const CloseParams = withCdpMeta(z.object({ }).passthrough(), "Page.close.params", "commandParams", { method: "Page.close" }); export const CloseResult = withCdpMeta(z.object({ }).passthrough(), "Page.close.result", "commandResult", { method: "Page.close" }); +export const CloseCommand = withCdpCommand("Page.close", CloseParams, CloseResult); export const SetWebLifecycleStateParams = withCdpMeta(z.object({ "state": z.enum(["frozen", "active"]) }).passthrough(), "Page.setWebLifecycleState.params", "commandParams", { method: "Page.setWebLifecycleState" }); export const SetWebLifecycleStateResult = withCdpMeta(z.object({ }).passthrough(), "Page.setWebLifecycleState.result", "commandResult", { method: "Page.setWebLifecycleState" }); +export const SetWebLifecycleStateCommand = withCdpCommand("Page.setWebLifecycleState", SetWebLifecycleStateParams, SetWebLifecycleStateResult); export const StopScreencastParams = withCdpMeta(z.object({ }).passthrough(), "Page.stopScreencast.params", "commandParams", { method: "Page.stopScreencast" }); export const StopScreencastResult = withCdpMeta(z.object({ }).passthrough(), "Page.stopScreencast.result", "commandResult", { method: "Page.stopScreencast" }); +export const StopScreencastCommand = withCdpCommand("Page.stopScreencast", StopScreencastParams, StopScreencastResult); export const ProduceCompilationCacheParams = withCdpMeta(z.object({ "scripts": z.array(z.lazy(() => CompilationCacheParams)) }).passthrough(), "Page.produceCompilationCache.params", "commandParams", { method: "Page.produceCompilationCache" }); export const ProduceCompilationCacheResult = withCdpMeta(z.object({ }).passthrough(), "Page.produceCompilationCache.result", "commandResult", { method: "Page.produceCompilationCache" }); +export const ProduceCompilationCacheCommand = withCdpCommand("Page.produceCompilationCache", ProduceCompilationCacheParams, ProduceCompilationCacheResult); export const AddCompilationCacheParams = withCdpMeta(z.object({ "url": z.string(), "data": z.string() }).passthrough(), "Page.addCompilationCache.params", "commandParams", { method: "Page.addCompilationCache" }); export const AddCompilationCacheResult = withCdpMeta(z.object({ }).passthrough(), "Page.addCompilationCache.result", "commandResult", { method: "Page.addCompilationCache" }); +export const AddCompilationCacheCommand = withCdpCommand("Page.addCompilationCache", AddCompilationCacheParams, AddCompilationCacheResult); export const ClearCompilationCacheParams = withCdpMeta(z.object({ }).passthrough(), "Page.clearCompilationCache.params", "commandParams", { method: "Page.clearCompilationCache" }); export const ClearCompilationCacheResult = withCdpMeta(z.object({ }).passthrough(), "Page.clearCompilationCache.result", "commandResult", { method: "Page.clearCompilationCache" }); +export const ClearCompilationCacheCommand = withCdpCommand("Page.clearCompilationCache", ClearCompilationCacheParams, ClearCompilationCacheResult); export const SetSPCTransactionModeParams = withCdpMeta(z.object({ "mode": z.enum(["none", "autoAccept", "autoChooseToAuthAnotherWay", "autoReject", "autoOptOut"]) }).passthrough(), "Page.setSPCTransactionMode.params", "commandParams", { method: "Page.setSPCTransactionMode" }); export const SetSPCTransactionModeResult = withCdpMeta(z.object({ }).passthrough(), "Page.setSPCTransactionMode.result", "commandResult", { method: "Page.setSPCTransactionMode" }); +export const SetSPCTransactionModeCommand = withCdpCommand("Page.setSPCTransactionMode", SetSPCTransactionModeParams, SetSPCTransactionModeResult); export const SetRPHRegistrationModeParams = withCdpMeta(z.object({ "mode": z.enum(["none", "autoAccept", "autoReject"]) }).passthrough(), "Page.setRPHRegistrationMode.params", "commandParams", { method: "Page.setRPHRegistrationMode" }); export const SetRPHRegistrationModeResult = withCdpMeta(z.object({ }).passthrough(), "Page.setRPHRegistrationMode.result", "commandResult", { method: "Page.setRPHRegistrationMode" }); +export const SetRPHRegistrationModeCommand = withCdpCommand("Page.setRPHRegistrationMode", SetRPHRegistrationModeParams, SetRPHRegistrationModeResult); export const GenerateTestReportParams = withCdpMeta(z.object({ "message": z.string(), "group": z.string().optional() }).passthrough(), "Page.generateTestReport.params", "commandParams", { method: "Page.generateTestReport" }); export const GenerateTestReportResult = withCdpMeta(z.object({ }).passthrough(), "Page.generateTestReport.result", "commandResult", { method: "Page.generateTestReport" }); +export const GenerateTestReportCommand = withCdpCommand("Page.generateTestReport", GenerateTestReportParams, GenerateTestReportResult); export const WaitForDebuggerParams = withCdpMeta(z.object({ }).passthrough(), "Page.waitForDebugger.params", "commandParams", { method: "Page.waitForDebugger" }); export const WaitForDebuggerResult = withCdpMeta(z.object({ }).passthrough(), "Page.waitForDebugger.result", "commandResult", { method: "Page.waitForDebugger" }); +export const WaitForDebuggerCommand = withCdpCommand("Page.waitForDebugger", WaitForDebuggerParams, WaitForDebuggerResult); export const SetInterceptFileChooserDialogParams = withCdpMeta(z.object({ "enabled": z.boolean(), "cancel": z.boolean().optional() }).passthrough(), "Page.setInterceptFileChooserDialog.params", "commandParams", { method: "Page.setInterceptFileChooserDialog" }); export const SetInterceptFileChooserDialogResult = withCdpMeta(z.object({ }).passthrough(), "Page.setInterceptFileChooserDialog.result", "commandResult", { method: "Page.setInterceptFileChooserDialog" }); +export const SetInterceptFileChooserDialogCommand = withCdpCommand("Page.setInterceptFileChooserDialog", SetInterceptFileChooserDialogParams, SetInterceptFileChooserDialogResult); export const SetPrerenderingAllowedParams = withCdpMeta(z.object({ "isAllowed": z.boolean() }).passthrough(), "Page.setPrerenderingAllowed.params", "commandParams", { method: "Page.setPrerenderingAllowed" }); export const SetPrerenderingAllowedResult = withCdpMeta(z.object({ }).passthrough(), "Page.setPrerenderingAllowed.result", "commandResult", { method: "Page.setPrerenderingAllowed" }); +export const SetPrerenderingAllowedCommand = withCdpCommand("Page.setPrerenderingAllowed", SetPrerenderingAllowedParams, SetPrerenderingAllowedResult); export const GetAnnotatedPageContentParams = withCdpMeta(z.object({ "includeActionableInformation": z.boolean().optional() }).passthrough(), "Page.getAnnotatedPageContent.params", "commandParams", { method: "Page.getAnnotatedPageContent" }); export const GetAnnotatedPageContentResult = withCdpMeta(z.object({ "content": z.string() }).passthrough(), "Page.getAnnotatedPageContent.result", "commandResult", { method: "Page.getAnnotatedPageContent" }); +export const GetAnnotatedPageContentCommand = withCdpCommand("Page.getAnnotatedPageContent", GetAnnotatedPageContentParams, GetAnnotatedPageContentResult); export const DomContentEventFiredEvent = withCdpMeta(z.object({ "timestamp": z.lazy(() => Network.MonotonicTime) }).passthrough(), "Page.domContentEventFired", "event", { phase: "event" }); export const FileChooserOpenedEvent = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "mode": z.enum(["selectSingle", "selectMultiple"]), "backendNodeId": z.lazy(() => DOM.BackendNodeId).optional() }).passthrough(), "Page.fileChooserOpened", "event", { phase: "event" }); export const FrameAttachedEvent = withCdpMeta(z.object({ "frameId": z.lazy(() => FrameId), "parentFrameId": z.lazy(() => FrameId), "stack": z.lazy(() => Runtime.StackTrace).optional() }).passthrough(), "Page.frameAttached", "event", { phase: "event" }); @@ -429,67 +490,67 @@ export const zod = { CompilationCacheProducedEvent: CompilationCacheProducedEvent, } as const; export const commands = { - "Page.addScriptToEvaluateOnLoad": { params: AddScriptToEvaluateOnLoadParams, result: AddScriptToEvaluateOnLoadResult }, - "Page.addScriptToEvaluateOnNewDocument": { params: AddScriptToEvaluateOnNewDocumentParams, result: AddScriptToEvaluateOnNewDocumentResult }, - "Page.bringToFront": { params: BringToFrontParams, result: BringToFrontResult }, - "Page.captureScreenshot": { params: CaptureScreenshotParams, result: CaptureScreenshotResult }, - "Page.captureSnapshot": { params: CaptureSnapshotParams, result: CaptureSnapshotResult }, - "Page.clearDeviceMetricsOverride": { params: ClearDeviceMetricsOverrideParams, result: ClearDeviceMetricsOverrideResult }, - "Page.clearDeviceOrientationOverride": { params: ClearDeviceOrientationOverrideParams, result: ClearDeviceOrientationOverrideResult }, - "Page.clearGeolocationOverride": { params: ClearGeolocationOverrideParams, result: ClearGeolocationOverrideResult }, - "Page.createIsolatedWorld": { params: CreateIsolatedWorldParams, result: CreateIsolatedWorldResult }, - "Page.deleteCookie": { params: DeleteCookieParams, result: DeleteCookieResult }, - "Page.disable": { params: DisableParams, result: DisableResult }, - "Page.enable": { params: EnableParams, result: EnableResult }, - "Page.getAppManifest": { params: GetAppManifestParams, result: GetAppManifestResult }, - "Page.getInstallabilityErrors": { params: GetInstallabilityErrorsParams, result: GetInstallabilityErrorsResult }, - "Page.getManifestIcons": { params: GetManifestIconsParams, result: GetManifestIconsResult }, - "Page.getAppId": { params: GetAppIdParams, result: GetAppIdResult }, - "Page.getAdScriptAncestry": { params: GetAdScriptAncestryParams, result: GetAdScriptAncestryResult }, - "Page.getFrameTree": { params: GetFrameTreeParams, result: GetFrameTreeResult }, - "Page.getLayoutMetrics": { params: GetLayoutMetricsParams, result: GetLayoutMetricsResult }, - "Page.getNavigationHistory": { params: GetNavigationHistoryParams, result: GetNavigationHistoryResult }, - "Page.resetNavigationHistory": { params: ResetNavigationHistoryParams, result: ResetNavigationHistoryResult }, - "Page.getResourceContent": { params: GetResourceContentParams, result: GetResourceContentResult }, - "Page.getResourceTree": { params: GetResourceTreeParams, result: GetResourceTreeResult }, - "Page.handleJavaScriptDialog": { params: HandleJavaScriptDialogParams, result: HandleJavaScriptDialogResult }, - "Page.navigate": { params: NavigateParams, result: NavigateResult }, - "Page.navigateToHistoryEntry": { params: NavigateToHistoryEntryParams, result: NavigateToHistoryEntryResult }, - "Page.printToPDF": { params: PrintToPDFParams, result: PrintToPDFResult }, - "Page.reload": { params: ReloadParams, result: ReloadResult }, - "Page.removeScriptToEvaluateOnLoad": { params: RemoveScriptToEvaluateOnLoadParams, result: RemoveScriptToEvaluateOnLoadResult }, - "Page.removeScriptToEvaluateOnNewDocument": { params: RemoveScriptToEvaluateOnNewDocumentParams, result: RemoveScriptToEvaluateOnNewDocumentResult }, - "Page.screencastFrameAck": { params: ScreencastFrameAckParams, result: ScreencastFrameAckResult }, - "Page.searchInResource": { params: SearchInResourceParams, result: SearchInResourceResult }, - "Page.setAdBlockingEnabled": { params: SetAdBlockingEnabledParams, result: SetAdBlockingEnabledResult }, - "Page.setBypassCSP": { params: SetBypassCSPParams, result: SetBypassCSPResult }, - "Page.getPermissionsPolicyState": { params: GetPermissionsPolicyStateParams, result: GetPermissionsPolicyStateResult }, - "Page.getOriginTrials": { params: GetOriginTrialsParams, result: GetOriginTrialsResult }, - "Page.setDeviceMetricsOverride": { params: SetDeviceMetricsOverrideParams, result: SetDeviceMetricsOverrideResult }, - "Page.setDeviceOrientationOverride": { params: SetDeviceOrientationOverrideParams, result: SetDeviceOrientationOverrideResult }, - "Page.setFontFamilies": { params: SetFontFamiliesParams, result: SetFontFamiliesResult }, - "Page.setFontSizes": { params: SetFontSizesParams, result: SetFontSizesResult }, - "Page.setDocumentContent": { params: SetDocumentContentParams, result: SetDocumentContentResult }, - "Page.setDownloadBehavior": { params: SetDownloadBehaviorParams, result: SetDownloadBehaviorResult }, - "Page.setGeolocationOverride": { params: SetGeolocationOverrideParams, result: SetGeolocationOverrideResult }, - "Page.setLifecycleEventsEnabled": { params: SetLifecycleEventsEnabledParams, result: SetLifecycleEventsEnabledResult }, - "Page.setTouchEmulationEnabled": { params: SetTouchEmulationEnabledParams, result: SetTouchEmulationEnabledResult }, - "Page.startScreencast": { params: StartScreencastParams, result: StartScreencastResult }, - "Page.stopLoading": { params: StopLoadingParams, result: StopLoadingResult }, - "Page.crash": { params: CrashParams, result: CrashResult }, - "Page.close": { params: CloseParams, result: CloseResult }, - "Page.setWebLifecycleState": { params: SetWebLifecycleStateParams, result: SetWebLifecycleStateResult }, - "Page.stopScreencast": { params: StopScreencastParams, result: StopScreencastResult }, - "Page.produceCompilationCache": { params: ProduceCompilationCacheParams, result: ProduceCompilationCacheResult }, - "Page.addCompilationCache": { params: AddCompilationCacheParams, result: AddCompilationCacheResult }, - "Page.clearCompilationCache": { params: ClearCompilationCacheParams, result: ClearCompilationCacheResult }, - "Page.setSPCTransactionMode": { params: SetSPCTransactionModeParams, result: SetSPCTransactionModeResult }, - "Page.setRPHRegistrationMode": { params: SetRPHRegistrationModeParams, result: SetRPHRegistrationModeResult }, - "Page.generateTestReport": { params: GenerateTestReportParams, result: GenerateTestReportResult }, - "Page.waitForDebugger": { params: WaitForDebuggerParams, result: WaitForDebuggerResult }, - "Page.setInterceptFileChooserDialog": { params: SetInterceptFileChooserDialogParams, result: SetInterceptFileChooserDialogResult }, - "Page.setPrerenderingAllowed": { params: SetPrerenderingAllowedParams, result: SetPrerenderingAllowedResult }, - "Page.getAnnotatedPageContent": { params: GetAnnotatedPageContentParams, result: GetAnnotatedPageContentResult }, + "Page.addScriptToEvaluateOnLoad": AddScriptToEvaluateOnLoadCommand, + "Page.addScriptToEvaluateOnNewDocument": AddScriptToEvaluateOnNewDocumentCommand, + "Page.bringToFront": BringToFrontCommand, + "Page.captureScreenshot": CaptureScreenshotCommand, + "Page.captureSnapshot": CaptureSnapshotCommand, + "Page.clearDeviceMetricsOverride": ClearDeviceMetricsOverrideCommand, + "Page.clearDeviceOrientationOverride": ClearDeviceOrientationOverrideCommand, + "Page.clearGeolocationOverride": ClearGeolocationOverrideCommand, + "Page.createIsolatedWorld": CreateIsolatedWorldCommand, + "Page.deleteCookie": DeleteCookieCommand, + "Page.disable": DisableCommand, + "Page.enable": EnableCommand, + "Page.getAppManifest": GetAppManifestCommand, + "Page.getInstallabilityErrors": GetInstallabilityErrorsCommand, + "Page.getManifestIcons": GetManifestIconsCommand, + "Page.getAppId": GetAppIdCommand, + "Page.getAdScriptAncestry": GetAdScriptAncestryCommand, + "Page.getFrameTree": GetFrameTreeCommand, + "Page.getLayoutMetrics": GetLayoutMetricsCommand, + "Page.getNavigationHistory": GetNavigationHistoryCommand, + "Page.resetNavigationHistory": ResetNavigationHistoryCommand, + "Page.getResourceContent": GetResourceContentCommand, + "Page.getResourceTree": GetResourceTreeCommand, + "Page.handleJavaScriptDialog": HandleJavaScriptDialogCommand, + "Page.navigate": NavigateCommand, + "Page.navigateToHistoryEntry": NavigateToHistoryEntryCommand, + "Page.printToPDF": PrintToPDFCommand, + "Page.reload": ReloadCommand, + "Page.removeScriptToEvaluateOnLoad": RemoveScriptToEvaluateOnLoadCommand, + "Page.removeScriptToEvaluateOnNewDocument": RemoveScriptToEvaluateOnNewDocumentCommand, + "Page.screencastFrameAck": ScreencastFrameAckCommand, + "Page.searchInResource": SearchInResourceCommand, + "Page.setAdBlockingEnabled": SetAdBlockingEnabledCommand, + "Page.setBypassCSP": SetBypassCSPCommand, + "Page.getPermissionsPolicyState": GetPermissionsPolicyStateCommand, + "Page.getOriginTrials": GetOriginTrialsCommand, + "Page.setDeviceMetricsOverride": SetDeviceMetricsOverrideCommand, + "Page.setDeviceOrientationOverride": SetDeviceOrientationOverrideCommand, + "Page.setFontFamilies": SetFontFamiliesCommand, + "Page.setFontSizes": SetFontSizesCommand, + "Page.setDocumentContent": SetDocumentContentCommand, + "Page.setDownloadBehavior": SetDownloadBehaviorCommand, + "Page.setGeolocationOverride": SetGeolocationOverrideCommand, + "Page.setLifecycleEventsEnabled": SetLifecycleEventsEnabledCommand, + "Page.setTouchEmulationEnabled": SetTouchEmulationEnabledCommand, + "Page.startScreencast": StartScreencastCommand, + "Page.stopLoading": StopLoadingCommand, + "Page.crash": CrashCommand, + "Page.close": CloseCommand, + "Page.setWebLifecycleState": SetWebLifecycleStateCommand, + "Page.stopScreencast": StopScreencastCommand, + "Page.produceCompilationCache": ProduceCompilationCacheCommand, + "Page.addCompilationCache": AddCompilationCacheCommand, + "Page.clearCompilationCache": ClearCompilationCacheCommand, + "Page.setSPCTransactionMode": SetSPCTransactionModeCommand, + "Page.setRPHRegistrationMode": SetRPHRegistrationModeCommand, + "Page.generateTestReport": GenerateTestReportCommand, + "Page.waitForDebugger": WaitForDebuggerCommand, + "Page.setInterceptFileChooserDialog": SetInterceptFileChooserDialogCommand, + "Page.setPrerenderingAllowed": SetPrerenderingAllowedCommand, + "Page.getAnnotatedPageContent": GetAnnotatedPageContentCommand, } as const; export const events = { "Page.domContentEventFired": DomContentEventFiredEvent, diff --git a/js/src/types/generated/zod/Performance.ts b/js/src/types/generated/zod/Performance.ts index 1374ed8c..19f17751 100644 --- a/js/src/types/generated/zod/Performance.ts +++ b/js/src/types/generated/zod/Performance.ts @@ -1,17 +1,21 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const Metric = withCdpMeta(z.object({ "name": z.string(), "value": z.number() }).passthrough(), "Performance.Metric", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Performance.disable.params", "commandParams", { method: "Performance.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Performance.disable.result", "commandResult", { method: "Performance.disable" }); +export const DisableCommand = withCdpCommand("Performance.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ "timeDomain": z.enum(["timeTicks", "threadTicks"]).optional() }).passthrough(), "Performance.enable.params", "commandParams", { method: "Performance.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Performance.enable.result", "commandResult", { method: "Performance.enable" }); +export const EnableCommand = withCdpCommand("Performance.enable", EnableParams, EnableResult); export const SetTimeDomainParams = withCdpMeta(z.object({ "timeDomain": z.enum(["timeTicks", "threadTicks"]) }).passthrough(), "Performance.setTimeDomain.params", "commandParams", { method: "Performance.setTimeDomain" }); export const SetTimeDomainResult = withCdpMeta(z.object({ }).passthrough(), "Performance.setTimeDomain.result", "commandResult", { method: "Performance.setTimeDomain" }); +export const SetTimeDomainCommand = withCdpCommand("Performance.setTimeDomain", SetTimeDomainParams, SetTimeDomainResult); export const GetMetricsParams = withCdpMeta(z.object({ }).passthrough(), "Performance.getMetrics.params", "commandParams", { method: "Performance.getMetrics" }); export const GetMetricsResult = withCdpMeta(z.object({ "metrics": z.array(z.lazy(() => Metric)) }).passthrough(), "Performance.getMetrics.result", "commandResult", { method: "Performance.getMetrics" }); +export const GetMetricsCommand = withCdpCommand("Performance.getMetrics", GetMetricsParams, GetMetricsResult); export const MetricsEvent = withCdpMeta(z.object({ "metrics": z.array(z.lazy(() => Metric)), "title": z.string() }).passthrough(), "Performance.metrics", "event", { phase: "event" }); export const zod = { @@ -27,10 +31,10 @@ export const zod = { MetricsEvent: MetricsEvent, } as const; export const commands = { - "Performance.disable": { params: DisableParams, result: DisableResult }, - "Performance.enable": { params: EnableParams, result: EnableResult }, - "Performance.setTimeDomain": { params: SetTimeDomainParams, result: SetTimeDomainResult }, - "Performance.getMetrics": { params: GetMetricsParams, result: GetMetricsResult }, + "Performance.disable": DisableCommand, + "Performance.enable": EnableCommand, + "Performance.setTimeDomain": SetTimeDomainCommand, + "Performance.getMetrics": GetMetricsCommand, } as const; export const events = { "Performance.metrics": MetricsEvent, diff --git a/js/src/types/generated/zod/PerformanceTimeline.ts b/js/src/types/generated/zod/PerformanceTimeline.ts index efe9bde1..d79df172 100644 --- a/js/src/types/generated/zod/PerformanceTimeline.ts +++ b/js/src/types/generated/zod/PerformanceTimeline.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -12,6 +12,7 @@ export const LayoutShift = withCdpMeta(z.object({ "value": z.number(), "hadRecen export const TimelineEvent = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId), "type": z.string(), "name": z.string(), "time": z.lazy(() => Network.TimeSinceEpoch), "duration": z.number().optional(), "lcpDetails": z.lazy(() => LargestContentfulPaint).optional(), "layoutShiftDetails": z.lazy(() => LayoutShift).optional() }).passthrough(), "PerformanceTimeline.TimelineEvent", "type"); export const EnableParams = withCdpMeta(z.object({ "eventTypes": z.array(z.string()) }).passthrough(), "PerformanceTimeline.enable.params", "commandParams", { method: "PerformanceTimeline.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "PerformanceTimeline.enable.result", "commandResult", { method: "PerformanceTimeline.enable" }); +export const EnableCommand = withCdpCommand("PerformanceTimeline.enable", EnableParams, EnableResult); export const TimelineEventAddedEvent = withCdpMeta(z.object({ "event": z.lazy(() => TimelineEvent) }).passthrough(), "PerformanceTimeline.timelineEventAdded", "event", { phase: "event" }); export const zod = { @@ -24,7 +25,7 @@ export const zod = { TimelineEventAddedEvent: TimelineEventAddedEvent, } as const; export const commands = { - "PerformanceTimeline.enable": { params: EnableParams, result: EnableResult }, + "PerformanceTimeline.enable": EnableCommand, } as const; export const events = { "PerformanceTimeline.timelineEventAdded": TimelineEventAddedEvent, diff --git a/js/src/types/generated/zod/Preload.ts b/js/src/types/generated/zod/Preload.ts index 277bf93c..87bf43f5 100644 --- a/js/src/types/generated/zod/Preload.ts +++ b/js/src/types/generated/zod/Preload.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -20,8 +20,10 @@ export const PrefetchStatus = withCdpMeta(z.enum(["PrefetchAllowed", "PrefetchFa export const PrerenderMismatchedHeaders = withCdpMeta(z.object({ "headerName": z.string(), "initialValue": z.string().optional(), "activationValue": z.string().optional() }).passthrough(), "Preload.PrerenderMismatchedHeaders", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Preload.enable.params", "commandParams", { method: "Preload.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Preload.enable.result", "commandResult", { method: "Preload.enable" }); +export const EnableCommand = withCdpCommand("Preload.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Preload.disable.params", "commandParams", { method: "Preload.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Preload.disable.result", "commandResult", { method: "Preload.disable" }); +export const DisableCommand = withCdpCommand("Preload.disable", DisableParams, DisableResult); export const RuleSetUpdatedEvent = withCdpMeta(z.object({ "ruleSet": z.lazy(() => RuleSet) }).passthrough(), "Preload.ruleSetUpdated", "event", { phase: "event" }); export const RuleSetRemovedEvent = withCdpMeta(z.object({ "id": z.lazy(() => RuleSetId) }).passthrough(), "Preload.ruleSetRemoved", "event", { phase: "event" }); export const PreloadEnabledStateUpdatedEvent = withCdpMeta(z.object({ "disabledByPreference": z.boolean(), "disabledByDataSaver": z.boolean(), "disabledByBatterySaver": z.boolean(), "disabledByHoldbackPrefetchSpeculationRules": z.boolean(), "disabledByHoldbackPrerenderSpeculationRules": z.boolean() }).passthrough(), "Preload.preloadEnabledStateUpdated", "event", { phase: "event" }); @@ -54,8 +56,8 @@ export const zod = { PreloadingAttemptSourcesUpdatedEvent: PreloadingAttemptSourcesUpdatedEvent, } as const; export const commands = { - "Preload.enable": { params: EnableParams, result: EnableResult }, - "Preload.disable": { params: DisableParams, result: DisableResult }, + "Preload.enable": EnableCommand, + "Preload.disable": DisableCommand, } as const; export const events = { "Preload.ruleSetUpdated": RuleSetUpdatedEvent, diff --git a/js/src/types/generated/zod/Profiler.ts b/js/src/types/generated/zod/Profiler.ts index 48a0f9d7..713f0458 100644 --- a/js/src/types/generated/zod/Profiler.ts +++ b/js/src/types/generated/zod/Profiler.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Debugger from "./Debugger.js"; import * as Runtime from "./Runtime.js"; @@ -13,22 +13,31 @@ export const FunctionCoverage = withCdpMeta(z.object({ "functionName": z.string( export const ScriptCoverage = withCdpMeta(z.object({ "scriptId": z.lazy(() => Runtime.ScriptId), "url": z.string(), "functions": z.array(z.lazy(() => FunctionCoverage)) }).passthrough(), "Profiler.ScriptCoverage", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.disable.params", "commandParams", { method: "Profiler.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Profiler.disable.result", "commandResult", { method: "Profiler.disable" }); +export const DisableCommand = withCdpCommand("Profiler.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.enable.params", "commandParams", { method: "Profiler.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Profiler.enable.result", "commandResult", { method: "Profiler.enable" }); +export const EnableCommand = withCdpCommand("Profiler.enable", EnableParams, EnableResult); export const GetBestEffortCoverageParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.getBestEffortCoverage.params", "commandParams", { method: "Profiler.getBestEffortCoverage" }); export const GetBestEffortCoverageResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => ScriptCoverage)) }).passthrough(), "Profiler.getBestEffortCoverage.result", "commandResult", { method: "Profiler.getBestEffortCoverage" }); +export const GetBestEffortCoverageCommand = withCdpCommand("Profiler.getBestEffortCoverage", GetBestEffortCoverageParams, GetBestEffortCoverageResult); export const SetSamplingIntervalParams = withCdpMeta(z.object({ "interval": z.number().int() }).passthrough(), "Profiler.setSamplingInterval.params", "commandParams", { method: "Profiler.setSamplingInterval" }); export const SetSamplingIntervalResult = withCdpMeta(z.object({ }).passthrough(), "Profiler.setSamplingInterval.result", "commandResult", { method: "Profiler.setSamplingInterval" }); +export const SetSamplingIntervalCommand = withCdpCommand("Profiler.setSamplingInterval", SetSamplingIntervalParams, SetSamplingIntervalResult); export const StartParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.start.params", "commandParams", { method: "Profiler.start" }); export const StartResult = withCdpMeta(z.object({ }).passthrough(), "Profiler.start.result", "commandResult", { method: "Profiler.start" }); +export const StartCommand = withCdpCommand("Profiler.start", StartParams, StartResult); export const StartPreciseCoverageParams = withCdpMeta(z.object({ "callCount": z.boolean().optional(), "detailed": z.boolean().optional(), "allowTriggeredUpdates": z.boolean().optional() }).passthrough(), "Profiler.startPreciseCoverage.params", "commandParams", { method: "Profiler.startPreciseCoverage" }); export const StartPreciseCoverageResult = withCdpMeta(z.object({ "timestamp": z.number() }).passthrough(), "Profiler.startPreciseCoverage.result", "commandResult", { method: "Profiler.startPreciseCoverage" }); +export const StartPreciseCoverageCommand = withCdpCommand("Profiler.startPreciseCoverage", StartPreciseCoverageParams, StartPreciseCoverageResult); export const StopParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.stop.params", "commandParams", { method: "Profiler.stop" }); export const StopResult = withCdpMeta(z.object({ "profile": z.lazy(() => Profile) }).passthrough(), "Profiler.stop.result", "commandResult", { method: "Profiler.stop" }); +export const StopCommand = withCdpCommand("Profiler.stop", StopParams, StopResult); export const StopPreciseCoverageParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.stopPreciseCoverage.params", "commandParams", { method: "Profiler.stopPreciseCoverage" }); export const StopPreciseCoverageResult = withCdpMeta(z.object({ }).passthrough(), "Profiler.stopPreciseCoverage.result", "commandResult", { method: "Profiler.stopPreciseCoverage" }); +export const StopPreciseCoverageCommand = withCdpCommand("Profiler.stopPreciseCoverage", StopPreciseCoverageParams, StopPreciseCoverageResult); export const TakePreciseCoverageParams = withCdpMeta(z.object({ }).passthrough(), "Profiler.takePreciseCoverage.params", "commandParams", { method: "Profiler.takePreciseCoverage" }); export const TakePreciseCoverageResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => ScriptCoverage)), "timestamp": z.number() }).passthrough(), "Profiler.takePreciseCoverage.result", "commandResult", { method: "Profiler.takePreciseCoverage" }); +export const TakePreciseCoverageCommand = withCdpCommand("Profiler.takePreciseCoverage", TakePreciseCoverageParams, TakePreciseCoverageResult); export const ConsoleProfileFinishedEvent = withCdpMeta(z.object({ "id": z.string(), "location": z.lazy(() => Debugger.Location), "profile": z.lazy(() => Profile), "title": z.string().optional() }).passthrough(), "Profiler.consoleProfileFinished", "event", { phase: "event" }); export const ConsoleProfileStartedEvent = withCdpMeta(z.object({ "id": z.string(), "location": z.lazy(() => Debugger.Location), "title": z.string().optional() }).passthrough(), "Profiler.consoleProfileStarted", "event", { phase: "event" }); export const PreciseCoverageDeltaUpdateEvent = withCdpMeta(z.object({ "timestamp": z.number(), "occasion": z.string(), "result": z.array(z.lazy(() => ScriptCoverage)) }).passthrough(), "Profiler.preciseCoverageDeltaUpdate", "event", { phase: "event" }); @@ -63,15 +72,15 @@ export const zod = { PreciseCoverageDeltaUpdateEvent: PreciseCoverageDeltaUpdateEvent, } as const; export const commands = { - "Profiler.disable": { params: DisableParams, result: DisableResult }, - "Profiler.enable": { params: EnableParams, result: EnableResult }, - "Profiler.getBestEffortCoverage": { params: GetBestEffortCoverageParams, result: GetBestEffortCoverageResult }, - "Profiler.setSamplingInterval": { params: SetSamplingIntervalParams, result: SetSamplingIntervalResult }, - "Profiler.start": { params: StartParams, result: StartResult }, - "Profiler.startPreciseCoverage": { params: StartPreciseCoverageParams, result: StartPreciseCoverageResult }, - "Profiler.stop": { params: StopParams, result: StopResult }, - "Profiler.stopPreciseCoverage": { params: StopPreciseCoverageParams, result: StopPreciseCoverageResult }, - "Profiler.takePreciseCoverage": { params: TakePreciseCoverageParams, result: TakePreciseCoverageResult }, + "Profiler.disable": DisableCommand, + "Profiler.enable": EnableCommand, + "Profiler.getBestEffortCoverage": GetBestEffortCoverageCommand, + "Profiler.setSamplingInterval": SetSamplingIntervalCommand, + "Profiler.start": StartCommand, + "Profiler.startPreciseCoverage": StartPreciseCoverageCommand, + "Profiler.stop": StopCommand, + "Profiler.stopPreciseCoverage": StopPreciseCoverageCommand, + "Profiler.takePreciseCoverage": TakePreciseCoverageCommand, } as const; export const events = { "Profiler.consoleProfileFinished": ConsoleProfileFinishedEvent, diff --git a/js/src/types/generated/zod/Runtime.ts b/js/src/types/generated/zod/Runtime.ts index 11a6f111..1a567feb 100644 --- a/js/src/types/generated/zod/Runtime.ts +++ b/js/src/types/generated/zod/Runtime.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const ScriptId = withCdpMeta(z.string(), "Runtime.ScriptId", "type"); export const SerializationOptions = withCdpMeta(z.object({ "serialization": z.enum(["deep", "json", "idOnly"]), "maxDepth": z.number().int().optional(), "additionalParameters": z.record(z.string(), z.unknown()).optional() }).passthrough(), "Runtime.SerializationOptions", "type"); @@ -28,50 +28,73 @@ export const UniqueDebuggerId = withCdpMeta(z.string(), "Runtime.UniqueDebuggerI export const StackTraceId = withCdpMeta(z.object({ "id": z.string(), "debuggerId": z.lazy(() => UniqueDebuggerId).optional() }).passthrough(), "Runtime.StackTraceId", "type"); export const AwaitPromiseParams = withCdpMeta(z.object({ "promiseObjectId": z.lazy(() => RemoteObjectId), "returnByValue": z.boolean().optional(), "generatePreview": z.boolean().optional() }).passthrough(), "Runtime.awaitPromise.params", "commandParams", { method: "Runtime.awaitPromise" }); export const AwaitPromiseResult = withCdpMeta(z.object({ "result": z.lazy(() => RemoteObject), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.awaitPromise.result", "commandResult", { method: "Runtime.awaitPromise" }); +export const AwaitPromiseCommand = withCdpCommand("Runtime.awaitPromise", AwaitPromiseParams, AwaitPromiseResult); export const CallFunctionOnParams = withCdpMeta(z.object({ "functionDeclaration": z.string(), "objectId": z.lazy(() => RemoteObjectId).optional(), "arguments": z.array(z.lazy(() => CallArgument)).optional(), "silent": z.boolean().optional(), "returnByValue": z.boolean().optional(), "generatePreview": z.boolean().optional(), "userGesture": z.boolean().optional(), "awaitPromise": z.boolean().optional(), "executionContextId": z.lazy(() => ExecutionContextId).optional(), "objectGroup": z.string().optional(), "throwOnSideEffect": z.boolean().optional(), "uniqueContextId": z.string().optional(), "serializationOptions": z.lazy(() => SerializationOptions).optional() }).passthrough(), "Runtime.callFunctionOn.params", "commandParams", { method: "Runtime.callFunctionOn" }); export const CallFunctionOnResult = withCdpMeta(z.object({ "result": z.lazy(() => RemoteObject), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.callFunctionOn.result", "commandResult", { method: "Runtime.callFunctionOn" }); +export const CallFunctionOnCommand = withCdpCommand("Runtime.callFunctionOn", CallFunctionOnParams, CallFunctionOnResult); export const CompileScriptParams = withCdpMeta(z.object({ "expression": z.string(), "sourceURL": z.string(), "persistScript": z.boolean(), "executionContextId": z.lazy(() => ExecutionContextId).optional() }).passthrough(), "Runtime.compileScript.params", "commandParams", { method: "Runtime.compileScript" }); export const CompileScriptResult = withCdpMeta(z.object({ "scriptId": z.lazy(() => ScriptId).optional(), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.compileScript.result", "commandResult", { method: "Runtime.compileScript" }); +export const CompileScriptCommand = withCdpCommand("Runtime.compileScript", CompileScriptParams, CompileScriptResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.disable.params", "commandParams", { method: "Runtime.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.disable.result", "commandResult", { method: "Runtime.disable" }); +export const DisableCommand = withCdpCommand("Runtime.disable", DisableParams, DisableResult); export const DiscardConsoleEntriesParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.discardConsoleEntries.params", "commandParams", { method: "Runtime.discardConsoleEntries" }); export const DiscardConsoleEntriesResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.discardConsoleEntries.result", "commandResult", { method: "Runtime.discardConsoleEntries" }); +export const DiscardConsoleEntriesCommand = withCdpCommand("Runtime.discardConsoleEntries", DiscardConsoleEntriesParams, DiscardConsoleEntriesResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.enable.params", "commandParams", { method: "Runtime.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.enable.result", "commandResult", { method: "Runtime.enable" }); +export const EnableCommand = withCdpCommand("Runtime.enable", EnableParams, EnableResult); export const EvaluateParams = withCdpMeta(z.object({ "expression": z.string(), "objectGroup": z.string().optional(), "includeCommandLineAPI": z.boolean().optional(), "silent": z.boolean().optional(), "contextId": z.lazy(() => ExecutionContextId).optional(), "returnByValue": z.boolean().optional(), "generatePreview": z.boolean().optional(), "userGesture": z.boolean().optional(), "awaitPromise": z.boolean().optional(), "throwOnSideEffect": z.boolean().optional(), "timeout": z.lazy(() => TimeDelta).optional(), "disableBreaks": z.boolean().optional(), "replMode": z.boolean().optional(), "allowUnsafeEvalBlockedByCSP": z.boolean().optional(), "uniqueContextId": z.string().optional(), "serializationOptions": z.lazy(() => SerializationOptions).optional() }).passthrough(), "Runtime.evaluate.params", "commandParams", { method: "Runtime.evaluate" }); export const EvaluateResult = withCdpMeta(z.object({ "result": z.lazy(() => RemoteObject), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.evaluate.result", "commandResult", { method: "Runtime.evaluate" }); +export const EvaluateCommand = withCdpCommand("Runtime.evaluate", EvaluateParams, EvaluateResult); export const GetIsolateIdParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.getIsolateId.params", "commandParams", { method: "Runtime.getIsolateId" }); export const GetIsolateIdResult = withCdpMeta(z.object({ "id": z.string() }).passthrough(), "Runtime.getIsolateId.result", "commandResult", { method: "Runtime.getIsolateId" }); +export const GetIsolateIdCommand = withCdpCommand("Runtime.getIsolateId", GetIsolateIdParams, GetIsolateIdResult); export const GetHeapUsageParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.getHeapUsage.params", "commandParams", { method: "Runtime.getHeapUsage" }); export const GetHeapUsageResult = withCdpMeta(z.object({ "usedSize": z.number(), "totalSize": z.number(), "embedderHeapUsedSize": z.number(), "backingStorageSize": z.number() }).passthrough(), "Runtime.getHeapUsage.result", "commandResult", { method: "Runtime.getHeapUsage" }); +export const GetHeapUsageCommand = withCdpCommand("Runtime.getHeapUsage", GetHeapUsageParams, GetHeapUsageResult); export const GetPropertiesParams = withCdpMeta(z.object({ "objectId": z.lazy(() => RemoteObjectId), "ownProperties": z.boolean().optional(), "accessorPropertiesOnly": z.boolean().optional(), "generatePreview": z.boolean().optional(), "nonIndexedPropertiesOnly": z.boolean().optional() }).passthrough(), "Runtime.getProperties.params", "commandParams", { method: "Runtime.getProperties" }); export const GetPropertiesResult = withCdpMeta(z.object({ "result": z.array(z.lazy(() => PropertyDescriptor)), "internalProperties": z.array(z.lazy(() => InternalPropertyDescriptor)).optional(), "privateProperties": z.array(z.lazy(() => PrivatePropertyDescriptor)).optional(), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.getProperties.result", "commandResult", { method: "Runtime.getProperties" }); +export const GetPropertiesCommand = withCdpCommand("Runtime.getProperties", GetPropertiesParams, GetPropertiesResult); export const GlobalLexicalScopeNamesParams = withCdpMeta(z.object({ "executionContextId": z.lazy(() => ExecutionContextId).optional() }).passthrough(), "Runtime.globalLexicalScopeNames.params", "commandParams", { method: "Runtime.globalLexicalScopeNames" }); export const GlobalLexicalScopeNamesResult = withCdpMeta(z.object({ "names": z.array(z.string()) }).passthrough(), "Runtime.globalLexicalScopeNames.result", "commandResult", { method: "Runtime.globalLexicalScopeNames" }); +export const GlobalLexicalScopeNamesCommand = withCdpCommand("Runtime.globalLexicalScopeNames", GlobalLexicalScopeNamesParams, GlobalLexicalScopeNamesResult); export const QueryObjectsParams = withCdpMeta(z.object({ "prototypeObjectId": z.lazy(() => RemoteObjectId), "objectGroup": z.string().optional() }).passthrough(), "Runtime.queryObjects.params", "commandParams", { method: "Runtime.queryObjects" }); export const QueryObjectsResult = withCdpMeta(z.object({ "objects": z.lazy(() => RemoteObject) }).passthrough(), "Runtime.queryObjects.result", "commandResult", { method: "Runtime.queryObjects" }); +export const QueryObjectsCommand = withCdpCommand("Runtime.queryObjects", QueryObjectsParams, QueryObjectsResult); export const ReleaseObjectParams = withCdpMeta(z.object({ "objectId": z.lazy(() => RemoteObjectId) }).passthrough(), "Runtime.releaseObject.params", "commandParams", { method: "Runtime.releaseObject" }); export const ReleaseObjectResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.releaseObject.result", "commandResult", { method: "Runtime.releaseObject" }); +export const ReleaseObjectCommand = withCdpCommand("Runtime.releaseObject", ReleaseObjectParams, ReleaseObjectResult); export const ReleaseObjectGroupParams = withCdpMeta(z.object({ "objectGroup": z.string() }).passthrough(), "Runtime.releaseObjectGroup.params", "commandParams", { method: "Runtime.releaseObjectGroup" }); export const ReleaseObjectGroupResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.releaseObjectGroup.result", "commandResult", { method: "Runtime.releaseObjectGroup" }); +export const ReleaseObjectGroupCommand = withCdpCommand("Runtime.releaseObjectGroup", ReleaseObjectGroupParams, ReleaseObjectGroupResult); export const RunIfWaitingForDebuggerParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.runIfWaitingForDebugger.params", "commandParams", { method: "Runtime.runIfWaitingForDebugger" }); export const RunIfWaitingForDebuggerResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.runIfWaitingForDebugger.result", "commandResult", { method: "Runtime.runIfWaitingForDebugger" }); +export const RunIfWaitingForDebuggerCommand = withCdpCommand("Runtime.runIfWaitingForDebugger", RunIfWaitingForDebuggerParams, RunIfWaitingForDebuggerResult); export const RunScriptParams = withCdpMeta(z.object({ "scriptId": z.lazy(() => ScriptId), "executionContextId": z.lazy(() => ExecutionContextId).optional(), "objectGroup": z.string().optional(), "silent": z.boolean().optional(), "includeCommandLineAPI": z.boolean().optional(), "returnByValue": z.boolean().optional(), "generatePreview": z.boolean().optional(), "awaitPromise": z.boolean().optional() }).passthrough(), "Runtime.runScript.params", "commandParams", { method: "Runtime.runScript" }); export const RunScriptResult = withCdpMeta(z.object({ "result": z.lazy(() => RemoteObject), "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.runScript.result", "commandResult", { method: "Runtime.runScript" }); +export const RunScriptCommand = withCdpCommand("Runtime.runScript", RunScriptParams, RunScriptResult); export const SetAsyncCallStackDepthParams = withCdpMeta(z.object({ "maxDepth": z.number().int() }).passthrough(), "Runtime.setAsyncCallStackDepth.params", "commandParams", { method: "Runtime.setAsyncCallStackDepth" }); export const SetAsyncCallStackDepthResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.setAsyncCallStackDepth.result", "commandResult", { method: "Runtime.setAsyncCallStackDepth" }); +export const SetAsyncCallStackDepthCommand = withCdpCommand("Runtime.setAsyncCallStackDepth", SetAsyncCallStackDepthParams, SetAsyncCallStackDepthResult); export const SetCustomObjectFormatterEnabledParams = withCdpMeta(z.object({ "enabled": z.boolean() }).passthrough(), "Runtime.setCustomObjectFormatterEnabled.params", "commandParams", { method: "Runtime.setCustomObjectFormatterEnabled" }); export const SetCustomObjectFormatterEnabledResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.setCustomObjectFormatterEnabled.result", "commandResult", { method: "Runtime.setCustomObjectFormatterEnabled" }); +export const SetCustomObjectFormatterEnabledCommand = withCdpCommand("Runtime.setCustomObjectFormatterEnabled", SetCustomObjectFormatterEnabledParams, SetCustomObjectFormatterEnabledResult); export const SetMaxCallStackSizeToCaptureParams = withCdpMeta(z.object({ "size": z.number().int() }).passthrough(), "Runtime.setMaxCallStackSizeToCapture.params", "commandParams", { method: "Runtime.setMaxCallStackSizeToCapture" }); export const SetMaxCallStackSizeToCaptureResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.setMaxCallStackSizeToCapture.result", "commandResult", { method: "Runtime.setMaxCallStackSizeToCapture" }); +export const SetMaxCallStackSizeToCaptureCommand = withCdpCommand("Runtime.setMaxCallStackSizeToCapture", SetMaxCallStackSizeToCaptureParams, SetMaxCallStackSizeToCaptureResult); export const TerminateExecutionParams = withCdpMeta(z.object({ }).passthrough(), "Runtime.terminateExecution.params", "commandParams", { method: "Runtime.terminateExecution" }); export const TerminateExecutionResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.terminateExecution.result", "commandResult", { method: "Runtime.terminateExecution" }); +export const TerminateExecutionCommand = withCdpCommand("Runtime.terminateExecution", TerminateExecutionParams, TerminateExecutionResult); export const AddBindingParams = withCdpMeta(z.object({ "name": z.string(), "executionContextId": z.lazy(() => ExecutionContextId).optional(), "executionContextName": z.string().optional() }).passthrough(), "Runtime.addBinding.params", "commandParams", { method: "Runtime.addBinding" }); export const AddBindingResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.addBinding.result", "commandResult", { method: "Runtime.addBinding" }); +export const AddBindingCommand = withCdpCommand("Runtime.addBinding", AddBindingParams, AddBindingResult); export const RemoveBindingParams = withCdpMeta(z.object({ "name": z.string() }).passthrough(), "Runtime.removeBinding.params", "commandParams", { method: "Runtime.removeBinding" }); export const RemoveBindingResult = withCdpMeta(z.object({ }).passthrough(), "Runtime.removeBinding.result", "commandResult", { method: "Runtime.removeBinding" }); +export const RemoveBindingCommand = withCdpCommand("Runtime.removeBinding", RemoveBindingParams, RemoveBindingResult); export const GetExceptionDetailsParams = withCdpMeta(z.object({ "errorObjectId": z.lazy(() => RemoteObjectId) }).passthrough(), "Runtime.getExceptionDetails.params", "commandParams", { method: "Runtime.getExceptionDetails" }); export const GetExceptionDetailsResult = withCdpMeta(z.object({ "exceptionDetails": z.lazy(() => ExceptionDetails).optional() }).passthrough(), "Runtime.getExceptionDetails.result", "commandResult", { method: "Runtime.getExceptionDetails" }); +export const GetExceptionDetailsCommand = withCdpCommand("Runtime.getExceptionDetails", GetExceptionDetailsParams, GetExceptionDetailsResult); export const BindingCalledEvent = withCdpMeta(z.object({ "name": z.string(), "payload": z.string(), "executionContextId": z.lazy(() => ExecutionContextId) }).passthrough(), "Runtime.bindingCalled", "event", { phase: "event" }); export const ConsoleAPICalledEvent = withCdpMeta(z.object({ "type": z.enum(["log", "debug", "info", "error", "warning", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "profile", "profileEnd", "count", "timeEnd"]), "args": z.array(z.lazy(() => RemoteObject)), "executionContextId": z.lazy(() => ExecutionContextId), "timestamp": z.lazy(() => Timestamp), "stackTrace": z.lazy(() => StackTrace).optional(), "context": z.string().optional() }).passthrough(), "Runtime.consoleAPICalled", "event", { phase: "event" }); export const ExceptionRevokedEvent = withCdpMeta(z.object({ "reason": z.string(), "exceptionId": z.number().int() }).passthrough(), "Runtime.exceptionRevoked", "event", { phase: "event" }); @@ -161,29 +184,29 @@ export const zod = { InspectRequestedEvent: InspectRequestedEvent, } as const; export const commands = { - "Runtime.awaitPromise": { params: AwaitPromiseParams, result: AwaitPromiseResult }, - "Runtime.callFunctionOn": { params: CallFunctionOnParams, result: CallFunctionOnResult }, - "Runtime.compileScript": { params: CompileScriptParams, result: CompileScriptResult }, - "Runtime.disable": { params: DisableParams, result: DisableResult }, - "Runtime.discardConsoleEntries": { params: DiscardConsoleEntriesParams, result: DiscardConsoleEntriesResult }, - "Runtime.enable": { params: EnableParams, result: EnableResult }, - "Runtime.evaluate": { params: EvaluateParams, result: EvaluateResult }, - "Runtime.getIsolateId": { params: GetIsolateIdParams, result: GetIsolateIdResult }, - "Runtime.getHeapUsage": { params: GetHeapUsageParams, result: GetHeapUsageResult }, - "Runtime.getProperties": { params: GetPropertiesParams, result: GetPropertiesResult }, - "Runtime.globalLexicalScopeNames": { params: GlobalLexicalScopeNamesParams, result: GlobalLexicalScopeNamesResult }, - "Runtime.queryObjects": { params: QueryObjectsParams, result: QueryObjectsResult }, - "Runtime.releaseObject": { params: ReleaseObjectParams, result: ReleaseObjectResult }, - "Runtime.releaseObjectGroup": { params: ReleaseObjectGroupParams, result: ReleaseObjectGroupResult }, - "Runtime.runIfWaitingForDebugger": { params: RunIfWaitingForDebuggerParams, result: RunIfWaitingForDebuggerResult }, - "Runtime.runScript": { params: RunScriptParams, result: RunScriptResult }, - "Runtime.setAsyncCallStackDepth": { params: SetAsyncCallStackDepthParams, result: SetAsyncCallStackDepthResult }, - "Runtime.setCustomObjectFormatterEnabled": { params: SetCustomObjectFormatterEnabledParams, result: SetCustomObjectFormatterEnabledResult }, - "Runtime.setMaxCallStackSizeToCapture": { params: SetMaxCallStackSizeToCaptureParams, result: SetMaxCallStackSizeToCaptureResult }, - "Runtime.terminateExecution": { params: TerminateExecutionParams, result: TerminateExecutionResult }, - "Runtime.addBinding": { params: AddBindingParams, result: AddBindingResult }, - "Runtime.removeBinding": { params: RemoveBindingParams, result: RemoveBindingResult }, - "Runtime.getExceptionDetails": { params: GetExceptionDetailsParams, result: GetExceptionDetailsResult }, + "Runtime.awaitPromise": AwaitPromiseCommand, + "Runtime.callFunctionOn": CallFunctionOnCommand, + "Runtime.compileScript": CompileScriptCommand, + "Runtime.disable": DisableCommand, + "Runtime.discardConsoleEntries": DiscardConsoleEntriesCommand, + "Runtime.enable": EnableCommand, + "Runtime.evaluate": EvaluateCommand, + "Runtime.getIsolateId": GetIsolateIdCommand, + "Runtime.getHeapUsage": GetHeapUsageCommand, + "Runtime.getProperties": GetPropertiesCommand, + "Runtime.globalLexicalScopeNames": GlobalLexicalScopeNamesCommand, + "Runtime.queryObjects": QueryObjectsCommand, + "Runtime.releaseObject": ReleaseObjectCommand, + "Runtime.releaseObjectGroup": ReleaseObjectGroupCommand, + "Runtime.runIfWaitingForDebugger": RunIfWaitingForDebuggerCommand, + "Runtime.runScript": RunScriptCommand, + "Runtime.setAsyncCallStackDepth": SetAsyncCallStackDepthCommand, + "Runtime.setCustomObjectFormatterEnabled": SetCustomObjectFormatterEnabledCommand, + "Runtime.setMaxCallStackSizeToCapture": SetMaxCallStackSizeToCaptureCommand, + "Runtime.terminateExecution": TerminateExecutionCommand, + "Runtime.addBinding": AddBindingCommand, + "Runtime.removeBinding": RemoveBindingCommand, + "Runtime.getExceptionDetails": GetExceptionDetailsCommand, } as const; export const events = { "Runtime.bindingCalled": BindingCalledEvent, diff --git a/js/src/types/generated/zod/Schema.ts b/js/src/types/generated/zod/Schema.ts index bbc668b2..907e2fc5 100644 --- a/js/src/types/generated/zod/Schema.ts +++ b/js/src/types/generated/zod/Schema.ts @@ -1,11 +1,12 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const Domain = withCdpMeta(z.object({ "name": z.string(), "version": z.string() }).passthrough(), "Schema.Domain", "type"); export const GetDomainsParams = withCdpMeta(z.object({ }).passthrough(), "Schema.getDomains.params", "commandParams", { method: "Schema.getDomains" }); export const GetDomainsResult = withCdpMeta(z.object({ "domains": z.array(z.lazy(() => Domain)) }).passthrough(), "Schema.getDomains.result", "commandResult", { method: "Schema.getDomains" }); +export const GetDomainsCommand = withCdpCommand("Schema.getDomains", GetDomainsParams, GetDomainsResult); export const zod = { Domain: Domain, @@ -13,7 +14,7 @@ export const zod = { GetDomainsResult: GetDomainsResult, } as const; export const commands = { - "Schema.getDomains": { params: GetDomainsParams, result: GetDomainsResult }, + "Schema.getDomains": GetDomainsCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Security.ts b/js/src/types/generated/zod/Security.ts index 3c6c4e2a..bec165f0 100644 --- a/js/src/types/generated/zod/Security.ts +++ b/js/src/types/generated/zod/Security.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Network from "./Network.js"; export const CertificateId = withCdpMeta(z.number().int(), "Security.CertificateId", "type"); @@ -16,14 +16,19 @@ export const InsecureContentStatus = withCdpMeta(z.object({ "ranMixedContent": z export const CertificateErrorAction = withCdpMeta(z.enum(["continue", "cancel"]), "Security.CertificateErrorAction", "type"); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "Security.disable.params", "commandParams", { method: "Security.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "Security.disable.result", "commandResult", { method: "Security.disable" }); +export const DisableCommand = withCdpCommand("Security.disable", DisableParams, DisableResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "Security.enable.params", "commandParams", { method: "Security.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "Security.enable.result", "commandResult", { method: "Security.enable" }); +export const EnableCommand = withCdpCommand("Security.enable", EnableParams, EnableResult); export const SetIgnoreCertificateErrorsParams = withCdpMeta(z.object({ "ignore": z.boolean() }).passthrough(), "Security.setIgnoreCertificateErrors.params", "commandParams", { method: "Security.setIgnoreCertificateErrors" }); export const SetIgnoreCertificateErrorsResult = withCdpMeta(z.object({ }).passthrough(), "Security.setIgnoreCertificateErrors.result", "commandResult", { method: "Security.setIgnoreCertificateErrors" }); +export const SetIgnoreCertificateErrorsCommand = withCdpCommand("Security.setIgnoreCertificateErrors", SetIgnoreCertificateErrorsParams, SetIgnoreCertificateErrorsResult); export const HandleCertificateErrorParams = withCdpMeta(z.object({ "eventId": z.number().int(), "action": z.lazy(() => CertificateErrorAction) }).passthrough(), "Security.handleCertificateError.params", "commandParams", { method: "Security.handleCertificateError" }); export const HandleCertificateErrorResult = withCdpMeta(z.object({ }).passthrough(), "Security.handleCertificateError.result", "commandResult", { method: "Security.handleCertificateError" }); +export const HandleCertificateErrorCommand = withCdpCommand("Security.handleCertificateError", HandleCertificateErrorParams, HandleCertificateErrorResult); export const SetOverrideCertificateErrorsParams = withCdpMeta(z.object({ "override": z.boolean() }).passthrough(), "Security.setOverrideCertificateErrors.params", "commandParams", { method: "Security.setOverrideCertificateErrors" }); export const SetOverrideCertificateErrorsResult = withCdpMeta(z.object({ }).passthrough(), "Security.setOverrideCertificateErrors.result", "commandResult", { method: "Security.setOverrideCertificateErrors" }); +export const SetOverrideCertificateErrorsCommand = withCdpCommand("Security.setOverrideCertificateErrors", SetOverrideCertificateErrorsParams, SetOverrideCertificateErrorsResult); export const CertificateErrorEvent = withCdpMeta(z.object({ "eventId": z.number().int(), "errorType": z.string(), "requestURL": z.string() }).passthrough(), "Security.certificateError", "event", { phase: "event" }); export const VisibleSecurityStateChangedEvent = withCdpMeta(z.object({ "visibleSecurityState": z.lazy(() => VisibleSecurityState) }).passthrough(), "Security.visibleSecurityStateChanged", "event", { phase: "event" }); export const SecurityStateChangedEvent = withCdpMeta(z.object({ "securityState": z.lazy(() => SecurityState), "schemeIsCryptographic": z.boolean(), "explanations": z.array(z.lazy(() => SecurityStateExplanation)), "insecureContentStatus": z.lazy(() => InsecureContentStatus), "summary": z.string().optional() }).passthrough(), "Security.securityStateChanged", "event", { phase: "event" }); @@ -54,11 +59,11 @@ export const zod = { SecurityStateChangedEvent: SecurityStateChangedEvent, } as const; export const commands = { - "Security.disable": { params: DisableParams, result: DisableResult }, - "Security.enable": { params: EnableParams, result: EnableResult }, - "Security.setIgnoreCertificateErrors": { params: SetIgnoreCertificateErrorsParams, result: SetIgnoreCertificateErrorsResult }, - "Security.handleCertificateError": { params: HandleCertificateErrorParams, result: HandleCertificateErrorResult }, - "Security.setOverrideCertificateErrors": { params: SetOverrideCertificateErrorsParams, result: SetOverrideCertificateErrorsResult }, + "Security.disable": DisableCommand, + "Security.enable": EnableCommand, + "Security.setIgnoreCertificateErrors": SetIgnoreCertificateErrorsCommand, + "Security.handleCertificateError": HandleCertificateErrorCommand, + "Security.setOverrideCertificateErrors": SetOverrideCertificateErrorsCommand, } as const; export const events = { "Security.certificateError": CertificateErrorEvent, diff --git a/js/src/types/generated/zod/ServiceWorker.ts b/js/src/types/generated/zod/ServiceWorker.ts index 636a7765..d508970b 100644 --- a/js/src/types/generated/zod/ServiceWorker.ts +++ b/js/src/types/generated/zod/ServiceWorker.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Target from "./Target.js"; export const RegistrationID = withCdpMeta(z.string(), "ServiceWorker.RegistrationID", "type"); @@ -12,28 +12,40 @@ export const ServiceWorkerVersion = withCdpMeta(z.object({ "versionId": z.string export const ServiceWorkerErrorMessage = withCdpMeta(z.object({ "errorMessage": z.string(), "registrationId": z.lazy(() => RegistrationID), "versionId": z.string(), "sourceURL": z.string(), "lineNumber": z.number().int(), "columnNumber": z.number().int() }).passthrough(), "ServiceWorker.ServiceWorkerErrorMessage", "type"); export const DeliverPushMessageParams = withCdpMeta(z.object({ "origin": z.string(), "registrationId": z.lazy(() => RegistrationID), "data": z.string() }).passthrough(), "ServiceWorker.deliverPushMessage.params", "commandParams", { method: "ServiceWorker.deliverPushMessage" }); export const DeliverPushMessageResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.deliverPushMessage.result", "commandResult", { method: "ServiceWorker.deliverPushMessage" }); +export const DeliverPushMessageCommand = withCdpCommand("ServiceWorker.deliverPushMessage", DeliverPushMessageParams, DeliverPushMessageResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.disable.params", "commandParams", { method: "ServiceWorker.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.disable.result", "commandResult", { method: "ServiceWorker.disable" }); +export const DisableCommand = withCdpCommand("ServiceWorker.disable", DisableParams, DisableResult); export const DispatchSyncEventParams = withCdpMeta(z.object({ "origin": z.string(), "registrationId": z.lazy(() => RegistrationID), "tag": z.string(), "lastChance": z.boolean() }).passthrough(), "ServiceWorker.dispatchSyncEvent.params", "commandParams", { method: "ServiceWorker.dispatchSyncEvent" }); export const DispatchSyncEventResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.dispatchSyncEvent.result", "commandResult", { method: "ServiceWorker.dispatchSyncEvent" }); +export const DispatchSyncEventCommand = withCdpCommand("ServiceWorker.dispatchSyncEvent", DispatchSyncEventParams, DispatchSyncEventResult); export const DispatchPeriodicSyncEventParams = withCdpMeta(z.object({ "origin": z.string(), "registrationId": z.lazy(() => RegistrationID), "tag": z.string() }).passthrough(), "ServiceWorker.dispatchPeriodicSyncEvent.params", "commandParams", { method: "ServiceWorker.dispatchPeriodicSyncEvent" }); export const DispatchPeriodicSyncEventResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.dispatchPeriodicSyncEvent.result", "commandResult", { method: "ServiceWorker.dispatchPeriodicSyncEvent" }); +export const DispatchPeriodicSyncEventCommand = withCdpCommand("ServiceWorker.dispatchPeriodicSyncEvent", DispatchPeriodicSyncEventParams, DispatchPeriodicSyncEventResult); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.enable.params", "commandParams", { method: "ServiceWorker.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.enable.result", "commandResult", { method: "ServiceWorker.enable" }); +export const EnableCommand = withCdpCommand("ServiceWorker.enable", EnableParams, EnableResult); export const SetForceUpdateOnPageLoadParams = withCdpMeta(z.object({ "forceUpdateOnPageLoad": z.boolean() }).passthrough(), "ServiceWorker.setForceUpdateOnPageLoad.params", "commandParams", { method: "ServiceWorker.setForceUpdateOnPageLoad" }); export const SetForceUpdateOnPageLoadResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.setForceUpdateOnPageLoad.result", "commandResult", { method: "ServiceWorker.setForceUpdateOnPageLoad" }); +export const SetForceUpdateOnPageLoadCommand = withCdpCommand("ServiceWorker.setForceUpdateOnPageLoad", SetForceUpdateOnPageLoadParams, SetForceUpdateOnPageLoadResult); export const SkipWaitingParams = withCdpMeta(z.object({ "scopeURL": z.string() }).passthrough(), "ServiceWorker.skipWaiting.params", "commandParams", { method: "ServiceWorker.skipWaiting" }); export const SkipWaitingResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.skipWaiting.result", "commandResult", { method: "ServiceWorker.skipWaiting" }); +export const SkipWaitingCommand = withCdpCommand("ServiceWorker.skipWaiting", SkipWaitingParams, SkipWaitingResult); export const StartWorkerParams = withCdpMeta(z.object({ "scopeURL": z.string() }).passthrough(), "ServiceWorker.startWorker.params", "commandParams", { method: "ServiceWorker.startWorker" }); export const StartWorkerResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.startWorker.result", "commandResult", { method: "ServiceWorker.startWorker" }); +export const StartWorkerCommand = withCdpCommand("ServiceWorker.startWorker", StartWorkerParams, StartWorkerResult); export const StopAllWorkersParams = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.stopAllWorkers.params", "commandParams", { method: "ServiceWorker.stopAllWorkers" }); export const StopAllWorkersResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.stopAllWorkers.result", "commandResult", { method: "ServiceWorker.stopAllWorkers" }); +export const StopAllWorkersCommand = withCdpCommand("ServiceWorker.stopAllWorkers", StopAllWorkersParams, StopAllWorkersResult); export const StopWorkerParams = withCdpMeta(z.object({ "versionId": z.string() }).passthrough(), "ServiceWorker.stopWorker.params", "commandParams", { method: "ServiceWorker.stopWorker" }); export const StopWorkerResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.stopWorker.result", "commandResult", { method: "ServiceWorker.stopWorker" }); +export const StopWorkerCommand = withCdpCommand("ServiceWorker.stopWorker", StopWorkerParams, StopWorkerResult); export const UnregisterParams = withCdpMeta(z.object({ "scopeURL": z.string() }).passthrough(), "ServiceWorker.unregister.params", "commandParams", { method: "ServiceWorker.unregister" }); export const UnregisterResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.unregister.result", "commandResult", { method: "ServiceWorker.unregister" }); +export const UnregisterCommand = withCdpCommand("ServiceWorker.unregister", UnregisterParams, UnregisterResult); export const UpdateRegistrationParams = withCdpMeta(z.object({ "scopeURL": z.string() }).passthrough(), "ServiceWorker.updateRegistration.params", "commandParams", { method: "ServiceWorker.updateRegistration" }); export const UpdateRegistrationResult = withCdpMeta(z.object({ }).passthrough(), "ServiceWorker.updateRegistration.result", "commandResult", { method: "ServiceWorker.updateRegistration" }); +export const UpdateRegistrationCommand = withCdpCommand("ServiceWorker.updateRegistration", UpdateRegistrationParams, UpdateRegistrationResult); export const WorkerErrorReportedEvent = withCdpMeta(z.object({ "errorMessage": z.lazy(() => ServiceWorkerErrorMessage) }).passthrough(), "ServiceWorker.workerErrorReported", "event", { phase: "event" }); export const WorkerRegistrationUpdatedEvent = withCdpMeta(z.object({ "registrations": z.array(z.lazy(() => ServiceWorkerRegistration)) }).passthrough(), "ServiceWorker.workerRegistrationUpdated", "event", { phase: "event" }); export const WorkerVersionUpdatedEvent = withCdpMeta(z.object({ "versions": z.array(z.lazy(() => ServiceWorkerVersion)) }).passthrough(), "ServiceWorker.workerVersionUpdated", "event", { phase: "event" }); @@ -74,18 +86,18 @@ export const zod = { WorkerVersionUpdatedEvent: WorkerVersionUpdatedEvent, } as const; export const commands = { - "ServiceWorker.deliverPushMessage": { params: DeliverPushMessageParams, result: DeliverPushMessageResult }, - "ServiceWorker.disable": { params: DisableParams, result: DisableResult }, - "ServiceWorker.dispatchSyncEvent": { params: DispatchSyncEventParams, result: DispatchSyncEventResult }, - "ServiceWorker.dispatchPeriodicSyncEvent": { params: DispatchPeriodicSyncEventParams, result: DispatchPeriodicSyncEventResult }, - "ServiceWorker.enable": { params: EnableParams, result: EnableResult }, - "ServiceWorker.setForceUpdateOnPageLoad": { params: SetForceUpdateOnPageLoadParams, result: SetForceUpdateOnPageLoadResult }, - "ServiceWorker.skipWaiting": { params: SkipWaitingParams, result: SkipWaitingResult }, - "ServiceWorker.startWorker": { params: StartWorkerParams, result: StartWorkerResult }, - "ServiceWorker.stopAllWorkers": { params: StopAllWorkersParams, result: StopAllWorkersResult }, - "ServiceWorker.stopWorker": { params: StopWorkerParams, result: StopWorkerResult }, - "ServiceWorker.unregister": { params: UnregisterParams, result: UnregisterResult }, - "ServiceWorker.updateRegistration": { params: UpdateRegistrationParams, result: UpdateRegistrationResult }, + "ServiceWorker.deliverPushMessage": DeliverPushMessageCommand, + "ServiceWorker.disable": DisableCommand, + "ServiceWorker.dispatchSyncEvent": DispatchSyncEventCommand, + "ServiceWorker.dispatchPeriodicSyncEvent": DispatchPeriodicSyncEventCommand, + "ServiceWorker.enable": EnableCommand, + "ServiceWorker.setForceUpdateOnPageLoad": SetForceUpdateOnPageLoadCommand, + "ServiceWorker.skipWaiting": SkipWaitingCommand, + "ServiceWorker.startWorker": StartWorkerCommand, + "ServiceWorker.stopAllWorkers": StopAllWorkersCommand, + "ServiceWorker.stopWorker": StopWorkerCommand, + "ServiceWorker.unregister": UnregisterCommand, + "ServiceWorker.updateRegistration": UpdateRegistrationCommand, } as const; export const events = { "ServiceWorker.workerErrorReported": WorkerErrorReportedEvent, diff --git a/js/src/types/generated/zod/SmartCardEmulation.ts b/js/src/types/generated/zod/SmartCardEmulation.ts index 946008e3..255d0574 100644 --- a/js/src/types/generated/zod/SmartCardEmulation.ts +++ b/js/src/types/generated/zod/SmartCardEmulation.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const ResultCode = withCdpMeta(z.enum(["success", "removed-card", "reset-card", "unpowered-card", "unresponsive-card", "unsupported-card", "reader-unavailable", "sharing-violation", "not-transacted", "no-smartcard", "proto-mismatch", "system-cancelled", "not-ready", "cancelled", "insufficient-buffer", "invalid-handle", "invalid-parameter", "invalid-value", "no-memory", "timeout", "unknown-reader", "unsupported-feature", "no-readers-available", "service-stopped", "no-service", "comm-error", "internal-error", "server-too-busy", "unexpected", "shutdown", "unknown-card", "unknown"]), "SmartCardEmulation.ResultCode", "type"); export const ShareMode = withCdpMeta(z.enum(["shared", "exclusive", "direct"]), "SmartCardEmulation.ShareMode", "type"); @@ -14,28 +14,40 @@ export const ReaderStateIn = withCdpMeta(z.object({ "reader": z.string(), "curre export const ReaderStateOut = withCdpMeta(z.object({ "reader": z.string(), "eventState": z.lazy(() => ReaderStateFlags), "eventCount": z.number().int(), "atr": z.string() }).passthrough(), "SmartCardEmulation.ReaderStateOut", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.enable.params", "commandParams", { method: "SmartCardEmulation.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.enable.result", "commandResult", { method: "SmartCardEmulation.enable" }); +export const EnableCommand = withCdpCommand("SmartCardEmulation.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.disable.params", "commandParams", { method: "SmartCardEmulation.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.disable.result", "commandResult", { method: "SmartCardEmulation.disable" }); +export const DisableCommand = withCdpCommand("SmartCardEmulation.disable", DisableParams, DisableResult); export const ReportEstablishContextResultParams = withCdpMeta(z.object({ "requestId": z.string(), "contextId": z.number().int() }).passthrough(), "SmartCardEmulation.reportEstablishContextResult.params", "commandParams", { method: "SmartCardEmulation.reportEstablishContextResult" }); export const ReportEstablishContextResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportEstablishContextResult.result", "commandResult", { method: "SmartCardEmulation.reportEstablishContextResult" }); +export const ReportEstablishContextResultCommand = withCdpCommand("SmartCardEmulation.reportEstablishContextResult", ReportEstablishContextResultParams, ReportEstablishContextResultResult); export const ReportReleaseContextResultParams = withCdpMeta(z.object({ "requestId": z.string() }).passthrough(), "SmartCardEmulation.reportReleaseContextResult.params", "commandParams", { method: "SmartCardEmulation.reportReleaseContextResult" }); export const ReportReleaseContextResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportReleaseContextResult.result", "commandResult", { method: "SmartCardEmulation.reportReleaseContextResult" }); +export const ReportReleaseContextResultCommand = withCdpCommand("SmartCardEmulation.reportReleaseContextResult", ReportReleaseContextResultParams, ReportReleaseContextResultResult); export const ReportListReadersResultParams = withCdpMeta(z.object({ "requestId": z.string(), "readers": z.array(z.string()) }).passthrough(), "SmartCardEmulation.reportListReadersResult.params", "commandParams", { method: "SmartCardEmulation.reportListReadersResult" }); export const ReportListReadersResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportListReadersResult.result", "commandResult", { method: "SmartCardEmulation.reportListReadersResult" }); +export const ReportListReadersResultCommand = withCdpCommand("SmartCardEmulation.reportListReadersResult", ReportListReadersResultParams, ReportListReadersResultResult); export const ReportGetStatusChangeResultParams = withCdpMeta(z.object({ "requestId": z.string(), "readerStates": z.array(z.lazy(() => ReaderStateOut)) }).passthrough(), "SmartCardEmulation.reportGetStatusChangeResult.params", "commandParams", { method: "SmartCardEmulation.reportGetStatusChangeResult" }); export const ReportGetStatusChangeResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportGetStatusChangeResult.result", "commandResult", { method: "SmartCardEmulation.reportGetStatusChangeResult" }); +export const ReportGetStatusChangeResultCommand = withCdpCommand("SmartCardEmulation.reportGetStatusChangeResult", ReportGetStatusChangeResultParams, ReportGetStatusChangeResultResult); export const ReportBeginTransactionResultParams = withCdpMeta(z.object({ "requestId": z.string(), "handle": z.number().int() }).passthrough(), "SmartCardEmulation.reportBeginTransactionResult.params", "commandParams", { method: "SmartCardEmulation.reportBeginTransactionResult" }); export const ReportBeginTransactionResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportBeginTransactionResult.result", "commandResult", { method: "SmartCardEmulation.reportBeginTransactionResult" }); +export const ReportBeginTransactionResultCommand = withCdpCommand("SmartCardEmulation.reportBeginTransactionResult", ReportBeginTransactionResultParams, ReportBeginTransactionResultResult); export const ReportPlainResultParams = withCdpMeta(z.object({ "requestId": z.string() }).passthrough(), "SmartCardEmulation.reportPlainResult.params", "commandParams", { method: "SmartCardEmulation.reportPlainResult" }); export const ReportPlainResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportPlainResult.result", "commandResult", { method: "SmartCardEmulation.reportPlainResult" }); +export const ReportPlainResultCommand = withCdpCommand("SmartCardEmulation.reportPlainResult", ReportPlainResultParams, ReportPlainResultResult); export const ReportConnectResultParams = withCdpMeta(z.object({ "requestId": z.string(), "handle": z.number().int(), "activeProtocol": z.lazy(() => Protocol).optional() }).passthrough(), "SmartCardEmulation.reportConnectResult.params", "commandParams", { method: "SmartCardEmulation.reportConnectResult" }); export const ReportConnectResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportConnectResult.result", "commandResult", { method: "SmartCardEmulation.reportConnectResult" }); +export const ReportConnectResultCommand = withCdpCommand("SmartCardEmulation.reportConnectResult", ReportConnectResultParams, ReportConnectResultResult); export const ReportDataResultParams = withCdpMeta(z.object({ "requestId": z.string(), "data": z.string() }).passthrough(), "SmartCardEmulation.reportDataResult.params", "commandParams", { method: "SmartCardEmulation.reportDataResult" }); export const ReportDataResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportDataResult.result", "commandResult", { method: "SmartCardEmulation.reportDataResult" }); +export const ReportDataResultCommand = withCdpCommand("SmartCardEmulation.reportDataResult", ReportDataResultParams, ReportDataResultResult); export const ReportStatusResultParams = withCdpMeta(z.object({ "requestId": z.string(), "readerName": z.string(), "state": z.lazy(() => ConnectionState), "atr": z.string(), "protocol": z.lazy(() => Protocol).optional() }).passthrough(), "SmartCardEmulation.reportStatusResult.params", "commandParams", { method: "SmartCardEmulation.reportStatusResult" }); export const ReportStatusResultResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportStatusResult.result", "commandResult", { method: "SmartCardEmulation.reportStatusResult" }); +export const ReportStatusResultCommand = withCdpCommand("SmartCardEmulation.reportStatusResult", ReportStatusResultParams, ReportStatusResultResult); export const ReportErrorParams = withCdpMeta(z.object({ "requestId": z.string(), "resultCode": z.lazy(() => ResultCode) }).passthrough(), "SmartCardEmulation.reportError.params", "commandParams", { method: "SmartCardEmulation.reportError" }); export const ReportErrorResult = withCdpMeta(z.object({ }).passthrough(), "SmartCardEmulation.reportError.result", "commandResult", { method: "SmartCardEmulation.reportError" }); +export const ReportErrorCommand = withCdpCommand("SmartCardEmulation.reportError", ReportErrorParams, ReportErrorResult); export const EstablishContextRequestedEvent = withCdpMeta(z.object({ "requestId": z.string() }).passthrough(), "SmartCardEmulation.establishContextRequested", "event", { phase: "event" }); export const ReleaseContextRequestedEvent = withCdpMeta(z.object({ "requestId": z.string(), "contextId": z.number().int() }).passthrough(), "SmartCardEmulation.releaseContextRequested", "event", { phase: "event" }); export const ListReadersRequestedEvent = withCdpMeta(z.object({ "requestId": z.string(), "contextId": z.number().int() }).passthrough(), "SmartCardEmulation.listReadersRequested", "event", { phase: "event" }); @@ -101,18 +113,18 @@ export const zod = { EndTransactionRequestedEvent: EndTransactionRequestedEvent, } as const; export const commands = { - "SmartCardEmulation.enable": { params: EnableParams, result: EnableResult }, - "SmartCardEmulation.disable": { params: DisableParams, result: DisableResult }, - "SmartCardEmulation.reportEstablishContextResult": { params: ReportEstablishContextResultParams, result: ReportEstablishContextResultResult }, - "SmartCardEmulation.reportReleaseContextResult": { params: ReportReleaseContextResultParams, result: ReportReleaseContextResultResult }, - "SmartCardEmulation.reportListReadersResult": { params: ReportListReadersResultParams, result: ReportListReadersResultResult }, - "SmartCardEmulation.reportGetStatusChangeResult": { params: ReportGetStatusChangeResultParams, result: ReportGetStatusChangeResultResult }, - "SmartCardEmulation.reportBeginTransactionResult": { params: ReportBeginTransactionResultParams, result: ReportBeginTransactionResultResult }, - "SmartCardEmulation.reportPlainResult": { params: ReportPlainResultParams, result: ReportPlainResultResult }, - "SmartCardEmulation.reportConnectResult": { params: ReportConnectResultParams, result: ReportConnectResultResult }, - "SmartCardEmulation.reportDataResult": { params: ReportDataResultParams, result: ReportDataResultResult }, - "SmartCardEmulation.reportStatusResult": { params: ReportStatusResultParams, result: ReportStatusResultResult }, - "SmartCardEmulation.reportError": { params: ReportErrorParams, result: ReportErrorResult }, + "SmartCardEmulation.enable": EnableCommand, + "SmartCardEmulation.disable": DisableCommand, + "SmartCardEmulation.reportEstablishContextResult": ReportEstablishContextResultCommand, + "SmartCardEmulation.reportReleaseContextResult": ReportReleaseContextResultCommand, + "SmartCardEmulation.reportListReadersResult": ReportListReadersResultCommand, + "SmartCardEmulation.reportGetStatusChangeResult": ReportGetStatusChangeResultCommand, + "SmartCardEmulation.reportBeginTransactionResult": ReportBeginTransactionResultCommand, + "SmartCardEmulation.reportPlainResult": ReportPlainResultCommand, + "SmartCardEmulation.reportConnectResult": ReportConnectResultCommand, + "SmartCardEmulation.reportDataResult": ReportDataResultCommand, + "SmartCardEmulation.reportStatusResult": ReportStatusResultCommand, + "SmartCardEmulation.reportError": ReportErrorCommand, } as const; export const events = { "SmartCardEmulation.establishContextRequested": EstablishContextRequestedEvent, diff --git a/js/src/types/generated/zod/Storage.ts b/js/src/types/generated/zod/Storage.ts index 637aafbb..14dc2333 100644 --- a/js/src/types/generated/zod/Storage.ts +++ b/js/src/types/generated/zod/Storage.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Browser from "./Browser.js"; import * as Network from "./Network.js"; import * as Page from "./Page.js"; @@ -29,72 +29,106 @@ export const StorageBucketInfo = withCdpMeta(z.object({ "bucket": z.lazy(() => S export const RelatedWebsiteSet = withCdpMeta(z.object({ "primarySites": z.array(z.string()), "associatedSites": z.array(z.string()), "serviceSites": z.array(z.string()) }).passthrough(), "Storage.RelatedWebsiteSet", "type"); export const GetStorageKeyForFrameParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId) }).passthrough(), "Storage.getStorageKeyForFrame.params", "commandParams", { method: "Storage.getStorageKeyForFrame" }); export const GetStorageKeyForFrameResult = withCdpMeta(z.object({ "storageKey": z.lazy(() => SerializedStorageKey) }).passthrough(), "Storage.getStorageKeyForFrame.result", "commandResult", { method: "Storage.getStorageKeyForFrame" }); +export const GetStorageKeyForFrameCommand = withCdpCommand("Storage.getStorageKeyForFrame", GetStorageKeyForFrameParams, GetStorageKeyForFrameResult); export const GetStorageKeyParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId).optional() }).passthrough(), "Storage.getStorageKey.params", "commandParams", { method: "Storage.getStorageKey" }); export const GetStorageKeyResult = withCdpMeta(z.object({ "storageKey": z.lazy(() => SerializedStorageKey) }).passthrough(), "Storage.getStorageKey.result", "commandResult", { method: "Storage.getStorageKey" }); +export const GetStorageKeyCommand = withCdpCommand("Storage.getStorageKey", GetStorageKeyParams, GetStorageKeyResult); export const ClearDataForOriginParams = withCdpMeta(z.object({ "origin": z.string(), "storageTypes": z.string() }).passthrough(), "Storage.clearDataForOrigin.params", "commandParams", { method: "Storage.clearDataForOrigin" }); export const ClearDataForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.clearDataForOrigin.result", "commandResult", { method: "Storage.clearDataForOrigin" }); +export const ClearDataForOriginCommand = withCdpCommand("Storage.clearDataForOrigin", ClearDataForOriginParams, ClearDataForOriginResult); export const ClearDataForStorageKeyParams = withCdpMeta(z.object({ "storageKey": z.string(), "storageTypes": z.string() }).passthrough(), "Storage.clearDataForStorageKey.params", "commandParams", { method: "Storage.clearDataForStorageKey" }); export const ClearDataForStorageKeyResult = withCdpMeta(z.object({ }).passthrough(), "Storage.clearDataForStorageKey.result", "commandResult", { method: "Storage.clearDataForStorageKey" }); +export const ClearDataForStorageKeyCommand = withCdpCommand("Storage.clearDataForStorageKey", ClearDataForStorageKeyParams, ClearDataForStorageKeyResult); export const GetCookiesParams = withCdpMeta(z.object({ "browserContextId": z.lazy(() => Browser.BrowserContextID).optional() }).passthrough(), "Storage.getCookies.params", "commandParams", { method: "Storage.getCookies" }); export const GetCookiesResult = withCdpMeta(z.object({ "cookies": z.array(z.lazy(() => Network.Cookie)) }).passthrough(), "Storage.getCookies.result", "commandResult", { method: "Storage.getCookies" }); +export const GetCookiesCommand = withCdpCommand("Storage.getCookies", GetCookiesParams, GetCookiesResult); export const SetCookiesParams = withCdpMeta(z.object({ "cookies": z.array(z.lazy(() => Network.CookieParam)), "browserContextId": z.lazy(() => Browser.BrowserContextID).optional() }).passthrough(), "Storage.setCookies.params", "commandParams", { method: "Storage.setCookies" }); export const SetCookiesResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setCookies.result", "commandResult", { method: "Storage.setCookies" }); +export const SetCookiesCommand = withCdpCommand("Storage.setCookies", SetCookiesParams, SetCookiesResult); export const ClearCookiesParams = withCdpMeta(z.object({ "browserContextId": z.lazy(() => Browser.BrowserContextID).optional() }).passthrough(), "Storage.clearCookies.params", "commandParams", { method: "Storage.clearCookies" }); export const ClearCookiesResult = withCdpMeta(z.object({ }).passthrough(), "Storage.clearCookies.result", "commandResult", { method: "Storage.clearCookies" }); +export const ClearCookiesCommand = withCdpCommand("Storage.clearCookies", ClearCookiesParams, ClearCookiesResult); export const GetUsageAndQuotaParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Storage.getUsageAndQuota.params", "commandParams", { method: "Storage.getUsageAndQuota" }); export const GetUsageAndQuotaResult = withCdpMeta(z.object({ "usage": z.number(), "quota": z.number(), "overrideActive": z.boolean(), "usageBreakdown": z.array(z.lazy(() => UsageForType)) }).passthrough(), "Storage.getUsageAndQuota.result", "commandResult", { method: "Storage.getUsageAndQuota" }); +export const GetUsageAndQuotaCommand = withCdpCommand("Storage.getUsageAndQuota", GetUsageAndQuotaParams, GetUsageAndQuotaResult); export const OverrideQuotaForOriginParams = withCdpMeta(z.object({ "origin": z.string(), "quotaSize": z.number().optional() }).passthrough(), "Storage.overrideQuotaForOrigin.params", "commandParams", { method: "Storage.overrideQuotaForOrigin" }); export const OverrideQuotaForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.overrideQuotaForOrigin.result", "commandResult", { method: "Storage.overrideQuotaForOrigin" }); +export const OverrideQuotaForOriginCommand = withCdpCommand("Storage.overrideQuotaForOrigin", OverrideQuotaForOriginParams, OverrideQuotaForOriginResult); export const TrackCacheStorageForOriginParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Storage.trackCacheStorageForOrigin.params", "commandParams", { method: "Storage.trackCacheStorageForOrigin" }); export const TrackCacheStorageForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.trackCacheStorageForOrigin.result", "commandResult", { method: "Storage.trackCacheStorageForOrigin" }); +export const TrackCacheStorageForOriginCommand = withCdpCommand("Storage.trackCacheStorageForOrigin", TrackCacheStorageForOriginParams, TrackCacheStorageForOriginResult); export const TrackCacheStorageForStorageKeyParams = withCdpMeta(z.object({ "storageKey": z.string() }).passthrough(), "Storage.trackCacheStorageForStorageKey.params", "commandParams", { method: "Storage.trackCacheStorageForStorageKey" }); export const TrackCacheStorageForStorageKeyResult = withCdpMeta(z.object({ }).passthrough(), "Storage.trackCacheStorageForStorageKey.result", "commandResult", { method: "Storage.trackCacheStorageForStorageKey" }); +export const TrackCacheStorageForStorageKeyCommand = withCdpCommand("Storage.trackCacheStorageForStorageKey", TrackCacheStorageForStorageKeyParams, TrackCacheStorageForStorageKeyResult); export const TrackIndexedDBForOriginParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Storage.trackIndexedDBForOrigin.params", "commandParams", { method: "Storage.trackIndexedDBForOrigin" }); export const TrackIndexedDBForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.trackIndexedDBForOrigin.result", "commandResult", { method: "Storage.trackIndexedDBForOrigin" }); +export const TrackIndexedDBForOriginCommand = withCdpCommand("Storage.trackIndexedDBForOrigin", TrackIndexedDBForOriginParams, TrackIndexedDBForOriginResult); export const TrackIndexedDBForStorageKeyParams = withCdpMeta(z.object({ "storageKey": z.string() }).passthrough(), "Storage.trackIndexedDBForStorageKey.params", "commandParams", { method: "Storage.trackIndexedDBForStorageKey" }); export const TrackIndexedDBForStorageKeyResult = withCdpMeta(z.object({ }).passthrough(), "Storage.trackIndexedDBForStorageKey.result", "commandResult", { method: "Storage.trackIndexedDBForStorageKey" }); +export const TrackIndexedDBForStorageKeyCommand = withCdpCommand("Storage.trackIndexedDBForStorageKey", TrackIndexedDBForStorageKeyParams, TrackIndexedDBForStorageKeyResult); export const UntrackCacheStorageForOriginParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Storage.untrackCacheStorageForOrigin.params", "commandParams", { method: "Storage.untrackCacheStorageForOrigin" }); export const UntrackCacheStorageForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.untrackCacheStorageForOrigin.result", "commandResult", { method: "Storage.untrackCacheStorageForOrigin" }); +export const UntrackCacheStorageForOriginCommand = withCdpCommand("Storage.untrackCacheStorageForOrigin", UntrackCacheStorageForOriginParams, UntrackCacheStorageForOriginResult); export const UntrackCacheStorageForStorageKeyParams = withCdpMeta(z.object({ "storageKey": z.string() }).passthrough(), "Storage.untrackCacheStorageForStorageKey.params", "commandParams", { method: "Storage.untrackCacheStorageForStorageKey" }); export const UntrackCacheStorageForStorageKeyResult = withCdpMeta(z.object({ }).passthrough(), "Storage.untrackCacheStorageForStorageKey.result", "commandResult", { method: "Storage.untrackCacheStorageForStorageKey" }); +export const UntrackCacheStorageForStorageKeyCommand = withCdpCommand("Storage.untrackCacheStorageForStorageKey", UntrackCacheStorageForStorageKeyParams, UntrackCacheStorageForStorageKeyResult); export const UntrackIndexedDBForOriginParams = withCdpMeta(z.object({ "origin": z.string() }).passthrough(), "Storage.untrackIndexedDBForOrigin.params", "commandParams", { method: "Storage.untrackIndexedDBForOrigin" }); export const UntrackIndexedDBForOriginResult = withCdpMeta(z.object({ }).passthrough(), "Storage.untrackIndexedDBForOrigin.result", "commandResult", { method: "Storage.untrackIndexedDBForOrigin" }); +export const UntrackIndexedDBForOriginCommand = withCdpCommand("Storage.untrackIndexedDBForOrigin", UntrackIndexedDBForOriginParams, UntrackIndexedDBForOriginResult); export const UntrackIndexedDBForStorageKeyParams = withCdpMeta(z.object({ "storageKey": z.string() }).passthrough(), "Storage.untrackIndexedDBForStorageKey.params", "commandParams", { method: "Storage.untrackIndexedDBForStorageKey" }); export const UntrackIndexedDBForStorageKeyResult = withCdpMeta(z.object({ }).passthrough(), "Storage.untrackIndexedDBForStorageKey.result", "commandResult", { method: "Storage.untrackIndexedDBForStorageKey" }); +export const UntrackIndexedDBForStorageKeyCommand = withCdpCommand("Storage.untrackIndexedDBForStorageKey", UntrackIndexedDBForStorageKeyParams, UntrackIndexedDBForStorageKeyResult); export const GetTrustTokensParams = withCdpMeta(z.object({ }).passthrough(), "Storage.getTrustTokens.params", "commandParams", { method: "Storage.getTrustTokens" }); export const GetTrustTokensResult = withCdpMeta(z.object({ "tokens": z.array(z.lazy(() => TrustTokens)) }).passthrough(), "Storage.getTrustTokens.result", "commandResult", { method: "Storage.getTrustTokens" }); +export const GetTrustTokensCommand = withCdpCommand("Storage.getTrustTokens", GetTrustTokensParams, GetTrustTokensResult); export const ClearTrustTokensParams = withCdpMeta(z.object({ "issuerOrigin": z.string() }).passthrough(), "Storage.clearTrustTokens.params", "commandParams", { method: "Storage.clearTrustTokens" }); export const ClearTrustTokensResult = withCdpMeta(z.object({ "didDeleteTokens": z.boolean() }).passthrough(), "Storage.clearTrustTokens.result", "commandResult", { method: "Storage.clearTrustTokens" }); +export const ClearTrustTokensCommand = withCdpCommand("Storage.clearTrustTokens", ClearTrustTokensParams, ClearTrustTokensResult); export const GetInterestGroupDetailsParams = withCdpMeta(z.object({ "ownerOrigin": z.string(), "name": z.string() }).passthrough(), "Storage.getInterestGroupDetails.params", "commandParams", { method: "Storage.getInterestGroupDetails" }); export const GetInterestGroupDetailsResult = withCdpMeta(z.object({ "details": z.record(z.string(), z.unknown()) }).passthrough(), "Storage.getInterestGroupDetails.result", "commandResult", { method: "Storage.getInterestGroupDetails" }); +export const GetInterestGroupDetailsCommand = withCdpCommand("Storage.getInterestGroupDetails", GetInterestGroupDetailsParams, GetInterestGroupDetailsResult); export const SetInterestGroupTrackingParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "Storage.setInterestGroupTracking.params", "commandParams", { method: "Storage.setInterestGroupTracking" }); export const SetInterestGroupTrackingResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setInterestGroupTracking.result", "commandResult", { method: "Storage.setInterestGroupTracking" }); +export const SetInterestGroupTrackingCommand = withCdpCommand("Storage.setInterestGroupTracking", SetInterestGroupTrackingParams, SetInterestGroupTrackingResult); export const SetInterestGroupAuctionTrackingParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "Storage.setInterestGroupAuctionTracking.params", "commandParams", { method: "Storage.setInterestGroupAuctionTracking" }); export const SetInterestGroupAuctionTrackingResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setInterestGroupAuctionTracking.result", "commandResult", { method: "Storage.setInterestGroupAuctionTracking" }); +export const SetInterestGroupAuctionTrackingCommand = withCdpCommand("Storage.setInterestGroupAuctionTracking", SetInterestGroupAuctionTrackingParams, SetInterestGroupAuctionTrackingResult); export const GetSharedStorageMetadataParams = withCdpMeta(z.object({ "ownerOrigin": z.string() }).passthrough(), "Storage.getSharedStorageMetadata.params", "commandParams", { method: "Storage.getSharedStorageMetadata" }); export const GetSharedStorageMetadataResult = withCdpMeta(z.object({ "metadata": z.lazy(() => SharedStorageMetadata) }).passthrough(), "Storage.getSharedStorageMetadata.result", "commandResult", { method: "Storage.getSharedStorageMetadata" }); +export const GetSharedStorageMetadataCommand = withCdpCommand("Storage.getSharedStorageMetadata", GetSharedStorageMetadataParams, GetSharedStorageMetadataResult); export const GetSharedStorageEntriesParams = withCdpMeta(z.object({ "ownerOrigin": z.string() }).passthrough(), "Storage.getSharedStorageEntries.params", "commandParams", { method: "Storage.getSharedStorageEntries" }); export const GetSharedStorageEntriesResult = withCdpMeta(z.object({ "entries": z.array(z.lazy(() => SharedStorageEntry)) }).passthrough(), "Storage.getSharedStorageEntries.result", "commandResult", { method: "Storage.getSharedStorageEntries" }); +export const GetSharedStorageEntriesCommand = withCdpCommand("Storage.getSharedStorageEntries", GetSharedStorageEntriesParams, GetSharedStorageEntriesResult); export const SetSharedStorageEntryParams = withCdpMeta(z.object({ "ownerOrigin": z.string(), "key": z.string(), "value": z.string(), "ignoreIfPresent": z.boolean().optional() }).passthrough(), "Storage.setSharedStorageEntry.params", "commandParams", { method: "Storage.setSharedStorageEntry" }); export const SetSharedStorageEntryResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setSharedStorageEntry.result", "commandResult", { method: "Storage.setSharedStorageEntry" }); +export const SetSharedStorageEntryCommand = withCdpCommand("Storage.setSharedStorageEntry", SetSharedStorageEntryParams, SetSharedStorageEntryResult); export const DeleteSharedStorageEntryParams = withCdpMeta(z.object({ "ownerOrigin": z.string(), "key": z.string() }).passthrough(), "Storage.deleteSharedStorageEntry.params", "commandParams", { method: "Storage.deleteSharedStorageEntry" }); export const DeleteSharedStorageEntryResult = withCdpMeta(z.object({ }).passthrough(), "Storage.deleteSharedStorageEntry.result", "commandResult", { method: "Storage.deleteSharedStorageEntry" }); +export const DeleteSharedStorageEntryCommand = withCdpCommand("Storage.deleteSharedStorageEntry", DeleteSharedStorageEntryParams, DeleteSharedStorageEntryResult); export const ClearSharedStorageEntriesParams = withCdpMeta(z.object({ "ownerOrigin": z.string() }).passthrough(), "Storage.clearSharedStorageEntries.params", "commandParams", { method: "Storage.clearSharedStorageEntries" }); export const ClearSharedStorageEntriesResult = withCdpMeta(z.object({ }).passthrough(), "Storage.clearSharedStorageEntries.result", "commandResult", { method: "Storage.clearSharedStorageEntries" }); +export const ClearSharedStorageEntriesCommand = withCdpCommand("Storage.clearSharedStorageEntries", ClearSharedStorageEntriesParams, ClearSharedStorageEntriesResult); export const ResetSharedStorageBudgetParams = withCdpMeta(z.object({ "ownerOrigin": z.string() }).passthrough(), "Storage.resetSharedStorageBudget.params", "commandParams", { method: "Storage.resetSharedStorageBudget" }); export const ResetSharedStorageBudgetResult = withCdpMeta(z.object({ }).passthrough(), "Storage.resetSharedStorageBudget.result", "commandResult", { method: "Storage.resetSharedStorageBudget" }); +export const ResetSharedStorageBudgetCommand = withCdpCommand("Storage.resetSharedStorageBudget", ResetSharedStorageBudgetParams, ResetSharedStorageBudgetResult); export const SetSharedStorageTrackingParams = withCdpMeta(z.object({ "enable": z.boolean() }).passthrough(), "Storage.setSharedStorageTracking.params", "commandParams", { method: "Storage.setSharedStorageTracking" }); export const SetSharedStorageTrackingResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setSharedStorageTracking.result", "commandResult", { method: "Storage.setSharedStorageTracking" }); +export const SetSharedStorageTrackingCommand = withCdpCommand("Storage.setSharedStorageTracking", SetSharedStorageTrackingParams, SetSharedStorageTrackingResult); export const SetStorageBucketTrackingParams = withCdpMeta(z.object({ "storageKey": z.string(), "enable": z.boolean() }).passthrough(), "Storage.setStorageBucketTracking.params", "commandParams", { method: "Storage.setStorageBucketTracking" }); export const SetStorageBucketTrackingResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setStorageBucketTracking.result", "commandResult", { method: "Storage.setStorageBucketTracking" }); +export const SetStorageBucketTrackingCommand = withCdpCommand("Storage.setStorageBucketTracking", SetStorageBucketTrackingParams, SetStorageBucketTrackingResult); export const DeleteStorageBucketParams = withCdpMeta(z.object({ "bucket": z.lazy(() => StorageBucket) }).passthrough(), "Storage.deleteStorageBucket.params", "commandParams", { method: "Storage.deleteStorageBucket" }); export const DeleteStorageBucketResult = withCdpMeta(z.object({ }).passthrough(), "Storage.deleteStorageBucket.result", "commandResult", { method: "Storage.deleteStorageBucket" }); +export const DeleteStorageBucketCommand = withCdpCommand("Storage.deleteStorageBucket", DeleteStorageBucketParams, DeleteStorageBucketResult); export const RunBounceTrackingMitigationsParams = withCdpMeta(z.object({ }).passthrough(), "Storage.runBounceTrackingMitigations.params", "commandParams", { method: "Storage.runBounceTrackingMitigations" }); export const RunBounceTrackingMitigationsResult = withCdpMeta(z.object({ "deletedSites": z.array(z.string()) }).passthrough(), "Storage.runBounceTrackingMitigations.result", "commandResult", { method: "Storage.runBounceTrackingMitigations" }); +export const RunBounceTrackingMitigationsCommand = withCdpCommand("Storage.runBounceTrackingMitigations", RunBounceTrackingMitigationsParams, RunBounceTrackingMitigationsResult); export const GetRelatedWebsiteSetsParams = withCdpMeta(z.object({ }).passthrough(), "Storage.getRelatedWebsiteSets.params", "commandParams", { method: "Storage.getRelatedWebsiteSets" }); export const GetRelatedWebsiteSetsResult = withCdpMeta(z.object({ "sets": z.array(z.lazy(() => RelatedWebsiteSet)) }).passthrough(), "Storage.getRelatedWebsiteSets.result", "commandResult", { method: "Storage.getRelatedWebsiteSets" }); +export const GetRelatedWebsiteSetsCommand = withCdpCommand("Storage.getRelatedWebsiteSets", GetRelatedWebsiteSetsParams, GetRelatedWebsiteSetsResult); export const SetProtectedAudienceKAnonymityParams = withCdpMeta(z.object({ "owner": z.string(), "name": z.string(), "hashes": z.array(z.string()) }).passthrough(), "Storage.setProtectedAudienceKAnonymity.params", "commandParams", { method: "Storage.setProtectedAudienceKAnonymity" }); export const SetProtectedAudienceKAnonymityResult = withCdpMeta(z.object({ }).passthrough(), "Storage.setProtectedAudienceKAnonymity.result", "commandResult", { method: "Storage.setProtectedAudienceKAnonymity" }); +export const SetProtectedAudienceKAnonymityCommand = withCdpCommand("Storage.setProtectedAudienceKAnonymity", SetProtectedAudienceKAnonymityParams, SetProtectedAudienceKAnonymityResult); export const CacheStorageContentUpdatedEvent = withCdpMeta(z.object({ "origin": z.string(), "storageKey": z.string(), "bucketId": z.string(), "cacheName": z.string() }).passthrough(), "Storage.cacheStorageContentUpdated", "event", { phase: "event" }); export const CacheStorageListUpdatedEvent = withCdpMeta(z.object({ "origin": z.string(), "storageKey": z.string(), "bucketId": z.string() }).passthrough(), "Storage.cacheStorageListUpdated", "event", { phase: "event" }); export const IndexedDBContentUpdatedEvent = withCdpMeta(z.object({ "origin": z.string(), "storageKey": z.string(), "bucketId": z.string(), "databaseName": z.string(), "objectStoreName": z.string() }).passthrough(), "Storage.indexedDBContentUpdated", "event", { phase: "event" }); @@ -209,40 +243,40 @@ export const zod = { StorageBucketDeletedEvent: StorageBucketDeletedEvent, } as const; export const commands = { - "Storage.getStorageKeyForFrame": { params: GetStorageKeyForFrameParams, result: GetStorageKeyForFrameResult }, - "Storage.getStorageKey": { params: GetStorageKeyParams, result: GetStorageKeyResult }, - "Storage.clearDataForOrigin": { params: ClearDataForOriginParams, result: ClearDataForOriginResult }, - "Storage.clearDataForStorageKey": { params: ClearDataForStorageKeyParams, result: ClearDataForStorageKeyResult }, - "Storage.getCookies": { params: GetCookiesParams, result: GetCookiesResult }, - "Storage.setCookies": { params: SetCookiesParams, result: SetCookiesResult }, - "Storage.clearCookies": { params: ClearCookiesParams, result: ClearCookiesResult }, - "Storage.getUsageAndQuota": { params: GetUsageAndQuotaParams, result: GetUsageAndQuotaResult }, - "Storage.overrideQuotaForOrigin": { params: OverrideQuotaForOriginParams, result: OverrideQuotaForOriginResult }, - "Storage.trackCacheStorageForOrigin": { params: TrackCacheStorageForOriginParams, result: TrackCacheStorageForOriginResult }, - "Storage.trackCacheStorageForStorageKey": { params: TrackCacheStorageForStorageKeyParams, result: TrackCacheStorageForStorageKeyResult }, - "Storage.trackIndexedDBForOrigin": { params: TrackIndexedDBForOriginParams, result: TrackIndexedDBForOriginResult }, - "Storage.trackIndexedDBForStorageKey": { params: TrackIndexedDBForStorageKeyParams, result: TrackIndexedDBForStorageKeyResult }, - "Storage.untrackCacheStorageForOrigin": { params: UntrackCacheStorageForOriginParams, result: UntrackCacheStorageForOriginResult }, - "Storage.untrackCacheStorageForStorageKey": { params: UntrackCacheStorageForStorageKeyParams, result: UntrackCacheStorageForStorageKeyResult }, - "Storage.untrackIndexedDBForOrigin": { params: UntrackIndexedDBForOriginParams, result: UntrackIndexedDBForOriginResult }, - "Storage.untrackIndexedDBForStorageKey": { params: UntrackIndexedDBForStorageKeyParams, result: UntrackIndexedDBForStorageKeyResult }, - "Storage.getTrustTokens": { params: GetTrustTokensParams, result: GetTrustTokensResult }, - "Storage.clearTrustTokens": { params: ClearTrustTokensParams, result: ClearTrustTokensResult }, - "Storage.getInterestGroupDetails": { params: GetInterestGroupDetailsParams, result: GetInterestGroupDetailsResult }, - "Storage.setInterestGroupTracking": { params: SetInterestGroupTrackingParams, result: SetInterestGroupTrackingResult }, - "Storage.setInterestGroupAuctionTracking": { params: SetInterestGroupAuctionTrackingParams, result: SetInterestGroupAuctionTrackingResult }, - "Storage.getSharedStorageMetadata": { params: GetSharedStorageMetadataParams, result: GetSharedStorageMetadataResult }, - "Storage.getSharedStorageEntries": { params: GetSharedStorageEntriesParams, result: GetSharedStorageEntriesResult }, - "Storage.setSharedStorageEntry": { params: SetSharedStorageEntryParams, result: SetSharedStorageEntryResult }, - "Storage.deleteSharedStorageEntry": { params: DeleteSharedStorageEntryParams, result: DeleteSharedStorageEntryResult }, - "Storage.clearSharedStorageEntries": { params: ClearSharedStorageEntriesParams, result: ClearSharedStorageEntriesResult }, - "Storage.resetSharedStorageBudget": { params: ResetSharedStorageBudgetParams, result: ResetSharedStorageBudgetResult }, - "Storage.setSharedStorageTracking": { params: SetSharedStorageTrackingParams, result: SetSharedStorageTrackingResult }, - "Storage.setStorageBucketTracking": { params: SetStorageBucketTrackingParams, result: SetStorageBucketTrackingResult }, - "Storage.deleteStorageBucket": { params: DeleteStorageBucketParams, result: DeleteStorageBucketResult }, - "Storage.runBounceTrackingMitigations": { params: RunBounceTrackingMitigationsParams, result: RunBounceTrackingMitigationsResult }, - "Storage.getRelatedWebsiteSets": { params: GetRelatedWebsiteSetsParams, result: GetRelatedWebsiteSetsResult }, - "Storage.setProtectedAudienceKAnonymity": { params: SetProtectedAudienceKAnonymityParams, result: SetProtectedAudienceKAnonymityResult }, + "Storage.getStorageKeyForFrame": GetStorageKeyForFrameCommand, + "Storage.getStorageKey": GetStorageKeyCommand, + "Storage.clearDataForOrigin": ClearDataForOriginCommand, + "Storage.clearDataForStorageKey": ClearDataForStorageKeyCommand, + "Storage.getCookies": GetCookiesCommand, + "Storage.setCookies": SetCookiesCommand, + "Storage.clearCookies": ClearCookiesCommand, + "Storage.getUsageAndQuota": GetUsageAndQuotaCommand, + "Storage.overrideQuotaForOrigin": OverrideQuotaForOriginCommand, + "Storage.trackCacheStorageForOrigin": TrackCacheStorageForOriginCommand, + "Storage.trackCacheStorageForStorageKey": TrackCacheStorageForStorageKeyCommand, + "Storage.trackIndexedDBForOrigin": TrackIndexedDBForOriginCommand, + "Storage.trackIndexedDBForStorageKey": TrackIndexedDBForStorageKeyCommand, + "Storage.untrackCacheStorageForOrigin": UntrackCacheStorageForOriginCommand, + "Storage.untrackCacheStorageForStorageKey": UntrackCacheStorageForStorageKeyCommand, + "Storage.untrackIndexedDBForOrigin": UntrackIndexedDBForOriginCommand, + "Storage.untrackIndexedDBForStorageKey": UntrackIndexedDBForStorageKeyCommand, + "Storage.getTrustTokens": GetTrustTokensCommand, + "Storage.clearTrustTokens": ClearTrustTokensCommand, + "Storage.getInterestGroupDetails": GetInterestGroupDetailsCommand, + "Storage.setInterestGroupTracking": SetInterestGroupTrackingCommand, + "Storage.setInterestGroupAuctionTracking": SetInterestGroupAuctionTrackingCommand, + "Storage.getSharedStorageMetadata": GetSharedStorageMetadataCommand, + "Storage.getSharedStorageEntries": GetSharedStorageEntriesCommand, + "Storage.setSharedStorageEntry": SetSharedStorageEntryCommand, + "Storage.deleteSharedStorageEntry": DeleteSharedStorageEntryCommand, + "Storage.clearSharedStorageEntries": ClearSharedStorageEntriesCommand, + "Storage.resetSharedStorageBudget": ResetSharedStorageBudgetCommand, + "Storage.setSharedStorageTracking": SetSharedStorageTrackingCommand, + "Storage.setStorageBucketTracking": SetStorageBucketTrackingCommand, + "Storage.deleteStorageBucket": DeleteStorageBucketCommand, + "Storage.runBounceTrackingMitigations": RunBounceTrackingMitigationsCommand, + "Storage.getRelatedWebsiteSets": GetRelatedWebsiteSetsCommand, + "Storage.setProtectedAudienceKAnonymity": SetProtectedAudienceKAnonymityCommand, } as const; export const events = { "Storage.cacheStorageContentUpdated": CacheStorageContentUpdatedEvent, diff --git a/js/src/types/generated/zod/SystemInfo.ts b/js/src/types/generated/zod/SystemInfo.ts index d66b96ad..b3d7883d 100644 --- a/js/src/types/generated/zod/SystemInfo.ts +++ b/js/src/types/generated/zod/SystemInfo.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const GPUDevice = withCdpMeta(z.object({ "vendorId": z.number(), "deviceId": z.number(), "subSysId": z.number().optional(), "revision": z.number().optional(), "vendorString": z.string(), "deviceString": z.string(), "driverVendor": z.string(), "driverVersion": z.string() }).passthrough(), "SystemInfo.GPUDevice", "type"); export const Size = withCdpMeta(z.object({ "width": z.number().int(), "height": z.number().int() }).passthrough(), "SystemInfo.Size", "type"); @@ -13,10 +13,13 @@ export const GPUInfo = withCdpMeta(z.object({ "devices": z.array(z.lazy(() => GP export const ProcessInfo = withCdpMeta(z.object({ "type": z.string(), "id": z.number().int(), "cpuTime": z.number() }).passthrough(), "SystemInfo.ProcessInfo", "type"); export const GetInfoParams = withCdpMeta(z.object({ }).passthrough(), "SystemInfo.getInfo.params", "commandParams", { method: "SystemInfo.getInfo" }); export const GetInfoResult = withCdpMeta(z.object({ "gpu": z.lazy(() => GPUInfo), "modelName": z.string(), "modelVersion": z.string(), "commandLine": z.string() }).passthrough(), "SystemInfo.getInfo.result", "commandResult", { method: "SystemInfo.getInfo" }); +export const GetInfoCommand = withCdpCommand("SystemInfo.getInfo", GetInfoParams, GetInfoResult); export const GetFeatureStateParams = withCdpMeta(z.object({ "featureState": z.string() }).passthrough(), "SystemInfo.getFeatureState.params", "commandParams", { method: "SystemInfo.getFeatureState" }); export const GetFeatureStateResult = withCdpMeta(z.object({ "featureEnabled": z.boolean() }).passthrough(), "SystemInfo.getFeatureState.result", "commandResult", { method: "SystemInfo.getFeatureState" }); +export const GetFeatureStateCommand = withCdpCommand("SystemInfo.getFeatureState", GetFeatureStateParams, GetFeatureStateResult); export const GetProcessInfoParams = withCdpMeta(z.object({ }).passthrough(), "SystemInfo.getProcessInfo.params", "commandParams", { method: "SystemInfo.getProcessInfo" }); export const GetProcessInfoResult = withCdpMeta(z.object({ "processInfo": z.array(z.lazy(() => ProcessInfo)) }).passthrough(), "SystemInfo.getProcessInfo.result", "commandResult", { method: "SystemInfo.getProcessInfo" }); +export const GetProcessInfoCommand = withCdpCommand("SystemInfo.getProcessInfo", GetProcessInfoParams, GetProcessInfoResult); export const zod = { GPUDevice: GPUDevice, @@ -35,9 +38,9 @@ export const zod = { GetProcessInfoResult: GetProcessInfoResult, } as const; export const commands = { - "SystemInfo.getInfo": { params: GetInfoParams, result: GetInfoResult }, - "SystemInfo.getFeatureState": { params: GetFeatureStateParams, result: GetFeatureStateResult }, - "SystemInfo.getProcessInfo": { params: GetProcessInfoParams, result: GetProcessInfoResult }, + "SystemInfo.getInfo": GetInfoCommand, + "SystemInfo.getFeatureState": GetFeatureStateCommand, + "SystemInfo.getProcessInfo": GetProcessInfoCommand, } as const; export const events = { } as const; diff --git a/js/src/types/generated/zod/Target.ts b/js/src/types/generated/zod/Target.ts index baeb0c2d..45232c19 100644 --- a/js/src/types/generated/zod/Target.ts +++ b/js/src/types/generated/zod/Target.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as Browser from "./Browser.js"; import * as Page from "./Page.js"; @@ -14,42 +14,61 @@ export const RemoteLocation = withCdpMeta(z.object({ "host": z.string(), "port": export const WindowState = withCdpMeta(z.enum(["normal", "minimized", "maximized", "fullscreen"]), "Target.WindowState", "type"); export const ActivateTargetParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID) }).passthrough(), "Target.activateTarget.params", "commandParams", { method: "Target.activateTarget" }); export const ActivateTargetResult = withCdpMeta(z.object({ }).passthrough(), "Target.activateTarget.result", "commandResult", { method: "Target.activateTarget" }); +export const ActivateTargetCommand = withCdpCommand("Target.activateTarget", ActivateTargetParams, ActivateTargetResult); export const AttachToTargetParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID), "flatten": z.boolean().optional() }).passthrough(), "Target.attachToTarget.params", "commandParams", { method: "Target.attachToTarget" }); export const AttachToTargetResult = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID) }).passthrough(), "Target.attachToTarget.result", "commandResult", { method: "Target.attachToTarget" }); +export const AttachToTargetCommand = withCdpCommand("Target.attachToTarget", AttachToTargetParams, AttachToTargetResult); export const AttachToBrowserTargetParams = withCdpMeta(z.object({ }).passthrough(), "Target.attachToBrowserTarget.params", "commandParams", { method: "Target.attachToBrowserTarget" }); export const AttachToBrowserTargetResult = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID) }).passthrough(), "Target.attachToBrowserTarget.result", "commandResult", { method: "Target.attachToBrowserTarget" }); +export const AttachToBrowserTargetCommand = withCdpCommand("Target.attachToBrowserTarget", AttachToBrowserTargetParams, AttachToBrowserTargetResult); export const CloseTargetParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID) }).passthrough(), "Target.closeTarget.params", "commandParams", { method: "Target.closeTarget" }); export const CloseTargetResult = withCdpMeta(z.object({ "success": z.boolean() }).passthrough(), "Target.closeTarget.result", "commandResult", { method: "Target.closeTarget" }); +export const CloseTargetCommand = withCdpCommand("Target.closeTarget", CloseTargetParams, CloseTargetResult); export const ExposeDevToolsProtocolParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID), "bindingName": z.string().optional(), "inheritPermissions": z.boolean().optional() }).passthrough(), "Target.exposeDevToolsProtocol.params", "commandParams", { method: "Target.exposeDevToolsProtocol" }); export const ExposeDevToolsProtocolResult = withCdpMeta(z.object({ }).passthrough(), "Target.exposeDevToolsProtocol.result", "commandResult", { method: "Target.exposeDevToolsProtocol" }); +export const ExposeDevToolsProtocolCommand = withCdpCommand("Target.exposeDevToolsProtocol", ExposeDevToolsProtocolParams, ExposeDevToolsProtocolResult); export const CreateBrowserContextParams = withCdpMeta(z.object({ "disposeOnDetach": z.boolean().optional(), "proxyServer": z.string().optional(), "proxyBypassList": z.string().optional(), "originsWithUniversalNetworkAccess": z.array(z.string()).optional() }).passthrough(), "Target.createBrowserContext.params", "commandParams", { method: "Target.createBrowserContext" }); export const CreateBrowserContextResult = withCdpMeta(z.object({ "browserContextId": z.lazy(() => Browser.BrowserContextID) }).passthrough(), "Target.createBrowserContext.result", "commandResult", { method: "Target.createBrowserContext" }); +export const CreateBrowserContextCommand = withCdpCommand("Target.createBrowserContext", CreateBrowserContextParams, CreateBrowserContextResult); export const GetBrowserContextsParams = withCdpMeta(z.object({ }).passthrough(), "Target.getBrowserContexts.params", "commandParams", { method: "Target.getBrowserContexts" }); export const GetBrowserContextsResult = withCdpMeta(z.object({ "browserContextIds": z.array(z.lazy(() => Browser.BrowserContextID)), "defaultBrowserContextId": z.lazy(() => Browser.BrowserContextID).optional() }).passthrough(), "Target.getBrowserContexts.result", "commandResult", { method: "Target.getBrowserContexts" }); +export const GetBrowserContextsCommand = withCdpCommand("Target.getBrowserContexts", GetBrowserContextsParams, GetBrowserContextsResult); export const CreateTargetParams = withCdpMeta(z.object({ "url": z.string(), "left": z.number().int().optional(), "top": z.number().int().optional(), "width": z.number().int().optional(), "height": z.number().int().optional(), "windowState": z.lazy(() => WindowState).optional(), "browserContextId": z.lazy(() => Browser.BrowserContextID).optional(), "enableBeginFrameControl": z.boolean().optional(), "newWindow": z.boolean().optional(), "background": z.boolean().optional(), "forTab": z.boolean().optional(), "hidden": z.boolean().optional(), "focus": z.boolean().optional() }).passthrough(), "Target.createTarget.params", "commandParams", { method: "Target.createTarget" }); export const CreateTargetResult = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID) }).passthrough(), "Target.createTarget.result", "commandResult", { method: "Target.createTarget" }); +export const CreateTargetCommand = withCdpCommand("Target.createTarget", CreateTargetParams, CreateTargetResult); export const DetachFromTargetParams = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID).optional(), "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.detachFromTarget.params", "commandParams", { method: "Target.detachFromTarget" }); export const DetachFromTargetResult = withCdpMeta(z.object({ }).passthrough(), "Target.detachFromTarget.result", "commandResult", { method: "Target.detachFromTarget" }); +export const DetachFromTargetCommand = withCdpCommand("Target.detachFromTarget", DetachFromTargetParams, DetachFromTargetResult); export const DisposeBrowserContextParams = withCdpMeta(z.object({ "browserContextId": z.lazy(() => Browser.BrowserContextID) }).passthrough(), "Target.disposeBrowserContext.params", "commandParams", { method: "Target.disposeBrowserContext" }); export const DisposeBrowserContextResult = withCdpMeta(z.object({ }).passthrough(), "Target.disposeBrowserContext.result", "commandResult", { method: "Target.disposeBrowserContext" }); +export const DisposeBrowserContextCommand = withCdpCommand("Target.disposeBrowserContext", DisposeBrowserContextParams, DisposeBrowserContextResult); export const GetTargetInfoParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.getTargetInfo.params", "commandParams", { method: "Target.getTargetInfo" }); export const GetTargetInfoResult = withCdpMeta(z.object({ "targetInfo": z.lazy(() => TargetInfo) }).passthrough(), "Target.getTargetInfo.result", "commandResult", { method: "Target.getTargetInfo" }); +export const GetTargetInfoCommand = withCdpCommand("Target.getTargetInfo", GetTargetInfoParams, GetTargetInfoResult); export const GetTargetsParams = withCdpMeta(z.object({ "filter": z.lazy(() => TargetFilter).optional() }).passthrough(), "Target.getTargets.params", "commandParams", { method: "Target.getTargets" }); export const GetTargetsResult = withCdpMeta(z.object({ "targetInfos": z.array(z.lazy(() => TargetInfo)) }).passthrough(), "Target.getTargets.result", "commandResult", { method: "Target.getTargets" }); +export const GetTargetsCommand = withCdpCommand("Target.getTargets", GetTargetsParams, GetTargetsResult); export const SendMessageToTargetParams = withCdpMeta(z.object({ "message": z.string(), "sessionId": z.lazy(() => SessionID).optional(), "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.sendMessageToTarget.params", "commandParams", { method: "Target.sendMessageToTarget" }); export const SendMessageToTargetResult = withCdpMeta(z.object({ }).passthrough(), "Target.sendMessageToTarget.result", "commandResult", { method: "Target.sendMessageToTarget" }); +export const SendMessageToTargetCommand = withCdpCommand("Target.sendMessageToTarget", SendMessageToTargetParams, SendMessageToTargetResult); export const SetAutoAttachParams = withCdpMeta(z.object({ "autoAttach": z.boolean(), "waitForDebuggerOnStart": z.boolean(), "flatten": z.boolean().optional(), "filter": z.lazy(() => TargetFilter).optional() }).passthrough(), "Target.setAutoAttach.params", "commandParams", { method: "Target.setAutoAttach" }); export const SetAutoAttachResult = withCdpMeta(z.object({ }).passthrough(), "Target.setAutoAttach.result", "commandResult", { method: "Target.setAutoAttach" }); +export const SetAutoAttachCommand = withCdpCommand("Target.setAutoAttach", SetAutoAttachParams, SetAutoAttachResult); export const AutoAttachRelatedParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID), "waitForDebuggerOnStart": z.boolean(), "filter": z.lazy(() => TargetFilter).optional() }).passthrough(), "Target.autoAttachRelated.params", "commandParams", { method: "Target.autoAttachRelated" }); export const AutoAttachRelatedResult = withCdpMeta(z.object({ }).passthrough(), "Target.autoAttachRelated.result", "commandResult", { method: "Target.autoAttachRelated" }); +export const AutoAttachRelatedCommand = withCdpCommand("Target.autoAttachRelated", AutoAttachRelatedParams, AutoAttachRelatedResult); export const SetDiscoverTargetsParams = withCdpMeta(z.object({ "discover": z.boolean(), "filter": z.lazy(() => TargetFilter).optional() }).passthrough(), "Target.setDiscoverTargets.params", "commandParams", { method: "Target.setDiscoverTargets" }); export const SetDiscoverTargetsResult = withCdpMeta(z.object({ }).passthrough(), "Target.setDiscoverTargets.result", "commandResult", { method: "Target.setDiscoverTargets" }); +export const SetDiscoverTargetsCommand = withCdpCommand("Target.setDiscoverTargets", SetDiscoverTargetsParams, SetDiscoverTargetsResult); export const SetRemoteLocationsParams = withCdpMeta(z.object({ "locations": z.array(z.lazy(() => RemoteLocation)) }).passthrough(), "Target.setRemoteLocations.params", "commandParams", { method: "Target.setRemoteLocations" }); export const SetRemoteLocationsResult = withCdpMeta(z.object({ }).passthrough(), "Target.setRemoteLocations.result", "commandResult", { method: "Target.setRemoteLocations" }); +export const SetRemoteLocationsCommand = withCdpCommand("Target.setRemoteLocations", SetRemoteLocationsParams, SetRemoteLocationsResult); export const GetDevToolsTargetParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID) }).passthrough(), "Target.getDevToolsTarget.params", "commandParams", { method: "Target.getDevToolsTarget" }); export const GetDevToolsTargetResult = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.getDevToolsTarget.result", "commandResult", { method: "Target.getDevToolsTarget" }); +export const GetDevToolsTargetCommand = withCdpCommand("Target.getDevToolsTarget", GetDevToolsTargetParams, GetDevToolsTargetResult); export const OpenDevToolsParams = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID), "panelId": z.string().optional() }).passthrough(), "Target.openDevTools.params", "commandParams", { method: "Target.openDevTools" }); export const OpenDevToolsResult = withCdpMeta(z.object({ "targetId": z.lazy(() => TargetID) }).passthrough(), "Target.openDevTools.result", "commandResult", { method: "Target.openDevTools" }); +export const OpenDevToolsCommand = withCdpCommand("Target.openDevTools", OpenDevToolsParams, OpenDevToolsResult); export const AttachedToTargetEvent = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID), "targetInfo": z.lazy(() => TargetInfo), "waitingForDebugger": z.boolean() }).passthrough(), "Target.attachedToTarget", "event", { phase: "event" }); export const DetachedFromTargetEvent = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID), "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.detachedFromTarget", "event", { phase: "event" }); export const ReceivedMessageFromTargetEvent = withCdpMeta(z.object({ "sessionId": z.lazy(() => SessionID), "message": z.string(), "targetId": z.lazy(() => TargetID).optional() }).passthrough(), "Target.receivedMessageFromTarget", "event", { phase: "event" }); @@ -113,25 +132,25 @@ export const zod = { TargetInfoChangedEvent: TargetInfoChangedEvent, } as const; export const commands = { - "Target.activateTarget": { params: ActivateTargetParams, result: ActivateTargetResult }, - "Target.attachToTarget": { params: AttachToTargetParams, result: AttachToTargetResult }, - "Target.attachToBrowserTarget": { params: AttachToBrowserTargetParams, result: AttachToBrowserTargetResult }, - "Target.closeTarget": { params: CloseTargetParams, result: CloseTargetResult }, - "Target.exposeDevToolsProtocol": { params: ExposeDevToolsProtocolParams, result: ExposeDevToolsProtocolResult }, - "Target.createBrowserContext": { params: CreateBrowserContextParams, result: CreateBrowserContextResult }, - "Target.getBrowserContexts": { params: GetBrowserContextsParams, result: GetBrowserContextsResult }, - "Target.createTarget": { params: CreateTargetParams, result: CreateTargetResult }, - "Target.detachFromTarget": { params: DetachFromTargetParams, result: DetachFromTargetResult }, - "Target.disposeBrowserContext": { params: DisposeBrowserContextParams, result: DisposeBrowserContextResult }, - "Target.getTargetInfo": { params: GetTargetInfoParams, result: GetTargetInfoResult }, - "Target.getTargets": { params: GetTargetsParams, result: GetTargetsResult }, - "Target.sendMessageToTarget": { params: SendMessageToTargetParams, result: SendMessageToTargetResult }, - "Target.setAutoAttach": { params: SetAutoAttachParams, result: SetAutoAttachResult }, - "Target.autoAttachRelated": { params: AutoAttachRelatedParams, result: AutoAttachRelatedResult }, - "Target.setDiscoverTargets": { params: SetDiscoverTargetsParams, result: SetDiscoverTargetsResult }, - "Target.setRemoteLocations": { params: SetRemoteLocationsParams, result: SetRemoteLocationsResult }, - "Target.getDevToolsTarget": { params: GetDevToolsTargetParams, result: GetDevToolsTargetResult }, - "Target.openDevTools": { params: OpenDevToolsParams, result: OpenDevToolsResult }, + "Target.activateTarget": ActivateTargetCommand, + "Target.attachToTarget": AttachToTargetCommand, + "Target.attachToBrowserTarget": AttachToBrowserTargetCommand, + "Target.closeTarget": CloseTargetCommand, + "Target.exposeDevToolsProtocol": ExposeDevToolsProtocolCommand, + "Target.createBrowserContext": CreateBrowserContextCommand, + "Target.getBrowserContexts": GetBrowserContextsCommand, + "Target.createTarget": CreateTargetCommand, + "Target.detachFromTarget": DetachFromTargetCommand, + "Target.disposeBrowserContext": DisposeBrowserContextCommand, + "Target.getTargetInfo": GetTargetInfoCommand, + "Target.getTargets": GetTargetsCommand, + "Target.sendMessageToTarget": SendMessageToTargetCommand, + "Target.setAutoAttach": SetAutoAttachCommand, + "Target.autoAttachRelated": AutoAttachRelatedCommand, + "Target.setDiscoverTargets": SetDiscoverTargetsCommand, + "Target.setRemoteLocations": SetRemoteLocationsCommand, + "Target.getDevToolsTarget": GetDevToolsTargetCommand, + "Target.openDevTools": OpenDevToolsCommand, } as const; export const events = { "Target.attachedToTarget": AttachedToTargetEvent, diff --git a/js/src/types/generated/zod/Tethering.ts b/js/src/types/generated/zod/Tethering.ts index 4ae9eafc..a18e0fd5 100644 --- a/js/src/types/generated/zod/Tethering.ts +++ b/js/src/types/generated/zod/Tethering.ts @@ -1,12 +1,14 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const BindParams = withCdpMeta(z.object({ "port": z.number().int() }).passthrough(), "Tethering.bind.params", "commandParams", { method: "Tethering.bind" }); export const BindResult = withCdpMeta(z.object({ }).passthrough(), "Tethering.bind.result", "commandResult", { method: "Tethering.bind" }); +export const BindCommand = withCdpCommand("Tethering.bind", BindParams, BindResult); export const UnbindParams = withCdpMeta(z.object({ "port": z.number().int() }).passthrough(), "Tethering.unbind.params", "commandParams", { method: "Tethering.unbind" }); export const UnbindResult = withCdpMeta(z.object({ }).passthrough(), "Tethering.unbind.result", "commandResult", { method: "Tethering.unbind" }); +export const UnbindCommand = withCdpCommand("Tethering.unbind", UnbindParams, UnbindResult); export const AcceptedEvent = withCdpMeta(z.object({ "port": z.number().int(), "connectionId": z.string() }).passthrough(), "Tethering.accepted", "event", { phase: "event" }); export const zod = { @@ -17,8 +19,8 @@ export const zod = { AcceptedEvent: AcceptedEvent, } as const; export const commands = { - "Tethering.bind": { params: BindParams, result: BindResult }, - "Tethering.unbind": { params: UnbindParams, result: UnbindResult }, + "Tethering.bind": BindCommand, + "Tethering.unbind": UnbindCommand, } as const; export const events = { "Tethering.accepted": AcceptedEvent, diff --git a/js/src/types/generated/zod/Tracing.ts b/js/src/types/generated/zod/Tracing.ts index ec4c11c7..8634c260 100644 --- a/js/src/types/generated/zod/Tracing.ts +++ b/js/src/types/generated/zod/Tracing.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as IO from "./IO.js"; export const MemoryDumpConfig = withCdpMeta(z.record(z.string(), z.unknown()), "Tracing.MemoryDumpConfig", "type"); @@ -12,16 +12,22 @@ export const MemoryDumpLevelOfDetail = withCdpMeta(z.enum(["background", "light" export const TracingBackend = withCdpMeta(z.enum(["auto", "chrome", "system"]), "Tracing.TracingBackend", "type"); export const EndParams = withCdpMeta(z.object({ }).passthrough(), "Tracing.end.params", "commandParams", { method: "Tracing.end" }); export const EndResult = withCdpMeta(z.object({ }).passthrough(), "Tracing.end.result", "commandResult", { method: "Tracing.end" }); +export const EndCommand = withCdpCommand("Tracing.end", EndParams, EndResult); export const GetCategoriesParams = withCdpMeta(z.object({ }).passthrough(), "Tracing.getCategories.params", "commandParams", { method: "Tracing.getCategories" }); export const GetCategoriesResult = withCdpMeta(z.object({ "categories": z.array(z.string()) }).passthrough(), "Tracing.getCategories.result", "commandResult", { method: "Tracing.getCategories" }); +export const GetCategoriesCommand = withCdpCommand("Tracing.getCategories", GetCategoriesParams, GetCategoriesResult); export const GetTrackEventDescriptorParams = withCdpMeta(z.object({ }).passthrough(), "Tracing.getTrackEventDescriptor.params", "commandParams", { method: "Tracing.getTrackEventDescriptor" }); export const GetTrackEventDescriptorResult = withCdpMeta(z.object({ "descriptor": z.string() }).passthrough(), "Tracing.getTrackEventDescriptor.result", "commandResult", { method: "Tracing.getTrackEventDescriptor" }); +export const GetTrackEventDescriptorCommand = withCdpCommand("Tracing.getTrackEventDescriptor", GetTrackEventDescriptorParams, GetTrackEventDescriptorResult); export const RecordClockSyncMarkerParams = withCdpMeta(z.object({ "syncId": z.string() }).passthrough(), "Tracing.recordClockSyncMarker.params", "commandParams", { method: "Tracing.recordClockSyncMarker" }); export const RecordClockSyncMarkerResult = withCdpMeta(z.object({ }).passthrough(), "Tracing.recordClockSyncMarker.result", "commandResult", { method: "Tracing.recordClockSyncMarker" }); +export const RecordClockSyncMarkerCommand = withCdpCommand("Tracing.recordClockSyncMarker", RecordClockSyncMarkerParams, RecordClockSyncMarkerResult); export const RequestMemoryDumpParams = withCdpMeta(z.object({ "deterministic": z.boolean().optional(), "levelOfDetail": z.lazy(() => MemoryDumpLevelOfDetail).optional() }).passthrough(), "Tracing.requestMemoryDump.params", "commandParams", { method: "Tracing.requestMemoryDump" }); export const RequestMemoryDumpResult = withCdpMeta(z.object({ "dumpGuid": z.string(), "success": z.boolean() }).passthrough(), "Tracing.requestMemoryDump.result", "commandResult", { method: "Tracing.requestMemoryDump" }); +export const RequestMemoryDumpCommand = withCdpCommand("Tracing.requestMemoryDump", RequestMemoryDumpParams, RequestMemoryDumpResult); export const StartParams = withCdpMeta(z.object({ "categories": z.string().optional(), "options": z.string().optional(), "bufferUsageReportingInterval": z.number().optional(), "transferMode": z.enum(["ReportEvents", "ReturnAsStream"]).optional(), "streamFormat": z.lazy(() => StreamFormat).optional(), "streamCompression": z.lazy(() => StreamCompression).optional(), "traceConfig": z.lazy(() => TraceConfig).optional(), "perfettoConfig": z.string().optional(), "tracingBackend": z.lazy(() => TracingBackend).optional() }).passthrough(), "Tracing.start.params", "commandParams", { method: "Tracing.start" }); export const StartResult = withCdpMeta(z.object({ }).passthrough(), "Tracing.start.result", "commandResult", { method: "Tracing.start" }); +export const StartCommand = withCdpCommand("Tracing.start", StartParams, StartResult); export const BufferUsageEvent = withCdpMeta(z.object({ "percentFull": z.number().optional(), "eventCount": z.number().optional(), "value": z.number().optional() }).passthrough(), "Tracing.bufferUsage", "event", { phase: "event" }); export const DataCollectedEvent = withCdpMeta(z.object({ "value": z.array(z.record(z.string(), z.unknown())) }).passthrough(), "Tracing.dataCollected", "event", { phase: "event" }); export const TracingCompleteEvent = withCdpMeta(z.object({ "dataLossOccurred": z.boolean(), "stream": z.lazy(() => IO.StreamHandle).optional(), "traceFormat": z.lazy(() => StreamFormat).optional(), "streamCompression": z.lazy(() => StreamCompression).optional() }).passthrough(), "Tracing.tracingComplete", "event", { phase: "event" }); @@ -50,12 +56,12 @@ export const zod = { TracingCompleteEvent: TracingCompleteEvent, } as const; export const commands = { - "Tracing.end": { params: EndParams, result: EndResult }, - "Tracing.getCategories": { params: GetCategoriesParams, result: GetCategoriesResult }, - "Tracing.getTrackEventDescriptor": { params: GetTrackEventDescriptorParams, result: GetTrackEventDescriptorResult }, - "Tracing.recordClockSyncMarker": { params: RecordClockSyncMarkerParams, result: RecordClockSyncMarkerResult }, - "Tracing.requestMemoryDump": { params: RequestMemoryDumpParams, result: RequestMemoryDumpResult }, - "Tracing.start": { params: StartParams, result: StartResult }, + "Tracing.end": EndCommand, + "Tracing.getCategories": GetCategoriesCommand, + "Tracing.getTrackEventDescriptor": GetTrackEventDescriptorCommand, + "Tracing.recordClockSyncMarker": RecordClockSyncMarkerCommand, + "Tracing.requestMemoryDump": RequestMemoryDumpCommand, + "Tracing.start": StartCommand, } as const; export const events = { "Tracing.bufferUsage": BufferUsageEvent, diff --git a/js/src/types/generated/zod/WebAudio.ts b/js/src/types/generated/zod/WebAudio.ts index 31693479..f67e3bd4 100644 --- a/js/src/types/generated/zod/WebAudio.ts +++ b/js/src/types/generated/zod/WebAudio.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const GraphObjectId = withCdpMeta(z.string(), "WebAudio.GraphObjectId", "type"); export const ContextType = withCdpMeta(z.enum(["realtime", "offline"]), "WebAudio.ContextType", "type"); @@ -18,10 +18,13 @@ export const AudioNode = withCdpMeta(z.object({ "nodeId": z.lazy(() => GraphObje export const AudioParam = withCdpMeta(z.object({ "paramId": z.lazy(() => GraphObjectId), "nodeId": z.lazy(() => GraphObjectId), "contextId": z.lazy(() => GraphObjectId), "paramType": z.lazy(() => ParamType), "rate": z.lazy(() => AutomationRate), "defaultValue": z.number(), "minValue": z.number(), "maxValue": z.number() }).passthrough(), "WebAudio.AudioParam", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "WebAudio.enable.params", "commandParams", { method: "WebAudio.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "WebAudio.enable.result", "commandResult", { method: "WebAudio.enable" }); +export const EnableCommand = withCdpCommand("WebAudio.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "WebAudio.disable.params", "commandParams", { method: "WebAudio.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "WebAudio.disable.result", "commandResult", { method: "WebAudio.disable" }); +export const DisableCommand = withCdpCommand("WebAudio.disable", DisableParams, DisableResult); export const GetRealtimeDataParams = withCdpMeta(z.object({ "contextId": z.lazy(() => GraphObjectId) }).passthrough(), "WebAudio.getRealtimeData.params", "commandParams", { method: "WebAudio.getRealtimeData" }); export const GetRealtimeDataResult = withCdpMeta(z.object({ "realtimeData": z.lazy(() => ContextRealtimeData) }).passthrough(), "WebAudio.getRealtimeData.result", "commandResult", { method: "WebAudio.getRealtimeData" }); +export const GetRealtimeDataCommand = withCdpCommand("WebAudio.getRealtimeData", GetRealtimeDataParams, GetRealtimeDataResult); export const ContextCreatedEvent = withCdpMeta(z.object({ "context": z.lazy(() => BaseAudioContext) }).passthrough(), "WebAudio.contextCreated", "event", { phase: "event" }); export const ContextWillBeDestroyedEvent = withCdpMeta(z.object({ "contextId": z.lazy(() => GraphObjectId) }).passthrough(), "WebAudio.contextWillBeDestroyed", "event", { phase: "event" }); export const ContextChangedEvent = withCdpMeta(z.object({ "context": z.lazy(() => BaseAudioContext) }).passthrough(), "WebAudio.contextChanged", "event", { phase: "event" }); @@ -71,9 +74,9 @@ export const zod = { NodeParamDisconnectedEvent: NodeParamDisconnectedEvent, } as const; export const commands = { - "WebAudio.enable": { params: EnableParams, result: EnableResult }, - "WebAudio.disable": { params: DisableParams, result: DisableResult }, - "WebAudio.getRealtimeData": { params: GetRealtimeDataParams, result: GetRealtimeDataResult }, + "WebAudio.enable": EnableCommand, + "WebAudio.disable": DisableCommand, + "WebAudio.getRealtimeData": GetRealtimeDataCommand, } as const; export const events = { "WebAudio.contextCreated": ContextCreatedEvent, diff --git a/js/src/types/generated/zod/WebAuthn.ts b/js/src/types/generated/zod/WebAuthn.ts index 76755a96..dc4fb32d 100644 --- a/js/src/types/generated/zod/WebAuthn.ts +++ b/js/src/types/generated/zod/WebAuthn.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; export const AuthenticatorId = withCdpMeta(z.string(), "WebAuthn.AuthenticatorId", "type"); export const AuthenticatorProtocol = withCdpMeta(z.enum(["u2f", "ctap2"]), "WebAuthn.AuthenticatorProtocol", "type"); @@ -11,30 +11,43 @@ export const VirtualAuthenticatorOptions = withCdpMeta(z.object({ "protocol": z. export const Credential = withCdpMeta(z.object({ "credentialId": z.string(), "isResidentCredential": z.boolean(), "rpId": z.string().optional(), "privateKey": z.string(), "userHandle": z.string().optional(), "signCount": z.number().int(), "largeBlob": z.string().optional(), "backupEligibility": z.boolean().optional(), "backupState": z.boolean().optional(), "userName": z.string().optional(), "userDisplayName": z.string().optional() }).passthrough(), "WebAuthn.Credential", "type"); export const EnableParams = withCdpMeta(z.object({ "enableUI": z.boolean().optional() }).passthrough(), "WebAuthn.enable.params", "commandParams", { method: "WebAuthn.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.enable.result", "commandResult", { method: "WebAuthn.enable" }); +export const EnableCommand = withCdpCommand("WebAuthn.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.disable.params", "commandParams", { method: "WebAuthn.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.disable.result", "commandResult", { method: "WebAuthn.disable" }); +export const DisableCommand = withCdpCommand("WebAuthn.disable", DisableParams, DisableResult); export const AddVirtualAuthenticatorParams = withCdpMeta(z.object({ "options": z.lazy(() => VirtualAuthenticatorOptions) }).passthrough(), "WebAuthn.addVirtualAuthenticator.params", "commandParams", { method: "WebAuthn.addVirtualAuthenticator" }); export const AddVirtualAuthenticatorResult = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId) }).passthrough(), "WebAuthn.addVirtualAuthenticator.result", "commandResult", { method: "WebAuthn.addVirtualAuthenticator" }); +export const AddVirtualAuthenticatorCommand = withCdpCommand("WebAuthn.addVirtualAuthenticator", AddVirtualAuthenticatorParams, AddVirtualAuthenticatorResult); export const SetResponseOverrideBitsParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "isBogusSignature": z.boolean().optional(), "isBadUV": z.boolean().optional(), "isBadUP": z.boolean().optional() }).passthrough(), "WebAuthn.setResponseOverrideBits.params", "commandParams", { method: "WebAuthn.setResponseOverrideBits" }); export const SetResponseOverrideBitsResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.setResponseOverrideBits.result", "commandResult", { method: "WebAuthn.setResponseOverrideBits" }); +export const SetResponseOverrideBitsCommand = withCdpCommand("WebAuthn.setResponseOverrideBits", SetResponseOverrideBitsParams, SetResponseOverrideBitsResult); export const RemoveVirtualAuthenticatorParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId) }).passthrough(), "WebAuthn.removeVirtualAuthenticator.params", "commandParams", { method: "WebAuthn.removeVirtualAuthenticator" }); export const RemoveVirtualAuthenticatorResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.removeVirtualAuthenticator.result", "commandResult", { method: "WebAuthn.removeVirtualAuthenticator" }); +export const RemoveVirtualAuthenticatorCommand = withCdpCommand("WebAuthn.removeVirtualAuthenticator", RemoveVirtualAuthenticatorParams, RemoveVirtualAuthenticatorResult); export const AddCredentialParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credential": z.lazy(() => Credential) }).passthrough(), "WebAuthn.addCredential.params", "commandParams", { method: "WebAuthn.addCredential" }); export const AddCredentialResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.addCredential.result", "commandResult", { method: "WebAuthn.addCredential" }); +export const AddCredentialCommand = withCdpCommand("WebAuthn.addCredential", AddCredentialParams, AddCredentialResult); export const GetCredentialParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credentialId": z.string() }).passthrough(), "WebAuthn.getCredential.params", "commandParams", { method: "WebAuthn.getCredential" }); export const GetCredentialResult = withCdpMeta(z.object({ "credential": z.lazy(() => Credential) }).passthrough(), "WebAuthn.getCredential.result", "commandResult", { method: "WebAuthn.getCredential" }); +export const GetCredentialCommand = withCdpCommand("WebAuthn.getCredential", GetCredentialParams, GetCredentialResult); export const GetCredentialsParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId) }).passthrough(), "WebAuthn.getCredentials.params", "commandParams", { method: "WebAuthn.getCredentials" }); export const GetCredentialsResult = withCdpMeta(z.object({ "credentials": z.array(z.lazy(() => Credential)) }).passthrough(), "WebAuthn.getCredentials.result", "commandResult", { method: "WebAuthn.getCredentials" }); +export const GetCredentialsCommand = withCdpCommand("WebAuthn.getCredentials", GetCredentialsParams, GetCredentialsResult); export const RemoveCredentialParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credentialId": z.string() }).passthrough(), "WebAuthn.removeCredential.params", "commandParams", { method: "WebAuthn.removeCredential" }); export const RemoveCredentialResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.removeCredential.result", "commandResult", { method: "WebAuthn.removeCredential" }); +export const RemoveCredentialCommand = withCdpCommand("WebAuthn.removeCredential", RemoveCredentialParams, RemoveCredentialResult); export const ClearCredentialsParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId) }).passthrough(), "WebAuthn.clearCredentials.params", "commandParams", { method: "WebAuthn.clearCredentials" }); export const ClearCredentialsResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.clearCredentials.result", "commandResult", { method: "WebAuthn.clearCredentials" }); +export const ClearCredentialsCommand = withCdpCommand("WebAuthn.clearCredentials", ClearCredentialsParams, ClearCredentialsResult); export const SetUserVerifiedParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "isUserVerified": z.boolean() }).passthrough(), "WebAuthn.setUserVerified.params", "commandParams", { method: "WebAuthn.setUserVerified" }); export const SetUserVerifiedResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.setUserVerified.result", "commandResult", { method: "WebAuthn.setUserVerified" }); +export const SetUserVerifiedCommand = withCdpCommand("WebAuthn.setUserVerified", SetUserVerifiedParams, SetUserVerifiedResult); export const SetAutomaticPresenceSimulationParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "enabled": z.boolean() }).passthrough(), "WebAuthn.setAutomaticPresenceSimulation.params", "commandParams", { method: "WebAuthn.setAutomaticPresenceSimulation" }); export const SetAutomaticPresenceSimulationResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.setAutomaticPresenceSimulation.result", "commandResult", { method: "WebAuthn.setAutomaticPresenceSimulation" }); +export const SetAutomaticPresenceSimulationCommand = withCdpCommand("WebAuthn.setAutomaticPresenceSimulation", SetAutomaticPresenceSimulationParams, SetAutomaticPresenceSimulationResult); export const SetCredentialPropertiesParams = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credentialId": z.string(), "backupEligibility": z.boolean().optional(), "backupState": z.boolean().optional() }).passthrough(), "WebAuthn.setCredentialProperties.params", "commandParams", { method: "WebAuthn.setCredentialProperties" }); export const SetCredentialPropertiesResult = withCdpMeta(z.object({ }).passthrough(), "WebAuthn.setCredentialProperties.result", "commandResult", { method: "WebAuthn.setCredentialProperties" }); +export const SetCredentialPropertiesCommand = withCdpCommand("WebAuthn.setCredentialProperties", SetCredentialPropertiesParams, SetCredentialPropertiesResult); export const CredentialAddedEvent = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credential": z.lazy(() => Credential) }).passthrough(), "WebAuthn.credentialAdded", "event", { phase: "event" }); export const CredentialDeletedEvent = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credentialId": z.string() }).passthrough(), "WebAuthn.credentialDeleted", "event", { phase: "event" }); export const CredentialUpdatedEvent = withCdpMeta(z.object({ "authenticatorId": z.lazy(() => AuthenticatorId), "credential": z.lazy(() => Credential) }).passthrough(), "WebAuthn.credentialUpdated", "event", { phase: "event" }); @@ -79,19 +92,19 @@ export const zod = { CredentialAssertedEvent: CredentialAssertedEvent, } as const; export const commands = { - "WebAuthn.enable": { params: EnableParams, result: EnableResult }, - "WebAuthn.disable": { params: DisableParams, result: DisableResult }, - "WebAuthn.addVirtualAuthenticator": { params: AddVirtualAuthenticatorParams, result: AddVirtualAuthenticatorResult }, - "WebAuthn.setResponseOverrideBits": { params: SetResponseOverrideBitsParams, result: SetResponseOverrideBitsResult }, - "WebAuthn.removeVirtualAuthenticator": { params: RemoveVirtualAuthenticatorParams, result: RemoveVirtualAuthenticatorResult }, - "WebAuthn.addCredential": { params: AddCredentialParams, result: AddCredentialResult }, - "WebAuthn.getCredential": { params: GetCredentialParams, result: GetCredentialResult }, - "WebAuthn.getCredentials": { params: GetCredentialsParams, result: GetCredentialsResult }, - "WebAuthn.removeCredential": { params: RemoveCredentialParams, result: RemoveCredentialResult }, - "WebAuthn.clearCredentials": { params: ClearCredentialsParams, result: ClearCredentialsResult }, - "WebAuthn.setUserVerified": { params: SetUserVerifiedParams, result: SetUserVerifiedResult }, - "WebAuthn.setAutomaticPresenceSimulation": { params: SetAutomaticPresenceSimulationParams, result: SetAutomaticPresenceSimulationResult }, - "WebAuthn.setCredentialProperties": { params: SetCredentialPropertiesParams, result: SetCredentialPropertiesResult }, + "WebAuthn.enable": EnableCommand, + "WebAuthn.disable": DisableCommand, + "WebAuthn.addVirtualAuthenticator": AddVirtualAuthenticatorCommand, + "WebAuthn.setResponseOverrideBits": SetResponseOverrideBitsCommand, + "WebAuthn.removeVirtualAuthenticator": RemoveVirtualAuthenticatorCommand, + "WebAuthn.addCredential": AddCredentialCommand, + "WebAuthn.getCredential": GetCredentialCommand, + "WebAuthn.getCredentials": GetCredentialsCommand, + "WebAuthn.removeCredential": RemoveCredentialCommand, + "WebAuthn.clearCredentials": ClearCredentialsCommand, + "WebAuthn.setUserVerified": SetUserVerifiedCommand, + "WebAuthn.setAutomaticPresenceSimulation": SetAutomaticPresenceSimulationCommand, + "WebAuthn.setCredentialProperties": SetCredentialPropertiesCommand, } as const; export const events = { "WebAuthn.credentialAdded": CredentialAddedEvent, diff --git a/js/src/types/generated/zod/WebMCP.ts b/js/src/types/generated/zod/WebMCP.ts index 0ca06d39..5fe6bd05 100644 --- a/js/src/types/generated/zod/WebMCP.ts +++ b/js/src/types/generated/zod/WebMCP.ts @@ -1,7 +1,7 @@ // Generated by types/codegen.ts from devtools-protocol@0.0.1621552. Do not edit by hand. // @ts-nocheck -- recursive protocol schemas intentionally use lazy self/cross references. import { z } from "zod"; -import { withCdpMeta } from "./helpers.js"; +import { withCdpCommand, withCdpMeta } from "./helpers.js"; import * as DOM from "./DOM.js"; import * as Page from "./Page.js"; import * as Runtime from "./Runtime.js"; @@ -12,12 +12,16 @@ export const Tool = withCdpMeta(z.object({ "name": z.string(), "description": z. export const RemovedTool = withCdpMeta(z.object({ "name": z.string(), "frameId": z.lazy(() => Page.FrameId) }).passthrough(), "WebMCP.RemovedTool", "type"); export const EnableParams = withCdpMeta(z.object({ }).passthrough(), "WebMCP.enable.params", "commandParams", { method: "WebMCP.enable" }); export const EnableResult = withCdpMeta(z.object({ }).passthrough(), "WebMCP.enable.result", "commandResult", { method: "WebMCP.enable" }); +export const EnableCommand = withCdpCommand("WebMCP.enable", EnableParams, EnableResult); export const DisableParams = withCdpMeta(z.object({ }).passthrough(), "WebMCP.disable.params", "commandParams", { method: "WebMCP.disable" }); export const DisableResult = withCdpMeta(z.object({ }).passthrough(), "WebMCP.disable.result", "commandResult", { method: "WebMCP.disable" }); +export const DisableCommand = withCdpCommand("WebMCP.disable", DisableParams, DisableResult); export const InvokeToolParams = withCdpMeta(z.object({ "frameId": z.lazy(() => Page.FrameId), "toolName": z.string(), "input": z.record(z.string(), z.unknown()) }).passthrough(), "WebMCP.invokeTool.params", "commandParams", { method: "WebMCP.invokeTool" }); export const InvokeToolResult = withCdpMeta(z.object({ "invocationId": z.string() }).passthrough(), "WebMCP.invokeTool.result", "commandResult", { method: "WebMCP.invokeTool" }); +export const InvokeToolCommand = withCdpCommand("WebMCP.invokeTool", InvokeToolParams, InvokeToolResult); export const CancelInvocationParams = withCdpMeta(z.object({ "invocationId": z.string() }).passthrough(), "WebMCP.cancelInvocation.params", "commandParams", { method: "WebMCP.cancelInvocation" }); export const CancelInvocationResult = withCdpMeta(z.object({ }).passthrough(), "WebMCP.cancelInvocation.result", "commandResult", { method: "WebMCP.cancelInvocation" }); +export const CancelInvocationCommand = withCdpCommand("WebMCP.cancelInvocation", CancelInvocationParams, CancelInvocationResult); export const ToolsAddedEvent = withCdpMeta(z.object({ "tools": z.array(z.lazy(() => Tool)) }).passthrough(), "WebMCP.toolsAdded", "event", { phase: "event" }); export const ToolsRemovedEvent = withCdpMeta(z.object({ "tools": z.array(z.lazy(() => RemovedTool)) }).passthrough(), "WebMCP.toolsRemoved", "event", { phase: "event" }); export const ToolInvokedEvent = withCdpMeta(z.object({ "toolName": z.string(), "frameId": z.lazy(() => Page.FrameId), "invocationId": z.string(), "input": z.string() }).passthrough(), "WebMCP.toolInvoked", "event", { phase: "event" }); @@ -42,10 +46,10 @@ export const zod = { ToolRespondedEvent: ToolRespondedEvent, } as const; export const commands = { - "WebMCP.enable": { params: EnableParams, result: EnableResult }, - "WebMCP.disable": { params: DisableParams, result: DisableResult }, - "WebMCP.invokeTool": { params: InvokeToolParams, result: InvokeToolResult }, - "WebMCP.cancelInvocation": { params: CancelInvocationParams, result: CancelInvocationResult }, + "WebMCP.enable": EnableCommand, + "WebMCP.disable": DisableCommand, + "WebMCP.invokeTool": InvokeToolCommand, + "WebMCP.cancelInvocation": CancelInvocationCommand, } as const; export const events = { "WebMCP.toolsAdded": ToolsAddedEvent, diff --git a/js/src/types/generated/zod/helpers.ts b/js/src/types/generated/zod/helpers.ts index 805339ad..a73fe65d 100644 --- a/js/src/types/generated/zod/helpers.ts +++ b/js/src/types/generated/zod/helpers.ts @@ -2,6 +2,7 @@ import type { z } from "zod"; export type CdpNamedSchema = T & { readonly id: string; readonly name: string; readonly kind: string; meta(): { id: string; name: string; kind: string } }; +export type CdpCommandSchema> = z.ZodType>, Result extends z.ZodType> = z.ZodType>, Name extends string = string> = { readonly id: Name; readonly name: Name; readonly kind: "command"; readonly params: Params; readonly result: Result; meta(): { id: Name; name: Name; kind: "command" } }; export const withCdpMeta = (schema: T, id: string, kind: string, extra = {}): CdpNamedSchema => { const meta = { id, name: id, kind, ...extra }; const named = schema.meta(meta); @@ -12,3 +13,14 @@ export const withCdpMeta = (schema: T, id: string, kind: st }); return named as CdpNamedSchema; }; +export const withCdpCommand = >, Result extends z.ZodType>>(id: Name, params: Params, result: Result): CdpCommandSchema => { + const meta = { id, name: id, kind: "command" as const }; + return { + id, + name: id, + kind: "command", + params, + result, + meta: () => meta, + }; +}; diff --git a/js/src/types/modcdp.ts b/js/src/types/modcdp.ts index b19e85ab..66abfd53 100644 --- a/js/src/types/modcdp.ts +++ b/js/src/types/modcdp.ts @@ -1,44 +1,63 @@ /// +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/types/modcdp.py +// - ./go/modcdp/types/types.go import { z } from "zod"; const isZodType = (value: unknown): value is z.ZodType => value != null && typeof value === "object" && typeof (value as z.ZodType).parse === "function"; -export const CdpCommandParamsSchema = z.object({}).passthrough(); -export type CdpCommandParams = z.infer; +const CdpCommandParamsSchema = z.record(z.string(), z.unknown()); +type CdpCommandParams = z.infer; -export const CdpCommandResultSchema = z.object({}).passthrough(); -export type CdpCommandResult = z.infer; +const CdpCommandResultSchema = z.record(z.string(), z.unknown()); +type CdpCommandResult = z.infer; -export const CdpEventParamsSchema = z.object({}).passthrough(); -export type CdpEventParams = z.infer; +const CdpEventParamsSchema = z.record(z.string(), z.unknown()); +type CdpEventParams = z.infer; -export const RuntimeBindingCalledEventSchema = z - .object({ - name: z.string(), - payload: z.string(), - executionContextId: z.number().optional(), - }) - .passthrough(); -export type RuntimeBindingCalledEvent = z.infer; +const RuntimeBindingCalledEventSchema = z.object({ + name: z.string(), + payload: z.string(), + executionContextId: z.number().optional().nullable(), +}); +type RuntimeBindingCalledEvent = z.infer; -export const TargetAttachedToTargetEventSchema = z +const TargetAttachedToTargetEventSchema = z.object({ + sessionId: z.string(), + targetInfo: z.object({ targetId: z.string() }), + waitingForDebugger: z.boolean(), +}); +type TargetAttachedToTargetEvent = z.infer; + +const DEFAULT_LAUNCHER_CHROME_READY_TIMEOUT_MS = 45_000; +const DEFAULT_LAUNCHER_CHROME_READY_POLL_INTERVAL_MS = 100; +const DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS = 10_000; +const DEFAULT_CLIENT_EVENT_WAIT_TIMEOUT_MS = 10_000; +const DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS = 250; +const DEFAULT_DOWNSTREAM_CLIENT_TIMEOUT_MS = 1_000; +const DEFAULT_ROUTER_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000; +const DEFAULT_UPSTREAM_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS = 250; +const DEFAULT_LAUNCHER_BB_BASE_URL = "https://api.browserbase.com"; +const DEFAULT_LAUNCHER_BB_VIEWPORT = { width: 1288, height: 711 }; + +const ModCDPRoutesSchema = z.object({}).catchall(z.string()); +type ModCDPRoutes = z.infer; + +const ModCDPRouterConfigSchema = z .object({ - sessionId: z.string(), - targetInfo: z.object({ targetId: z.string() }).passthrough(), - waitingForDebugger: z.boolean(), + router_routes: ModCDPRoutesSchema.default({}), + loopback_execution_context_timeout_ms: z.number().positive().default(DEFAULT_ROUTER_EXECUTION_CONTEXT_TIMEOUT_MS), }) - .passthrough(); -export type TargetAttachedToTargetEvent = z.infer; + .strict(); +type ModCDPRouterConfig = z.infer; -export const ModCDPRoutesSchema = z.object({}).catchall(z.string()); -export type ModCDPRoutes = z.infer; +const ModCDPCustomPayloadSchema = z.record(z.string(), z.unknown()); +type ModCDPCustomPayload = z.infer; -export const ModCDPCustomPayloadSchema = z.object({}).passthrough(); -export type ModCDPCustomPayload = z.infer; - -export type ModCDPNamedValue = { +type ModCDPNamedValue = { cdp_command_name?: string; cdp_event_name?: string; id?: string; @@ -53,7 +72,7 @@ export type ModCDPNamedValue = { | undefined; }; -export function normalizeModCDPName(value: ModCDPName) { +function normalizeModCDPName(value: ModCDPName) { if (typeof value === "string") return value; const meta = typeof value?.meta === "function" ? value.meta() : undefined; const name = @@ -69,7 +88,7 @@ export function normalizeModCDPName(value: ModCDPName) { return name; } -export const ModCDPNameSchema = z.custom((value) => { +const ModCDPNameSchema = z.custom((value) => { try { normalizeModCDPName(value as ModCDPName); return true; @@ -77,29 +96,29 @@ export const ModCDPNameSchema = z.custom((value) => { return false; } }); -export type ModCDPName = z.infer; +type ModCDPName = z.infer; -export const ModCDPZodTypeSchema = z.custom(isZodType); -export type ModCDPZodType = z.infer; +const ModCDPZodTypeSchema = z.custom(isZodType); +type ModCDPZodType = z.infer; -export const ModCDPPayloadJsonSchemaSchema = z.record(z.string(), z.unknown()); -export const ModCDPPayloadShapeSchema = z.record(z.string(), ModCDPZodTypeSchema); -export type ModCDPPayloadShape = z.infer; +const ModCDPPayloadJsonSchemaSchema = z.record(z.string(), z.unknown()); +const ModCDPPayloadShapeSchema = z.record(z.string(), ModCDPZodTypeSchema); +type ModCDPPayloadShape = z.infer; -export const ModCDPPayloadSchemaSpecSchema = z.union([ +const ModCDPPayloadSchemaSpecSchema = z.union([ ModCDPZodTypeSchema, ModCDPPayloadShapeSchema, ModCDPPayloadJsonSchemaSchema, ]); -export type ModCDPPayloadSchemaSpec = z.infer; +type ModCDPPayloadSchemaSpec = z.infer; -export function normalizeModCDPPayloadSchema(schema: ModCDPPayloadSchemaSpec | null | undefined) { +function validateZodSchema(schema: ModCDPPayloadSchemaSpec | null | undefined) { if (!schema) return null; if (isZodType(schema)) return schema; if (Object.values(schema).every(isZodType)) return z.object(schema as ModCDPPayloadShape).passthrough(); if (typeof schema === "object") { const zod_schema = z.fromJSONSchema(schema); - return isScalarJsonSchema(schema) ? z.object({ value: zod_schema }).passthrough() : zod_schema; + return isScalarJsonSchema(schema) ? z.object({ value: zod_schema }) : zod_schema; } throw new Error("Unsupported payload schema; pass a Zod schema, Zod shape, or object JSON schema."); } @@ -113,105 +132,151 @@ function isScalarJsonSchema(schema: Record) { ); } -export const ModCDPEvaluateParamsSchema = z.object({ +const ModCDPEvaluateParamsSchema = z.object({ expression: z.string(), - params: ModCDPCustomPayloadSchema.optional(), - cdpSessionId: z.string().nullable().optional(), + params: ModCDPCustomPayloadSchema.optional().nullable(), + cdpSessionId: z.string().optional().nullable(), }); -export type ModCDPEvaluateParams = z.infer; +type ModCDPEvaluateParams = z.infer; -export const ModCDPAddCustomCommandParamsSchema = z.object({ +const ModCDPAddCustomCommandParamsSchema = z.object({ name: ModCDPNameSchema, - expression: z.string().nullable().optional(), - params_schema: ModCDPPayloadSchemaSpecSchema.nullable().optional(), - result_schema: ModCDPPayloadSchemaSpecSchema.nullable().optional(), + expression: z.string().optional().nullable(), + params_schema: ModCDPPayloadSchemaSpecSchema.optional().nullable(), + result_schema: ModCDPPayloadSchemaSpecSchema.optional().nullable(), }); -export type ModCDPAddCustomCommandParams = z.infer; +type ModCDPAddCustomCommandParams = z.infer; -export const ModCDPAddCustomEventObjectParamsSchema = z.object({ +const ModCDPAddCustomEventObjectParamsSchema = z.object({ name: ModCDPNameSchema, - event_schema: ModCDPPayloadSchemaSpecSchema.nullable().optional(), + event_schema: ModCDPPayloadSchemaSpecSchema.optional().nullable(), }); -export type ModCDPAddCustomEventObjectParams = z.infer; -export const ModCDPAddCustomEventParamsSchema = z.union([ModCDPZodTypeSchema, ModCDPAddCustomEventObjectParamsSchema]); -export type ModCDPAddCustomEventParams = z.infer; +type ModCDPAddCustomEventObjectParams = z.infer; +const ModCDPAddCustomEventParamsSchema = z.union([ModCDPZodTypeSchema, ModCDPAddCustomEventObjectParamsSchema]); +type ModCDPAddCustomEventParams = z.infer; -export const ModCDPAddMiddlewareParamsSchema = z.object({ - name: ModCDPNameSchema.optional(), +const ModCDPAddMiddlewareParamsSchema = z.object({ + name: ModCDPNameSchema.optional().nullable(), phase: z.enum(["request", "response", "event"]), expression: z.string(), }); -export type ModCDPAddMiddlewareParams = z.infer; +type ModCDPAddMiddlewareParams = z.infer; -export const ModCDPLauncherOptionsSchema = z.object({}).passthrough(); -export type ModCDPLauncherOptions = z.infer; +const BrowserbaseBrowserSettingsSchema = z + .object({ + extensionId: z.string().optional(), + viewport: z.unknown().default(DEFAULT_LAUNCHER_BB_VIEWPORT), + }) + .catchall(z.unknown()); -export const ModCDPUpstreamOptionsSchema = z +const BrowserbaseSessionCreateParamsSchema = z .object({ - upstream_mode: z.enum(["ws", "pipe", "nativemessaging", "reversews", "nats"]).optional(), - upstream_nats_url: z.string().nullable().optional(), - upstream_nats_subject_prefix: z.string().nullable().optional(), - upstream_nats_wait_timeout_ms: z.number().positive().optional(), - upstream_reversews_bind: z.string().nullable().optional(), - upstream_reversews_wait_timeout_ms: z.number().positive().optional(), - upstream_nativemessaging_manifest: z.string().nullable().optional(), - upstream_nativemessaging_manifests: z.array(z.string()).nullable().optional(), - upstream_nativemessaging_host_name: z.string().nullable().optional(), - upstream_nativemessaging_wait_timeout_ms: z.number().positive().optional(), + browserSettings: BrowserbaseBrowserSettingsSchema.optional(), + userMetadata: z.record(z.string(), z.unknown()).default({}), + extensionId: z.string().optional(), + region: z.string().optional(), }) - .passthrough(); -export type ModCDPUpstreamOptions = z.infer; + .catchall(z.unknown()); -export const ModCDPClientOptionsSchema = z +const ModCDPLauncherConfigSchema = z .object({ - client_routes: ModCDPRoutesSchema.optional(), - client_hydrate_aliases: z.boolean().optional(), - client_mirror_upstream_events: z.boolean().optional(), - client_cdp_send_timeout_ms: z.number().positive().optional(), - client_event_wait_timeout_ms: z.number().positive().optional(), - client_heartbeat_interval_ms: z.number().positive().optional(), + launcher_mode: z.enum(["local", "remote", "bb", "none"]).default("none"), + launcher_local_executable_path: z.string().optional(), + launcher_local_user_data_dir: z.string().optional(), + launcher_remote_cdp_url: z.string().optional(), + launcher_local_cdp_listen_port: z.number().int().min(0).optional(), + launcher_local_headless: z.boolean().optional(), + launcher_local_sandbox: z.boolean().optional(), + launcher_local_args: z.array(z.string()).default([]), + launcher_local_extra_args: z.array(z.string()).default([]), + launcher_local_loopback_cdp: z.boolean().default(false), + launcher_local_cleanup_user_data_dir: z.boolean().default(false), + launcher_local_chrome_ready_timeout_ms: z.number().positive().default(DEFAULT_LAUNCHER_CHROME_READY_TIMEOUT_MS), + launcher_local_chrome_ready_poll_interval_ms: z + .number() + .positive() + .default(DEFAULT_LAUNCHER_CHROME_READY_POLL_INTERVAL_MS), + launcher_bb_api_key: z.string().optional(), + launcher_bb_base_url: z.string().default(DEFAULT_LAUNCHER_BB_BASE_URL), + launcher_bb_session_id: z.string().optional(), + launcher_bb_keep_alive: z.boolean().default(false), + launcher_bb_close_session_on_close: z.boolean().optional(), + launcher_bb_region: z.string().optional(), + launcher_bb_timeout: z.number().positive().optional(), + launcher_bb_extension_id: z.string().optional(), + launcher_bb_browser_settings: BrowserbaseBrowserSettingsSchema.default({ viewport: DEFAULT_LAUNCHER_BB_VIEWPORT }), + launcher_bb_user_metadata: z.record(z.string(), z.unknown()).default({}), + launcher_bb_session_create_params: BrowserbaseSessionCreateParamsSchema.default({ userMetadata: {} }), }) - .passthrough(); -export type ModCDPClientOptions = z.infer; + .strict(); +type ModCDPLauncherConfig = z.infer; -export const ModCDPServerOptionsSchema = z +const ModCDPUpstreamConfigSchema = z .object({ - server_loopback_cdp_url: z.string().nullable().optional(), - server_routes: ModCDPRoutesSchema.optional(), - server_browser_token: z.string().nullable().optional(), - server_cdp_send_timeout_ms: z.number().positive().optional(), - server_loopback_execution_context_timeout_ms: z.number().positive().optional(), - server_ws_connect_error_settle_timeout_ms: z.number().positive().optional(), - server_downstream_client_timeout_ms: z.number().positive().optional(), - server_close_browser_on_downstream_disconnect: z.boolean().optional(), + upstream_mode: z.enum(["ws"]).default("ws"), + upstream_ws_cdp_url: z.string().optional(), + upstream_ws_connect_error_settle_timeout_ms: z + .number() + .positive() + .default(DEFAULT_UPSTREAM_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS), + upstream_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), }) - .passthrough(); -export type ModCDPServerOptions = z.infer; - -export const ModCDPConfigureParamsSchema = z.object({ - launcher: ModCDPLauncherOptionsSchema.optional(), - upstream: ModCDPUpstreamOptionsSchema.optional(), - client: ModCDPClientOptionsSchema.optional(), - server: ModCDPServerOptionsSchema.optional(), - custom_commands: z.array(ModCDPAddCustomCommandParamsSchema).optional(), - custom_events: z.array(ModCDPAddCustomEventObjectParamsSchema).optional(), - custom_middlewares: z.array(ModCDPAddMiddlewareParamsSchema).optional(), -}); -export type ModCDPConfigureParams = z.infer; + .strict(); +type ModCDPUpstreamConfig = z.infer; + +const ModCDPClientConfigSchema = z + .object({ + client_hydrate_aliases: z.boolean().default(true), + client_mirror_upstream_events: z.boolean().default(true), + client_cdp_send_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS), + client_event_wait_timeout_ms: z.number().positive().default(DEFAULT_CLIENT_EVENT_WAIT_TIMEOUT_MS), + client_heartbeat_interval_ms: z.number().positive().default(DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS), + }) + .strict(); +type ModCDPClientConfig = z.infer; + +const ModCDPDownstreamConfigSchema = z + .object({ + downstream_client_timeout_ms: z.number().positive().default(DEFAULT_DOWNSTREAM_CLIENT_TIMEOUT_MS), + downstream_close_browser_on_disconnect: z.boolean().default(false), + closeBrowser: z + .custom<() => void | Promise>((value) => typeof value === "function") + .meta({ modcdp_wire: "omit" }) + .optional(), + }) + .strict(); +type ModCDPDownstreamConfig = z.infer; + +const ModCDPServerConfigSchema = z + .object({ + upstream: ModCDPUpstreamConfigSchema.optional(), + router: ModCDPRouterConfigSchema.optional(), + client_config: ModCDPClientConfigSchema.optional(), + downstream: ModCDPDownstreamConfigSchema.optional(), + server_browser_token: z.string().optional(), + custom_commands: z.array(ModCDPAddCustomCommandParamsSchema).optional(), + custom_events: z.array(ModCDPAddCustomEventObjectParamsSchema).optional(), + custom_middlewares: z.array(ModCDPAddMiddlewareParamsSchema).optional(), + }) + .strict(); +type ModCDPServerConfig = z.infer; -export const ModCDPPingParamsSchema = z.object({ +const ModCDPConfigureParamsSchema = ModCDPServerConfigSchema; +type ModCDPConfigureParams = z.infer; + +const ModCDPPingParamsSchema = z.object({ sent_at: z.number().optional(), }); -export type ModCDPPingParams = z.infer; +type ModCDPPingParams = z.infer; -export const ModCDPPongEventSchema = z.object({ +const ModCDPPongEventSchema = z.object({ sent_at: z.number(), received_at: z.number(), from: z.string(), }); -export type ModCDPPongEvent = z.infer; +type ModCDPPongEvent = z.infer; -export const ModCDPPingLatencySchema = z.object({ +const ModCDPPingLatencySchema = z.object({ sent_at: z.number(), received_at: z.number().nullable(), returned_at: z.number(), @@ -219,10 +284,74 @@ export const ModCDPPingLatencySchema = z.object({ service_worker_ms: z.number().nullable(), return_path_ms: z.number().nullable(), }); -export type ModCDPPingLatency = z.infer; +type ModCDPPingLatency = z.infer; + +const ModCDPGetTopologyParamsSchema = z.object({ + rootTargetId: z.string().optional().nullable(), + targetId: z.string().optional().nullable(), + active: z.boolean().optional().nullable(), +}); +type ModCDPGetTopologyParams = z.infer; -export const ModCDPCommandParamsSchema = z.union([ +const ModCDPTopologyFrameSchema = z.object({ + targetId: z.string(), + url: z.string().optional().nullable(), + parentFrameId: z.string().optional().nullable(), + outerBackendNodeId: z.number().int().optional().nullable(), +}); +type ModCDPTopologyFrame = z.infer; + +const ModCDPTopologyDomRootSchema = z.object({ + kind: z.enum(["document", "shadow"]), + frameId: z.string(), + outerBackendNodeId: z.number().int().optional().nullable(), + innerBackendNodeId: z.number().int().optional().nullable(), + mode: z.enum(["open", "closed", "user-agent"]).optional().nullable(), + executionContextId: z.number().int().optional().nullable(), + uniqueContextId: z.string().optional().nullable(), +}); +type ModCDPTopologyDomRoot = z.infer; + +const ModCDPTopologyTargetSchema = z + .object({ + targetId: z.string(), + type: z.string(), + title: z.string().optional().nullable(), + url: z.string().optional().nullable(), + attached: z.boolean().optional().nullable(), + parentId: z.string().optional().nullable(), + parentFrameId: z.string().optional().nullable(), + sessionId: z.string().optional().nullable(), + }) + .passthrough(); +type ModCDPTopologyTarget = z.infer; + +const ModCDPTopologyExecutionContextSchema = z.object({ + id: z.number().int(), + origin: z.string().optional().nullable(), + name: z.string().optional().nullable(), + uniqueId: z.string().optional().nullable(), + auxData: z.record(z.string(), z.unknown()).optional().nullable(), + sessionId: z.string().nullable(), + targetId: z.string(), + frameId: z.string().optional().nullable(), + world: z.string(), +}); +type ModCDPTopologyExecutionContext = z.infer; + +const ModCDPTopologySchema = z.object({ + objectGroup: z.string(), + rootFrameId: z.string(), + frames: z.record(z.string(), ModCDPTopologyFrameSchema), + roots: z.record(z.string(), ModCDPTopologyDomRootSchema), + targets: z.record(z.string(), ModCDPTopologyTargetSchema), + contexts: z.record(z.string(), ModCDPTopologyExecutionContextSchema), +}); +type ModCDPTopology = z.infer; + +const ModCDPCommandParamsSchema = z.union([ ModCDPEvaluateParamsSchema, + ModCDPGetTopologyParamsSchema, ModCDPAddCustomCommandParamsSchema, ModCDPAddCustomEventParamsSchema, ModCDPAddMiddlewareParamsSchema, @@ -230,189 +359,144 @@ export const ModCDPCommandParamsSchema = z.union([ ModCDPPingParamsSchema, ModCDPCustomPayloadSchema, ]); -export type ModCDPCommandParams = z.infer; +type ModCDPCommandParams = z.infer; -export const ModCDPCommandResultSchema = z.union([ - z.object({ ok: z.boolean() }).passthrough(), - ModCDPCustomPayloadSchema, -]); -export type ModCDPCommandResult = z.infer; +const ModCDPCommandResultSchema = z.union([z.object({ ok: z.boolean() }), ModCDPCustomPayloadSchema]); +type ModCDPCommandResult = z.infer; -export const ModCDPEvaluateResponseSchema = z.unknown(); -export type ModCDPEvaluateResponse = z.infer; +const ModCDPEvaluateResponseSchema = z.unknown(); +type ModCDPEvaluateResponse = z.infer; -export const ModCDPAddCustomCommandResponseSchema = z - .object({ - name: z.string(), - registered: z.boolean(), - }) - .passthrough(); -export type ModCDPAddCustomCommandResponse = z.infer; +const ModCDPGetTopologyResponseSchema = ModCDPTopologySchema; +type ModCDPGetTopologyResponse = z.infer; -export const ModCDPAddCustomEventResponseSchema = z - .object({ - name: z.string(), - registered: z.boolean(), - }) - .passthrough(); -export type ModCDPAddCustomEventResponse = z.infer; +const ModCDPAddCustomCommandResponseSchema = z.object({ + name: z.string(), + registered: z.boolean(), +}); +type ModCDPAddCustomCommandResponse = z.infer; -export const ModCDPAddMiddlewareResponseSchema = z - .object({ - name: z.string(), - phase: z.enum(["request", "response", "event"]), - registered: z.boolean(), - }) - .passthrough(); -export type ModCDPAddMiddlewareResponse = z.infer; +const ModCDPAddCustomEventResponseSchema = z.object({ + name: z.string(), + registered: z.boolean(), +}); +type ModCDPAddCustomEventResponse = z.infer; -export const ModCDPConfigureResponseSchema = z - .object({ - loopback_cdp_url: z.string().nullable().optional(), - routes: ModCDPRoutesSchema, - }) - .passthrough(); -export type ModCDPConfigureResponse = z.infer; +const ModCDPAddMiddlewareResponseSchema = z.object({ + name: z.string(), + phase: z.enum(["request", "response", "event"]), + registered: z.boolean(), +}); +type ModCDPAddMiddlewareResponse = z.infer; -export const ModCDPPingResponseSchema = z - .object({ - ok: z.boolean(), - }) - .passthrough(); -export type ModCDPPingResponse = z.infer; +const ModCDPConfigureResponseSchema = z.object({}).passthrough(); +type ModCDPConfigureResponse = z.infer; -export const ModCDPBindingPayloadSchema = z.object({ +const ModCDPPingResponseSchema = z.object({ + ok: z.boolean(), +}); +type ModCDPPingResponse = z.infer; + +const ModCDPBindingPayloadSchema = z.object({ event: z.string(), data: z.unknown(), cdpSessionId: z.string().nullable().optional(), }); -export type ModCDPBindingPayload = z.infer; +type ModCDPBindingPayload = z.infer; -export const CdpDebuggeeCommandParamsSchema = ModCDPCustomPayloadSchema.extend({ - debuggee: z.custom().nullable().optional(), - tabId: z.number().nullable().optional(), - targetId: z.string().nullable().optional(), - extensionId: z.string().nullable().optional(), -}); -export type CdpDebuggeeCommandParams = z.infer; +const CdpDebuggeeCommandParamsSchema = z + .object({ + debuggee: z.custom().nullable().optional(), + tabId: z.number().nullable().optional(), + targetId: z.string().nullable().optional(), + extensionId: z.string().nullable().optional(), + }) + .catchall(z.unknown()); +type CdpDebuggeeCommandParams = z.infer; -export const ProtocolParamsSchema = z.union([CdpCommandParamsSchema, ModCDPCommandParamsSchema]); -export type ProtocolParams = z.infer; +const ProtocolParamsSchema = z.union([CdpCommandParamsSchema, ModCDPCommandParamsSchema]); +type ProtocolParams = z.infer; -export const ProtocolResultSchema = z.union([CdpCommandResultSchema, ModCDPCommandResultSchema]); -export type ProtocolResult = z.infer; +const ProtocolResultSchema = z.union([CdpCommandResultSchema, ModCDPCommandResultSchema]); +type ProtocolResult = CdpCommandResult; -export const ProtocolEventParamsSchema = z.union([ - CdpEventParamsSchema, - ModCDPPongEventSchema, - ModCDPCustomPayloadSchema, -]); -export type ProtocolEventParams = z.infer; +const ProtocolEventParamsSchema = z.union([CdpEventParamsSchema, ModCDPPongEventSchema, ModCDPCustomPayloadSchema]); +type ProtocolEventParams = z.infer; -export const ProtocolPayloadSchema = z.union([ +const ProtocolPayloadSchema = z.union([ ProtocolParamsSchema, ProtocolResultSchema, ProtocolEventParamsSchema, ModCDPBindingPayloadSchema, z.null(), ]); -export type ProtocolPayload = z.infer; - -export const ModCDPCustomCommandRegistrationSchema = ModCDPAddCustomCommandParamsSchema.extend({ - expression: z.string().nullable().optional(), - handler: - z.custom< - (params: ProtocolParams, cdpSessionId: string | null, method?: string) => ProtocolResult | Promise - >(), -}); -export type ModCDPCustomCommandRegistration = z.infer; - -export const ModCDPCustomEventRegistrationSchema = ModCDPAddCustomEventObjectParamsSchema; -export type ModCDPCustomEventRegistration = z.infer; - -export const ModCDPMiddlewareRegistrationSchema = ModCDPAddMiddlewareParamsSchema.extend({ - expression: z.string().nullable().optional(), - handler: - z.custom< - ( - payload: ProtocolPayload, - next: (payload?: ProtocolPayload) => Promise, - context: ModCDPCustomPayload, - ) => ProtocolPayload | Promise - >(), -}); -export type ModCDPMiddlewareRegistration = z.infer; +type ProtocolPayload = z.infer; -export const CdpErrorSchema = z - .object({ - code: z.number().optional(), - message: z.string(), - data: z.unknown().optional(), - }) - .passthrough(); -export type CdpError = z.infer; +const ModCDPCustomCommandRegistrationSchema = ModCDPAddCustomCommandParamsSchema; +type ModCDPCustomCommandRegistration = z.infer; -export const CdpCommandMessageSchema = z - .object({ - id: z.number(), - method: z.string(), - params: ProtocolParamsSchema.optional(), - sessionId: z.string().optional(), - }) - .passthrough(); -export type CdpCommandMessage = z.infer; +const ModCDPCustomEventRegistrationSchema = ModCDPAddCustomEventObjectParamsSchema; +type ModCDPCustomEventRegistration = z.infer; -export const CdpResponseMessageSchema = z - .object({ - id: z.number(), - result: z.unknown().optional(), - error: CdpErrorSchema.optional(), - sessionId: z.string().optional(), - }) - .passthrough(); -export type CdpResponseMessage = z.infer; +const ModCDPMiddlewareRegistrationSchema = ModCDPAddMiddlewareParamsSchema; +type ModCDPMiddlewareRegistration = z.infer; -export const CdpEventMessageSchema = z - .object({ - method: z.string(), - params: ProtocolEventParamsSchema.optional(), - sessionId: z.string().optional(), - }) - .passthrough(); -export type CdpEventMessage = z.infer; +const CdpErrorSchema = z.object({ + code: z.number().optional().nullable(), + message: z.string(), + data: z.unknown().optional().nullable(), +}); +type CdpError = z.infer; -export const CdpMessageSchema = z.union([CdpCommandMessageSchema, CdpResponseMessageSchema, CdpEventMessageSchema]); -export type CdpMessage = z.infer; +const CdpCommandMessageSchema = z.object({ + id: z.number(), + method: z.string(), + params: ProtocolParamsSchema.optional().nullable(), + sessionId: z.string().optional().nullable(), +}); +type CdpCommandMessage = z.infer; -export const TranslatedStepSchema = z - .object({ - method: z.string(), - params: ProtocolParamsSchema.optional(), - sessionId: z.string().nullable().optional(), - unwrap: z.enum(["runtime", "runtime_json"]).optional(), - }) - .passthrough(); -export type TranslatedStep = z.infer; +const CdpResponseMessageSchema = z.object({ + id: z.number(), + result: z.unknown().optional().nullable(), + error: CdpErrorSchema.optional().nullable(), + sessionId: z.string().optional().nullable(), +}); +type CdpResponseMessage = z.infer; -export const TranslatedCommandSchema = z - .object({ - route: z.string(), - target: z.enum(["direct_cdp", "service_worker"]), - steps: z.array(TranslatedStepSchema), - }) - .passthrough(); -export type TranslatedCommand = z.infer; +const CdpEventMessageSchema = z.object({ + method: z.string(), + params: ProtocolEventParamsSchema.optional().nullable(), + sessionId: z.string().optional().nullable(), +}); +type CdpEventMessage = z.infer; -export const UnwrappedModCDPEventSchema = z - .object({ - event: z.string(), - data: ProtocolPayloadSchema, - sessionId: z.string().nullable(), - }) - .passthrough(); -export type UnwrappedModCDPEvent = z.infer; +const CdpMessageSchema = z.union([CdpCommandMessageSchema, CdpResponseMessageSchema, CdpEventMessageSchema]); +type CdpMessage = z.infer; + +const TranslatedStepSchema = z.object({ + method: z.string(), + params: ProtocolParamsSchema.optional().nullable(), + sessionId: z.string().optional().nullable(), + unwrap: z.enum(["runtime", "runtime_json"]).optional().nullable(), +}); +type TranslatedStep = z.infer; + +const TranslatedCommandSchema = z.object({ + route: z.string(), + target: z.enum(["direct_cdp", "service_worker"]), + steps: z.array(TranslatedStepSchema), +}); +type TranslatedCommand = z.infer; -export const Mod = { +const UnwrappedModCDPEventSchema = z.object({ + event: z.string(), + data: ProtocolPayloadSchema, + sessionId: z.string().nullable(), +}); +type UnwrappedModCDPEvent = z.infer; + +const Mod = { Routes: ModCDPRoutesSchema, CustomPayload: ModCDPCustomPayloadSchema, Name: ModCDPNameSchema, @@ -420,21 +504,29 @@ export const Mod = { PayloadShape: ModCDPPayloadShapeSchema, PayloadSchemaSpec: ModCDPPayloadSchemaSpecSchema, EvaluateParams: ModCDPEvaluateParamsSchema, + GetTopologyParams: ModCDPGetTopologyParamsSchema, AddCustomCommandParams: ModCDPAddCustomCommandParamsSchema, AddCustomEventObjectParams: ModCDPAddCustomEventObjectParamsSchema, AddCustomEventParams: ModCDPAddCustomEventParamsSchema, AddMiddlewareParams: ModCDPAddMiddlewareParamsSchema, - LauncherOptions: ModCDPLauncherOptionsSchema, - UpstreamOptions: ModCDPUpstreamOptionsSchema, - ClientOptions: ModCDPClientOptionsSchema, - ServerOptions: ModCDPServerOptionsSchema, + LauncherConfig: ModCDPLauncherConfigSchema, + UpstreamConfig: ModCDPUpstreamConfigSchema, + ClientConfig: ModCDPClientConfigSchema, + DownstreamConfig: ModCDPDownstreamConfigSchema, + ServerConfig: ModCDPServerConfigSchema, ConfigureParams: ModCDPConfigureParamsSchema, PingParams: ModCDPPingParamsSchema, PongEvent: ModCDPPongEventSchema, PingLatency: ModCDPPingLatencySchema, + TopologyFrame: ModCDPTopologyFrameSchema, + TopologyDomRoot: ModCDPTopologyDomRootSchema, + TopologyTarget: ModCDPTopologyTargetSchema, + TopologyExecutionContext: ModCDPTopologyExecutionContextSchema, + Topology: ModCDPTopologySchema, CommandParams: ModCDPCommandParamsSchema, CommandResult: ModCDPCommandResultSchema, EvaluateResponse: ModCDPEvaluateResponseSchema, + GetTopologyResponse: ModCDPGetTopologyResponseSchema, AddCustomCommandResponse: ModCDPAddCustomCommandResponseSchema, AddCustomEventResponse: ModCDPAddCustomEventResponseSchema, AddMiddlewareResponse: ModCDPAddMiddlewareResponseSchema, @@ -445,3 +537,139 @@ export const Mod = { CustomEventRegistration: ModCDPCustomEventRegistrationSchema, MiddlewareRegistration: ModCDPMiddlewareRegistrationSchema, } as const; + +export { + DEFAULT_LAUNCHER_BB_BASE_URL, + DEFAULT_LAUNCHER_BB_VIEWPORT, + DEFAULT_LAUNCHER_CHROME_READY_TIMEOUT_MS, + DEFAULT_LAUNCHER_CHROME_READY_POLL_INTERVAL_MS, + DEFAULT_CLIENT_CDP_SEND_TIMEOUT_MS, + DEFAULT_CLIENT_EVENT_WAIT_TIMEOUT_MS, + DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS, + DEFAULT_DOWNSTREAM_CLIENT_TIMEOUT_MS, + DEFAULT_ROUTER_EXECUTION_CONTEXT_TIMEOUT_MS, + DEFAULT_UPSTREAM_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS, + CdpCommandParamsSchema, + CdpCommandResultSchema, + CdpEventParamsSchema, + RuntimeBindingCalledEventSchema, + TargetAttachedToTargetEventSchema, + ModCDPRoutesSchema, + ModCDPRouterConfigSchema, + ModCDPCustomPayloadSchema, + normalizeModCDPName, + ModCDPNameSchema, + ModCDPZodTypeSchema, + ModCDPPayloadJsonSchemaSchema, + ModCDPPayloadShapeSchema, + ModCDPPayloadSchemaSpecSchema, + validateZodSchema, + ModCDPEvaluateParamsSchema, + ModCDPAddCustomCommandParamsSchema, + ModCDPAddCustomEventObjectParamsSchema, + ModCDPAddCustomEventParamsSchema, + ModCDPAddMiddlewareParamsSchema, + ModCDPLauncherConfigSchema, + ModCDPUpstreamConfigSchema, + ModCDPClientConfigSchema, + ModCDPDownstreamConfigSchema, + ModCDPServerConfigSchema, + ModCDPConfigureParamsSchema, + ModCDPPingParamsSchema, + ModCDPPongEventSchema, + ModCDPPingLatencySchema, + ModCDPGetTopologyParamsSchema, + ModCDPTopologyFrameSchema, + ModCDPTopologyDomRootSchema, + ModCDPTopologyTargetSchema, + ModCDPTopologyExecutionContextSchema, + ModCDPTopologySchema, + ModCDPCommandParamsSchema, + ModCDPCommandResultSchema, + ModCDPEvaluateResponseSchema, + ModCDPGetTopologyResponseSchema, + ModCDPAddCustomCommandResponseSchema, + ModCDPAddCustomEventResponseSchema, + ModCDPAddMiddlewareResponseSchema, + ModCDPConfigureResponseSchema, + ModCDPPingResponseSchema, + ModCDPBindingPayloadSchema, + CdpDebuggeeCommandParamsSchema, + ProtocolParamsSchema, + ProtocolResultSchema, + ProtocolEventParamsSchema, + ProtocolPayloadSchema, + ModCDPCustomCommandRegistrationSchema, + ModCDPCustomEventRegistrationSchema, + ModCDPMiddlewareRegistrationSchema, + CdpErrorSchema, + CdpCommandMessageSchema, + CdpResponseMessageSchema, + CdpEventMessageSchema, + CdpMessageSchema, + TranslatedStepSchema, + TranslatedCommandSchema, + UnwrappedModCDPEventSchema, + Mod, +}; +export type { + CdpCommandParams, + CdpCommandResult, + CdpEventParams, + RuntimeBindingCalledEvent, + TargetAttachedToTargetEvent, + ModCDPRoutes, + ModCDPRouterConfig, + ModCDPCustomPayload, + ModCDPNamedValue, + ModCDPName, + ModCDPZodType, + ModCDPPayloadShape, + ModCDPPayloadSchemaSpec, + ModCDPEvaluateParams, + ModCDPAddCustomCommandParams, + ModCDPAddCustomEventObjectParams, + ModCDPAddCustomEventParams, + ModCDPAddMiddlewareParams, + ModCDPLauncherConfig, + ModCDPUpstreamConfig, + ModCDPClientConfig, + ModCDPDownstreamConfig, + ModCDPServerConfig, + ModCDPConfigureParams, + ModCDPPingParams, + ModCDPPongEvent, + ModCDPPingLatency, + ModCDPGetTopologyParams, + ModCDPTopologyFrame, + ModCDPTopologyDomRoot, + ModCDPTopologyTarget, + ModCDPTopologyExecutionContext, + ModCDPTopology, + ModCDPCommandParams, + ModCDPCommandResult, + ModCDPEvaluateResponse, + ModCDPGetTopologyResponse, + ModCDPAddCustomCommandResponse, + ModCDPAddCustomEventResponse, + ModCDPAddMiddlewareResponse, + ModCDPConfigureResponse, + ModCDPPingResponse, + ModCDPBindingPayload, + CdpDebuggeeCommandParams, + ProtocolParams, + ProtocolResult, + ProtocolEventParams, + ProtocolPayload, + ModCDPCustomCommandRegistration, + ModCDPCustomEventRegistration, + ModCDPMiddlewareRegistration, + CdpError, + CdpCommandMessage, + CdpResponseMessage, + CdpEventMessage, + CdpMessage, + TranslatedStep, + TranslatedCommand, + UnwrappedModCDPEvent, +}; diff --git a/js/src/types/toJSON.ts b/js/src/types/toJSON.ts new file mode 100644 index 00000000..a9c9a454 --- /dev/null +++ b/js/src/types/toJSON.ts @@ -0,0 +1,35 @@ +// MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +// - ./python/modcdp/types/modcdp.py +// - ./go/modcdp/types/types.go +type ModCDPJSONChild = { toJSON(): unknown } | null | undefined; + +type ModCDPJSONConfig = { + config?: unknown; + state?: object; + children?: Record; +}; + +function simpleState(input: object) { + const state: Record = {}; + for (const [key, value] of Object.entries(input)) { + if (key === "config" || key.includes("token") || key.includes("secret") || key.includes("api_key")) continue; + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") state[key] = value; + } + return state; +} + +function modCDPToJSON(instance: object & { config?: unknown }, config: ModCDPJSONConfig = {}) { + const children: Record = {}; + for (const [key, child] of Object.entries(config.children ?? {})) { + if (child) children[key] = child.toJSON(); + } + return { + type: instance.constructor.name, + config: config.config ?? instance.config ?? {}, + state: { ...simpleState(instance), ...simpleState(config.state ?? {}) }, + ...(Object.keys(children).length > 0 ? { children } : {}), + }; +} + +export { modCDPToJSON }; diff --git a/js/test/ModCDPClient_protocol_validation.test.ts b/js/test/ModCDPClient_protocol_validation.test.ts index 8d82d43c..643da3d5 100644 --- a/js/test/ModCDPClient_protocol_validation.test.ts +++ b/js/test/ModCDPClient_protocol_validation.test.ts @@ -1,31 +1,33 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ModCDPClient_protocol_validation.py +// - ./go/modcdp/client/ModCDPClient_protocol_validation_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { test } from "vitest"; import { z } from "zod"; -import { ModCDPClient, type ModCDPClientInstance } from "../src/client/ModCDPClient.js"; +import { ModCDPClient } from "../src/index.js"; +import { CDPTypes } from "../src/types/CDPTypes.js"; import type { cdp } from "../src/types/generated/cdp.js"; -test("protocol validation covers native methods, native events, custom methods, custom events, and native overrides", async () => { - type CustomClient = ModCDPClientInstance< - { - "Custom.echo": { params: { text: string }; result: { ok: boolean } }; - "Target.getTargets": { - params: cdp.types.ts.Target.GetTargetsParams; - result: cdp.types.ts.Target.GetTargetsResult & { - targetInfos: Array; - }; - }; - }, - { "Custom.ready": { ok: boolean } } - >; - - const client = new ModCDPClient() as CustomClient; - - const runtimeParams: cdp.types.ts.Runtime.EvaluateParams = { expression: "1 + 1", returnByValue: true }; - const runtimeResult: cdp.types.ts.Runtime.EvaluateResult = { +test("native CDP schemas validate method params, return values, and event payloads statically and at runtime", () => { + const types = new CDPTypes(); + const client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); + const runtime_params: cdp.types.ts.Runtime.EvaluateParams = { + expression: "1 + 1", + returnByValue: true, + }; + const runtime_result: cdp.types.ts.Runtime.EvaluateResult = { result: { type: "number", value: 2, description: "2" }, }; - const nativeEvent: cdp.types.ts.Target.TargetCreatedEvent = { + const target_event: cdp.types.ts.Target.TargetCreatedEvent = { targetInfo: { targetId: "target-1", type: "page", @@ -35,79 +37,341 @@ test("protocol validation covers native methods, native events, custom methods, canAccessOpener: false, }, }; - const customParams: Parameters[0] = { text: "ok" }; - const customResult: Awaited> = { ok: true }; if (false) { + const alias_result: Awaited> = runtime_result; + void alias_result; + client.on(client.Target.targetCreated, (event) => { + const targetId: string = event.targetInfo.targetId; + void targetId; + // @ts-expect-error targetId is a string, not a number. + const badTargetId: number = event.targetInfo.targetId; + void badTargetId; + }); // @ts-expect-error Runtime.evaluate params require expression. - const badRuntimeParams: cdp.types.ts.Runtime.EvaluateParams = {}; - void badRuntimeParams; - const badNativeEvent: cdp.types.ts.Target.TargetCreatedEvent = { + client.Runtime.evaluate({ returnByValue: true }); + const badRuntimeResult: cdp.types.ts.Runtime.EvaluateResult = { + // @ts-expect-error Runtime.evaluate result must be a Runtime.RemoteObject. + result: { type: 1 }, + }; + void badRuntimeResult; + const badTargetEvent: cdp.types.ts.Target.TargetCreatedEvent = { targetInfo: { - ...nativeEvent.targetInfo, + ...target_event.targetInfo, // @ts-expect-error Target.targetCreated targetInfo.targetId is a string. targetId: 1, }, }; - void badNativeEvent; - // @ts-expect-error Custom.echo requires text. - client.Custom.echo({ id: "wrong" }); - // @ts-expect-error Custom.echo returns ok as boolean. - const badCustomResult: Awaited> = { ok: "yes" }; - void badCustomResult; - await client.Mod.addMiddleware({ - name: client.Target.getTargets, - phase: client.RESPONSE, - expression: "async (value, next) => next(value)", + void badTargetEvent; + } + + assert.deepEqual(types.parseCommandParams("Runtime.evaluate", runtime_params), runtime_params); + assert.deepEqual(types.parseCommandResult("Runtime.evaluate", runtime_result), runtime_result); + assert.deepEqual(types.parseEventPayload("Target.targetCreated", target_event), target_event); + assert.throws(() => types.parseCommandParams("Runtime.evaluate", { returnByValue: true })); + assert.throws(() => types.parseCommandResult("Runtime.evaluate", {})); + assert.throws(() => + types.parseEventPayload("Target.targetCreated", { + targetInfo: { ...target_event.targetInfo, targetId: 1 }, + }), + ); +}); + +test("Mod schemas validate method params, return values, event payloads, and middleware registrations statically and at runtime", () => { + const types = new CDPTypes(); + const client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); + const ping_params: cdp.types.ts.Mod.PingParams = { sent_at: 123 }; + const ping_result: cdp.types.ts.Mod.PingResponse = { ok: true }; + const pong_event: cdp.types.ts.Mod.PongEvent = { + sent_at: 123, + received_at: 124, + from: "extension-service-worker", + }; + const middleware_params: cdp.types.ts.Mod.AddMiddlewareParams = { + name: client.Target.getTargets, + phase: client.RESPONSE, + expression: "async (payload, next) => next(payload)", + }; + const middleware_result: cdp.types.ts.Mod.AddMiddlewareResponse = { + name: "Target.getTargets", + phase: "response", + registered: true, + }; + + if (false) { + const alias_result: Awaited> = ping_result; + void alias_result; + const alias_middleware_result: Awaited> = middleware_result; + void alias_middleware_result; + // @ts-expect-error Mod.ping sent_at must be a number. + client.Mod.ping({ sent_at: "123" }); + client.Mod.addMiddleware({ + name: "Custom.any", + // @ts-expect-error middleware phase must be request, response, or event. + phase: "after", + expression: "async (payload, next) => next(payload)", }); + // @ts-expect-error Mod.pong received_at is required. + const badPongEvent: cdp.types.ts.Mod.PongEvent = { + sent_at: 123, + from: "extension-service-worker", + }; + void badPongEvent; } - assert.deepEqual(client.command_params_schemas.get("Runtime.evaluate")?.parse(runtimeParams), runtimeParams); - assert.deepEqual(client.command_result_schemas.get("Runtime.evaluate")?.parse(runtimeResult), runtimeResult); - assert.deepEqual(client.event_schemas.get("Target.targetCreated")?.parse(nativeEvent), nativeEvent); - assert.throws(() => client.command_params_schemas.get("Runtime.evaluate")?.parse({})); - assert.throws(() => client.command_result_schemas.get("Runtime.evaluate")?.parse({})); - assert.throws(() => client.event_schemas.get("Target.targetCreated")?.parse({})); + assert.deepEqual(types.parseCommandParams("Mod.ping", ping_params), { + sent_at: 123, + }); + assert.deepEqual(types.parseCommandResult("Mod.ping", ping_result), { + ok: true, + }); + assert.deepEqual(types.parseEventPayload("Mod.pong", pong_event), pong_event); + assert.deepEqual(types.parseCommandParams("Mod.addMiddleware", middleware_params), middleware_params); + assert.deepEqual(types.parseCommandResult("Mod.addMiddleware", middleware_result), middleware_result); + assert.throws(() => types.parseCommandParams("Mod.ping", { sent_at: "123" })); + assert.throws(() => types.parseCommandResult("Mod.ping", { ok: "true" })); + assert.throws(() => + types.parseEventPayload("Mod.pong", { + sent_at: 123, + from: "extension-service-worker", + }), + ); + assert.throws(() => + types.parseCommandParams("Mod.addMiddleware", { + name: "Custom.any", + phase: "after", + expression: "async (payload, next) => next(payload)", + }), + ); + assert.throws(() => + types.parseCommandResult("Mod.addMiddleware", { + name: "Custom.any", + phase: "after", + registered: true, + }), + ); +}); - await client.Mod.addCustomCommand("Custom.echo", { - params_schema: z.object({ text: z.string() }), - result_schema: z.object({ ok: z.boolean() }), +test("constructor custom schemas validate command params, return values, events, and middleware registrations statically and at runtime", () => { + const SumParams = z.object({ left: z.number(), right: z.number() }); + const SumResult = z.object({ value: z.number() }); + const FinishedEvent = z.object({ total: z.number(), label: z.string() }); + const client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + types: { + custom_commands: { + "Custom.sum": { + params_schema: SumParams, + result_schema: SumResult, + expression: "async ({ left, right }) => ({ value: left + right })", + }, + }, + custom_events: { + "Custom.finished": { event_schema: FinishedEvent }, + }, + custom_middlewares: [ + { + name: "Custom.sum", + phase: "response", + expression: "async (payload, next) => next(payload)", + }, + ], + }, }); - await client.Mod.addCustomEvent("Custom.ready", { event_schema: z.object({ ok: z.boolean() }) }); - assert.deepEqual(client.command_params_schemas.get("Custom.echo")?.parse(customParams), customParams); - assert.deepEqual(client.command_result_schemas.get("Custom.echo")?.parse(customResult), customResult); - assert.deepEqual(client.event_schemas.get("Custom.ready")?.parse({ ok: true }), { ok: true }); - assert.throws(() => client.command_params_schemas.get("Custom.echo")?.parse({ text: 1 })); - assert.throws(() => client.command_result_schemas.get("Custom.echo")?.parse({ ok: "yes" })); - assert.throws(() => client.event_schemas.get("Custom.ready")?.parse({ ok: "yes" })); + if (false) { + const params: Parameters[0] = { + left: 1, + right: 2, + }; + void params; + const result: Awaited> = { value: 3 }; + void result; + client.on("Custom.finished", (event) => { + const total: number = event.total; + void total; + // @ts-expect-error Custom.finished total is a number. + const badTotal: string = event.total; + void badTotal; + }); + // @ts-expect-error Custom.sum requires numeric left/right params. + client.Custom.sum({ left: "1", right: 2 }); + // @ts-expect-error Custom.sum returns a CDP-style result object. + const badResult: Awaited> = 3; + void badResult; + } - await client.Mod.addCustomCommand({ - name: client.Target.getTargets, - result_schema: z.object({ - targetInfos: z.array( - z.object({ - targetId: z.string(), - type: z.string(), - title: z.string(), - url: z.string(), - attached: z.boolean(), - canAccessOpener: z.boolean(), - tabId: z.number().optional(), - }), - ), + assert.deepEqual(client.types.parseCommandParams("Custom.sum", { left: 1, right: 2 }), { left: 1, right: 2 }); + assert.deepEqual(client.types.parseCommandResult("Custom.sum", { value: 3 }), { value: 3 }); + assert.deepEqual( + client.types.parseEventPayload("Custom.finished", { + total: 3, + label: "ok", }), + { total: 3, label: "ok" }, + ); + assert.throws(() => client.types.parseCommandParams("Custom.sum", { left: "1", right: 2 })); + assert.throws(() => client.types.parseCommandResult("Custom.sum", { value: "3" })); + assert.throws(() => + client.types.parseEventPayload("Custom.finished", { + total: "3", + label: "ok", + }), + ); + assert.deepEqual(client.types.customMiddlewareWireRegistrations(), [ + { + name: "Custom.sum", + phase: "response", + expression: "async (payload, next) => next(payload)", + }, + ]); + assert.throws( + () => + new CDPTypes({ + custom_middlewares: [ + { + name: "Custom.sum", + phase: "after", + expression: "async (payload, next) => next(payload)", + } as never, + ], + }), + ); +}); + +test("dynamic Mod registration updates custom command, event, and middleware validation", async () => { + const client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, }); - await client.Mod.addCustomEvent({ name: client.Target.targetCreated }); - const extendedTargets: Awaited> = { - targetInfos: [{ ...nativeEvent.targetInfo, tabId: 7 }], - }; - assert.deepEqual(client.command_result_schemas.get("Target.getTargets")?.parse(extendedTargets), extendedTargets); assert.deepEqual( - client.event_schemas.get("Target.targetCreated")?.parse({ targetInfo: { ...nativeEvent.targetInfo, tabId: 7 } }), + await client.Mod.addCustomCommand("Custom.dynamic", { + params_schema: z.object({ text: z.string().min(1) }), + result_schema: z.object({ ok: z.boolean() }), + }), + { name: "Custom.dynamic", registered: true }, + ); + assert.deepEqual( + await client.Mod.addCustomEvent("Custom.dynamicReady", { + event_schema: z.object({ id: z.string().uuid() }), + }), + { name: "Custom.dynamicReady", registered: true }, + ); + assert.deepEqual( + await client.Mod.addMiddleware({ + name: "Custom.dynamic", + phase: client.RESPONSE, + expression: "async (payload, next) => next(payload)", + }), + { name: "Custom.dynamic", phase: "response", registered: true }, + ); + + assert.equal(typeof (client as unknown as { Custom: { dynamic: unknown } }).Custom.dynamic, "function"); + assert.deepEqual(client.types.parseCommandParams("Custom.dynamic", { text: "ok" }), { text: "ok" }); + assert.deepEqual(client.types.parseCommandResult("Custom.dynamic", { ok: true }), { ok: true }); + assert.deepEqual( + client.types.parseEventPayload("Custom.dynamicReady", { + id: "550e8400-e29b-41d4-a716-446655440000", + }), + { id: "550e8400-e29b-41d4-a716-446655440000" }, + ); + assert.deepEqual(client.types.customMiddlewareWireRegistrations(), [ { - targetInfo: { ...nativeEvent.targetInfo, tabId: 7 }, + name: "Custom.dynamic", + phase: "response", + expression: "async (payload, next) => next(payload)", }, + ]); + assert.throws(() => client.types.parseCommandParams("Custom.dynamic", { text: "" })); + assert.throws(() => client.types.parseCommandResult("Custom.dynamic", { ok: "yes" })); + assert.throws(() => client.types.parseEventPayload("Custom.dynamicReady", { id: "nope" })); + await assert.rejects(() => + client.Mod.addMiddleware({ + name: "Custom.dynamic", + phase: "after" as never, + expression: "async (payload, next) => next(payload)", + }), ); }); + +test("client.types update replaces the registry with extended runtime validation and preserves static custom aliases on typed clients", () => { + const client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); + const updated_types = client.types.update({ + custom_commands: { + "Custom.updated": { + params_schema: z.object({ count: z.number().int().positive() }), + result_schema: z.object({ done: z.boolean() }), + }, + }, + custom_events: { + "Custom.updatedReady": { event_schema: z.object({ ready: z.boolean() }) }, + }, + custom_middlewares: [ + { + name: "Custom.updated", + phase: "request", + expression: "async (payload, next) => next(payload)", + }, + ], + }); + const typed_client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + types: updated_types, + }); + client.types = updated_types; + + if (false) { + const params: Parameters[0] = { + count: 1, + }; + void params; + const result: Awaited> = { done: true }; + void result; + typed_client.on("Custom.updatedReady", (event) => { + const ready: boolean = event.ready; + void ready; + // @ts-expect-error Custom.updatedReady.ready is boolean. + const badReady: string = event.ready; + void badReady; + }); + // @ts-expect-error Custom.updated count is required. + typed_client.Custom.updated({}); + // @ts-expect-error Custom.updated returns a CDP-style result object. + const badResult: Awaited> = true; + void badResult; + } + + assert.equal(typeof (client as unknown as { Custom: { updated: unknown } }).Custom.updated, "function"); + assert.deepEqual(client.types.parseCommandParams("Custom.updated", { count: 1 }), { count: 1 }); + assert.deepEqual(client.types.parseCommandResult("Custom.updated", { done: true }), { done: true }); + assert.deepEqual(client.types.parseEventPayload("Custom.updatedReady", { ready: true }), { ready: true }); + assert.deepEqual(client.types.customMiddlewareWireRegistrations(), [ + { + name: "Custom.updated", + phase: "request", + expression: "async (payload, next) => next(payload)", + }, + ]); + assert.throws(() => client.types.parseCommandParams("Custom.updated", { count: 0 })); + assert.throws(() => client.types.parseCommandResult("Custom.updated", { done: "true" })); + assert.throws(() => client.types.parseEventPayload("Custom.updatedReady", { ready: "true" })); +}); diff --git a/js/test/browserPaths.ts b/js/test/browserPaths.ts new file mode 100644 index 00000000..efc680c3 --- /dev/null +++ b/js/test/browserPaths.ts @@ -0,0 +1,96 @@ +import { existsSync, readdirSync, statSync } from "node:fs"; +import { homedir, platform } from "node:os"; +import path from "node:path"; + +function wildcardToRegExp(value: string) { + return new RegExp(`^${value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`); +} + +function expandGlob(pattern: string) { + const normalized = path.normalize(pattern); + const { root } = path.parse(normalized); + const parts = normalized.slice(root.length).split(path.sep).filter(Boolean); + let candidates = [root || "."]; + for (const part of parts) { + const hasWildcard = part.includes("*"); + const matcher = hasWildcard ? wildcardToRegExp(part) : null; + const next: string[] = []; + for (const base of candidates) { + if (!existsSync(base)) continue; + if (!hasWildcard) { + const candidate = path.join(base, part); + if (existsSync(candidate)) next.push(candidate); + continue; + } + try { + for (const child of readdirSync(base)) { + if (matcher!.test(child)) next.push(path.join(base, child)); + } + } catch {} + } + candidates = next; + } + return candidates.filter((candidate) => existsSync(candidate)); +} + +function newestFirst(candidates: string[]) { + const score = (candidate: string) => { + const numbers = candidate.match(/\d+/g)?.map(Number) ?? []; + const version = numbers.length > 0 ? Math.max(...numbers) : 0; + let mtime = 0; + try { + mtime = statSync(candidate).mtimeMs; + } catch {} + return { version, mtime }; + }; + return [...new Set(candidates)].sort((a, b) => { + const left = score(a); + const right = score(b); + return right.version - left.version || right.mtime - left.mtime || a.localeCompare(b); + }); +} + +function chromeForTestingCandidates() { + const home = homedir(); + const patterns = + platform() === "darwin" + ? [ + path.join( + home, + "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ), + path.join(home, "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + path.join( + home, + "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + ), + ] + : platform() === "win32" + ? [ + path.join( + process.env.LOCALAPPDATA || path.join(home, "AppData/Local"), + "ms-playwright/chromium-*/chrome-win*/chrome.exe", + ), + path.join(home, ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), + ] + : [ + path.join(home, ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + path.join(home, ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ]; + return newestFirst(patterns.flatMap(expandGlob)); +} + +function loadExtensionTestBrowserPath() { + const explicit_candidates = [process.env.CHROME_PATH, platform() === "linux" ? "/usr/bin/chromium" : null].filter( + (candidate): candidate is string => Boolean(candidate), + ); + for (const candidate of explicit_candidates) { + if (existsSync(candidate)) return candidate; + } + const [chrome_for_testing] = chromeForTestingCandidates(); + if (chrome_for_testing) return chrome_for_testing; + throw new Error("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH."); +} + +export { loadExtensionTestBrowserPath }; diff --git a/js/test/helpers.BrowserLauncher.ts b/js/test/helpers.BrowserLauncher.ts deleted file mode 100644 index a2e27a01..00000000 --- a/js/test/helpers.BrowserLauncher.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { once } from "node:events"; -import WebSocket from "ws"; -import { expect } from "vitest"; - -import { resolveCdpWebSocketUrl } from "../src/launcher/BrowserLauncher.js"; - -type Pending = { - resolve: (value: Record) => void; - reject: (error: Error) => void; -}; - -export class CdpSocket { - private nextId = 1; - private pending = new Map(); - private events = new Map[]>(); - - constructor(readonly ws: WebSocket) { - ws.on("message", (data) => { - const message = JSON.parse(data.toString()) as Record; - if (typeof message.id === "number") { - const pending = this.pending.get(message.id); - if (!pending) return; - this.pending.delete(message.id); - if (message.error) pending.reject(new Error(JSON.stringify(message.error))); - else pending.resolve((message.result ?? {}) as Record); - return; - } - if (typeof message.method === "string") { - const events = this.events.get(message.method) ?? []; - events.push(message); - this.events.set(message.method, events); - } - }); - ws.on("close", () => { - for (const [id, pending] of this.pending) { - pending.reject(new Error(`CDP websocket closed while command ${id} was pending`)); - } - this.pending.clear(); - }); - } - - static async connect(endpoint: string) { - const cdp_url = await resolveCdpWebSocketUrl(endpoint, "test cdp endpoint"); - const ws = new WebSocket(cdp_url); - await once(ws, "open"); - return new CdpSocket(ws); - } - - send(method: string, params: Record = {}, sessionId?: string) { - const id = this.nextId++; - this.ws.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })); - return new Promise>((resolve, reject) => { - this.pending.set(id, { resolve, reject }); - }); - } - - async close() { - if (this.ws.readyState === WebSocket.CLOSED) return; - const closed = once(this.ws, "close").catch(() => {}); - this.ws.close(); - await closed; - } -} - -export class PipeCdpSocket { - private nextId = 100; - private pending = new Map(); - private buffer = ""; - - constructor( - readonly pipe_read: NodeJS.ReadableStream, - readonly pipe_write: NodeJS.WritableStream, - ) { - pipe_read.on("data", (chunk: Buffer | string) => { - this.buffer += chunk.toString(); - while (this.buffer.includes("\0")) { - const [raw, ...rest] = this.buffer.split("\0"); - this.buffer = rest.join("\0"); - if (!raw) continue; - const message = JSON.parse(raw) as Record; - if (typeof message.id !== "number") continue; - const pending = this.pending.get(message.id); - if (!pending) continue; - this.pending.delete(message.id); - if (message.error) pending.reject(new Error(JSON.stringify(message.error))); - else pending.resolve((message.result ?? {}) as Record); - } - }); - } - - send(method: string, params: Record = {}, sessionId?: string) { - const id = this.nextId++; - this.pipe_write.write(`${JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })}\0`); - return new Promise>((resolve, reject) => { - this.pending.set(id, { resolve, reject }); - }); - } -} - -export async function expectCdpBrowserSurface(cdp: Pick) { - const version = await cdp.send("Browser.getVersion"); - expect(version.product).toEqual(expect.stringMatching(/Chrome|Chromium/)); - expect(version.protocolVersion).toEqual(expect.any(String)); - - const created = await cdp.send("Target.createTarget", { url: "about:blank#modcdp-launcher-test" }); - expect(created.targetId).toEqual(expect.any(String)); - const targetId = created.targetId as string; - - try { - const attached = await cdp.send("Target.attachToTarget", { targetId, flatten: true }); - expect(attached.sessionId).toEqual(expect.any(String)); - const sessionId = attached.sessionId as string; - await cdp.send("Runtime.enable", {}, sessionId); - const evaluated = await cdp.send( - "Runtime.evaluate", - { expression: "(() => ({ ok: true, value: 42 }))()", returnByValue: true }, - sessionId, - ); - expect(evaluated.result).toMatchObject({ type: "object", value: { ok: true, value: 42 } }); - } finally { - await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); - } -} - -export async function expectHttpEndpointDown(url: string) { - await expect - .poll( - async () => { - try { - await fetch(`${url}/json/version`); - return false; - } catch { - return true; - } - }, - { timeout: 5_000, interval: 100 }, - ) - .toBe(true); -} diff --git a/js/test/test.AutoSessionRouter.ts b/js/test/test.AutoSessionRouter.ts index b5e74144..4d1fbc12 100644 --- a/js/test/test.AutoSessionRouter.ts +++ b/js/test/test.AutoSessionRouter.ts @@ -1,142 +1,119 @@ -import { once } from "node:events"; -import WebSocket from "ws"; -import { expect, test } from "vitest"; - -import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { AutoSessionRouter } from "../src/router/AutoSessionRouter.js"; - -test("AutoSessionRouter rejects pending execution context waiters when a session detaches", async () => { - const router = new AutoSessionRouter( - async () => ({}), - () => 5_000, - ); - const wait = router.waitForExecutionContext("detached-session", { - timeout_ms: 5_000, - }); - - router.recordProtocolEvent( - "Target.attachedToTarget", - { - sessionId: "detached-session", - targetInfo: { targetId: "target-1", type: "page" }, +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_AutoSessionRouter.py +// - ./go/modcdp/router/AutoSessionRouter_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "vitest"; + +import { ModCDPClient } from "../src/index.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); + +test("AutoSessionRouter tracks real target sessions and execution contexts from live CDP events", async () => { + const cdp = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, - null, - ); - router.recordProtocolEvent("Target.detachedFromTarget", { sessionId: "detached-session" }, null); - router.recordProtocolEvent("Runtime.executionContextCreated", { context: { id: 42 } }, "detached-session"); - - await expect(wait).rejects.toThrow( - "Runtime execution context wait cancelled because session detached-session detached.", - ); - expect(router.sessionIdForTarget("target-1")).toBeNull(); - expect(router.execution_contexts.get("detached-session")).toBeUndefined(); -}, 5_000); - -test("AutoSessionRouter bounds detached session guards and clears them when a session reattaches", () => { - const router = new AutoSessionRouter( - async () => ({}), - () => 5_000, - ); - - for (let index = 0; index < 1034; index += 1) { - router.recordProtocolEvent("Target.detachedFromTarget", { sessionId: `detached-session-${index}` }, null); - } - - const detached_sessions = (router as unknown as { detached_sessions: Map }).detached_sessions; - expect(detached_sessions.size).toBeLessThanOrEqual(1024); - - const recent_session_id = "detached-session-1033"; - router.recordProtocolEvent("Runtime.executionContextCreated", { context: { id: 42 } }, recent_session_id); - expect(router.execution_contexts.get(recent_session_id)).toBeUndefined(); - - router.recordProtocolEvent( - "Target.attachedToTarget", - { - sessionId: recent_session_id, - targetInfo: { targetId: "target-reattached", type: "page" }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, }, - null, - ); - router.recordProtocolEvent("Runtime.executionContextCreated", { context: { id: 43 } }, recent_session_id); - - expect(router.sessionIdForTarget("target-reattached")).toBe(recent_session_id); - expect(router.execution_contexts.get(recent_session_id)).toBe(43); -}); + router: { + router_routes: { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }, + }, + }); -test("AutoSessionRouter tracks real target sessions and execution contexts", async () => { - const chrome = await new LocalBrowserLauncher({ - headless: true, - }).launch(); - const ws = new WebSocket(chrome.cdp_url!); - await once(ws, "open"); - let next_id = 1; - const pending = new Map) => void>(); - const router = new AutoSessionRouter( - (method, params = {}, session_id = null) => - send(method, params as Record, session_id) as Promise>, - () => 30_000, - ); + let targetId: string | null = null; + let pendingTargetId: string | null = null; + try { + await cdp.connect(); + const created = await cdp.Target.createTarget({ url: "about:blank#modcdp-auto-session-router" }); + targetId = created.targetId; + await expectEventually(() => { + assert.equal(typeof cdp.router.sessionId_from_targetId.get(targetId!), "string"); + }); + const sessionId = cdp.router.sessionId_from_targetId.get(targetId); + assert.equal(typeof sessionId, "string"); - function send(method: string, params: Record = {}, session_id: string | null = null) { - const id = next_id++; - ws.send( - JSON.stringify({ - id, - method, - params, - ...(session_id ? { sessionId: session_id } : {}), - }), - ); - return new Promise>((resolve, reject) => { - pending.set(id, (message) => { - if (message.error) reject(new Error(JSON.stringify(message.error))); - else resolve((message.result ?? {}) as Record); - }); + const contextPromise = cdp.router.waitForExecutionContext(sessionId, { + timeout_ms: 30_000, }); - } + await cdp.send("Runtime.enable", {}, sessionId); + const contextId = await contextPromise; + assert.equal(typeof contextId, "number"); + assert.equal( + [...cdp.router.contexts.values()].some((context) => context.sessionId === sessionId && context.id === contextId), + true, + ); - ws.on("message", (data) => { - const message = JSON.parse(data.toString()) as Record; - if (typeof message.id === "number") { - pending.get(message.id)?.(message); - pending.delete(message.id); - return; - } - if (typeof message.method !== "string") return; - router.recordProtocolEvent( - message.method, - message.params, - typeof message.sessionId === "string" ? message.sessionId : null, + await cdp.Target.detachFromTarget({ sessionId }); + await expectEventually(() => { + assert.equal(cdp.router.sessionId_from_targetId.get(targetId!), undefined); + }); + assert.equal( + [...cdp.router.contexts.values()].some((context) => context.sessionId === sessionId), + false, ); - }); + await cdp.Target.closeTarget({ targetId }).catch(() => ({})); + targetId = null; - try { - await send("Target.setAutoAttach", { - autoAttach: true, - waitForDebuggerOnStart: false, - flatten: true, + const pendingCreated = await cdp.Target.createTarget({ + url: "about:blank#modcdp-auto-session-router-pending-context", }); - await send("Target.setDiscoverTargets", { discover: true }); - const created = await send("Target.createTarget", { - url: "about:blank#modcdp-auto-session-router", + pendingTargetId = pendingCreated.targetId; + await expectEventually(() => { + assert.equal(typeof cdp.router.sessionId_from_targetId.get(pendingTargetId!), "string"); }); - const target_id = created.targetId as string; - await expect.poll(() => router.sessionIdForTarget(target_id), { timeout: 5_000 }).toEqual(expect.any(String)); - const session_id = router.sessionIdForTarget(target_id)!; - - const context_promise = router.waitForExecutionContext(session_id, { + const pendingSessionId = cdp.router.sessionId_from_targetId.get(pendingTargetId); + assert.equal(typeof pendingSessionId, "string"); + const cancelledContextPromise = cdp.router.waitForExecutionContext(pendingSessionId, { timeout_ms: 30_000, }); - await send("Runtime.enable", {}, session_id); - await expect(context_promise).resolves.toEqual(expect.any(Number)); - expect(router.execution_contexts.get(session_id)).toEqual(expect.any(Number)); - - await send("Target.detachFromTarget", { sessionId: session_id }); - await expect.poll(() => router.sessionIdForTarget(target_id), { timeout: 5_000 }).toBeNull(); - await send("Target.closeTarget", { targetId: target_id }).catch(() => ({})); + const cancelledContextAssertion = assert.rejects( + cancelledContextPromise, + new RegExp(`Runtime execution context wait cancelled because session ${pendingSessionId} detached\\.`), + ); + await cdp.Target.detachFromTarget({ sessionId: pendingSessionId }); + await cancelledContextAssertion; + await expectEventually(() => { + assert.equal(cdp.router.sessionId_from_targetId.get(pendingTargetId!), undefined); + }); + await cdp.Target.closeTarget({ targetId: pendingTargetId }).catch(() => ({})); + pendingTargetId = null; } finally { - ws.close(); - await once(ws, "close").catch(() => {}); - await chrome.close(); + if (targetId) await cdp.Target.closeTarget({ targetId }).catch(() => ({})); + if (pendingTargetId) await cdp.Target.closeTarget({ targetId: pendingTargetId }).catch(() => ({})); + await cdp.close(); } }, 60_000); + +async function expectEventually(assertion: () => void, timeoutMs = 10_000) { + const deadline = Date.now() + timeoutMs; + let lastError: unknown = null; + while (Date.now() < deadline) { + try { + assertion(); + return; + } catch (error) { + lastError = error; + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} diff --git a/js/test/test.BBBrowserLauncher.ts b/js/test/test.BBBrowserLauncher.ts new file mode 100644 index 00000000..cfb70794 --- /dev/null +++ b/js/test/test.BBBrowserLauncher.ts @@ -0,0 +1,112 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_BBBrowserLauncher.py +// - ./go/modcdp/launcher/BBBrowserLauncher_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import { describe, expect, it } from "vitest"; + +import { BBBrowserLauncher } from "../src/launcher/BBBrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; + +const LIVE_BROWSERBASE_TIMEOUT_MS = 120_000; + +describe("BBBrowserLauncher", () => { + it( + "creates, verifies, resumes, and releases a real Browserbase browser session", + { timeout: LIVE_BROWSERBASE_TIMEOUT_MS }, + async () => { + expect( + process.env.BROWSERBASE_API_KEY?.trim(), + "BROWSERBASE_API_KEY is required for live Browserbase tests", + ).toBeTruthy(); + const launcher = new BBBrowserLauncher({ + launcher_bb_timeout: 120, + ...(process.env.BROWSERBASE_REGION ? { launcher_bb_region: process.env.BROWSERBASE_REGION } : {}), + launcher_bb_browser_settings: { + viewport: { width: 900, height: 700 }, + recordSession: false, + }, + launcher_bb_user_metadata: { + modcdp_launcher_test: "BBBrowserLauncher", + }, + }); + const browser = await launcher.launch(); + const cdp = new WSUpstreamTransport({ upstream_ws_cdp_url: browser.cdp_url }); + let resumed: Awaited> | null = null; + const session_id = browser.browserbase_session_id; + + try { + expect(session_id).toEqual(expect.any(String)); + expect(browser.browserbase_session_url).toContain(session_id); + expect(browser.cdp_url).toEqual(expect.stringMatching(/^wss:\/\//)); + await cdp.connect(); + await expectCdpBrowserSurface(cdp); + + const retrieved = await retrieveBrowserbaseSession(session_id!); + expect(retrieved.id).toBe(session_id); + expect(retrieved.status).toBe("RUNNING"); + + resumed = await new BBBrowserLauncher({ + launcher_bb_session_id: session_id, + launcher_bb_close_session_on_close: false, + }).launch(); + expect(resumed.browserbase_session_id).toBe(session_id); + expect(resumed.cdp_url).toEqual(expect.stringMatching(/^wss:\/\//)); + await expectCdpBrowserSurface(cdp); + } finally { + await cdp.close(); + await resumed?.close(); + await browser.close(); + await browser.close(); + } + + await expect + .poll(async () => (await retrieveBrowserbaseSession(session_id!)).status, { timeout: 30_000, interval: 1_000 }) + .not.toBe("RUNNING"); + }, + ); +}); + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep the setup semantics above 1:1 with translated tests; helpers here only use real ModCDP transports and real Browserbase APIs. +async function expectCdpBrowserSurface(cdp: WSUpstreamTransport) { + const version = await cdp.send("Browser.getVersion"); + expect(version.product).toEqual(expect.stringMatching(/Chrome|Chromium/)); + expect(version.protocolVersion).toEqual(expect.any(String)); + + const created = await cdp.send("Target.createTarget", { url: "about:blank#modcdp-launcher-test" }); + expect(created.targetId).toEqual(expect.any(String)); + const targetId = created.targetId as string; + + try { + const attached = await cdp.send("Target.attachToTarget", { targetId, flatten: true }); + expect(attached.sessionId).toEqual(expect.any(String)); + const sessionId = attached.sessionId as string; + await cdp.send("Runtime.enable", {}, sessionId); + const evaluated = await cdp.send( + "Runtime.evaluate", + { expression: "(() => ({ ok: true, value: 42 }))()", returnByValue: true }, + sessionId, + ); + expect(evaluated.result).toMatchObject({ type: "object", value: { ok: true, value: 42 } }); + } finally { + await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); + } +} + +function browserbaseApiUrl(pathname: string) { + return new URL( + pathname, + `${(process.env.BROWSERBASE_BASE_URL ?? "https://api.browserbase.com").replace(/\/$/, "")}/`, + ); +} + +async function retrieveBrowserbaseSession(session_id: string) { + const response = await fetch(browserbaseApiUrl(`/v1/sessions/${session_id}`), { + headers: { "x-bb-api-key": process.env.BROWSERBASE_API_KEY! }, + }); + expect(response.status).toBeGreaterThanOrEqual(200); + expect(response.status).toBeLessThan(300); + return (await response.json()) as Record; +} diff --git a/js/test/test.BBBrowserExtensionInjector.ts b/js/test/test.BBExtensionInjector.ts similarity index 58% rename from js/test/test.BBBrowserExtensionInjector.ts rename to js/test/test.BBExtensionInjector.ts index daa59b56..0cbb6b1a 100644 --- a/js/test/test.BBBrowserExtensionInjector.ts +++ b/js/test/test.BBExtensionInjector.ts @@ -1,14 +1,20 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_BBExtensionInjector.py +// - ./go/modcdp/injector/BBExtensionInjector_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, it } from "vitest"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; +import { ModCDPClient } from "../src/index.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); -describe("BBBrowserExtensionInjector", () => { +describe("BBExtensionInjector", () => { it( "uploads the real extension and launches a Browserbase browser with it installed", { timeout: 180_000 }, @@ -17,15 +23,13 @@ describe("BBBrowserExtensionInjector", () => { const cdp = new ModCDPClient({ launcher: { launcher_mode: "bb", - launcher_options: { - timeout: 120, - ...(process.env.BROWSERBASE_REGION ? { region: process.env.BROWSERBASE_REGION } : {}), - }, + launcher_bb_timeout: 120, + ...(process.env.BROWSERBASE_REGION ? { launcher_bb_region: process.env.BROWSERBASE_REGION } : {}), }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "inject", - injector_extension_path: EXTENSION_PATH, + injector_mode: "bb", + injector_bb_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, @@ -34,7 +38,7 @@ describe("BBBrowserExtensionInjector", () => { try { await cdp.connect(); assert.equal(cdp.connect_timing?.injector_source, "bb"); - assert.equal(typeof cdp.extension_id, "string"); + assert.equal(typeof cdp.injector?.extension_id, "string"); const service_worker_url = await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')", }); diff --git a/js/test/test.BorrowExtensionInjector.ts b/js/test/test.BorrowExtensionInjector.ts new file mode 100644 index 00000000..306844cf --- /dev/null +++ b/js/test/test.BorrowExtensionInjector.ts @@ -0,0 +1,60 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// Reason: not needed by Stagehand. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "vitest"; + +import { ModCDPClient } from "../src/index.js"; +import { extension_injector_constructors } from "../src/client/ModCDPClient.js"; +import { BorrowExtensionInjector } from "../src/injector/BorrowExtensionInjector.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); +extension_injector_constructors.set("borrow", BorrowExtensionInjector); + +test("BorrowExtensionInjector bootstraps ModCDP inside a live extension service worker", async () => { + const owner = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + }); + let cdp: ModCDPClient | null = null; + + try { + await owner.connect(); + cdp = new ModCDPClient({ + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + injector: { + injector_mode: "borrow", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + } as any, + }); + await cdp.connect(); + assert.equal(cdp.connect_timing?.injector_source, "borrow"); + assert.equal(cdp.injector?.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf"); + assert.equal( + await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')" }), + "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", + ); + } finally { + await cdp?.close(); + await owner.close(); + } +}, 60_000); diff --git a/js/test/test.BorrowedExtensionInjector.ts b/js/test/test.BorrowedExtensionInjector.ts deleted file mode 100644 index c4865b34..00000000 --- a/js/test/test.BorrowedExtensionInjector.ts +++ /dev/null @@ -1,49 +0,0 @@ -import assert from "node:assert/strict"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { test } from "vitest"; - -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); - -test("BorrowedExtensionInjector bootstraps ModCDP inside a live extension service worker", async () => { - const owner = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { headless: true }, - }, - upstream: { upstream_mode: "ws" }, - injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - }); - const cdp = new ModCDPClient({ - launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws" }, - injector: { - injector_mode: "borrow", - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - }); - - try { - await owner.connect(); - cdp.upstream.upstream_cdp_url = owner.cdp_url; - await cdp.connect(); - assert.equal(cdp.connect_timing?.injector_source, "borrowed"); - assert.equal(cdp.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf"); - assert.equal( - await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')" }), - "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", - ); - } finally { - await cdp.close(); - await owner.close(); - } -}, 60_000); diff --git a/js/test/test.BrowserLauncher.ts b/js/test/test.BrowserLauncher.ts index 60076204..ff339004 100644 --- a/js/test/test.BrowserLauncher.ts +++ b/js/test/test.BrowserLauncher.ts @@ -1,33 +1,36 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_BrowserLauncher.py +// - ./go/modcdp/launcher/BrowserLauncher_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import { describe, expect, it } from "vitest"; import { BrowserLauncher } from "../src/launcher/BrowserLauncher.js"; describe("BrowserLauncher", () => { - it("merges launch config and exposes transport/injector config without launching a browser", async () => { + it("merges config and exposes upstream config", async () => { const launcher = new BrowserLauncher({ - cdp_url: "ws://127.0.0.1:9222/devtools/browser/initial", - user_data_dir: "/tmp/modcdp-browser-launcher", - browserbase_api_key: "test-key", - injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - args: ["--load-extension=/tmp/args-one"], - extra_args: ["--load-extension=/tmp/one"], + launcher_remote_cdp_url: "ws://127.0.0.1:9222/devtools/browser/initial", + launcher_local_user_data_dir: "/tmp/modcdp-browser-launcher", }); launcher.update({ - cdp_url: "ws://127.0.0.1:9222/devtools/browser/updated", - args: ["--load-extension=/tmp/args-two", "--lang=en-US"], - extra_args: ["--load-extension=/tmp/two", "--window-size=900,700"], + launcher_remote_cdp_url: "ws://127.0.0.1:9222/devtools/browser/updated", }); - expect(launcher.options.args).toEqual(["--lang=en-US", "--load-extension=/tmp/args-one,/tmp/args-two"]); - expect(launcher.options.extra_args).toEqual(["--window-size=900,700", "--load-extension=/tmp/one,/tmp/two"]); - expect(launcher.getTransportConfig()).toMatchObject({ - cdp_url: "ws://127.0.0.1:9222/devtools/browser/updated", - user_data_dir: "/tmp/modcdp-browser-launcher", + expect(launcher.configForUpstream().upstream_ws_cdp_url).toBe("ws://127.0.0.1:9222/devtools/browser/updated"); + expect(launcher.config.launcher_local_user_data_dir).toEqual("/tmp/modcdp-browser-launcher"); + await expect(launcher.launch()).rejects.toThrow("BrowserLauncher.launch is not implemented."); + }); + + it("carries remote CDP config separately from launch args", async () => { + const launcher = new BrowserLauncher({ + launcher_remote_cdp_url: "ws://127.0.0.1:9222/devtools/browser/initial", }); - expect(launcher.getInjectorConfig()).toMatchObject({ - injector_browserbase_api_key: "test-key", - injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + launcher.update({ + launcher_remote_cdp_url: "ws://127.0.0.1:9222/devtools/browser/updated", }); - await expect(launcher.launch()).rejects.toThrow("BrowserLauncher.launch is not implemented."); + + expect(launcher.config.launcher_remote_cdp_url).toEqual("ws://127.0.0.1:9222/devtools/browser/updated"); }); }); diff --git a/js/test/test.BrowserTargetUpstreamTransport.ts b/js/test/test.BrowserTargetUpstreamTransport.ts new file mode 100644 index 00000000..e348d8ad --- /dev/null +++ b/js/test/test.BrowserTargetUpstreamTransport.ts @@ -0,0 +1,192 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// BrowserTargetUpstreamTransport: TS-only browser-target upstream transport coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "vitest"; + +import { ModCDPClient } from "../src/index.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); + +test("loopback browser-target upstream routes commands, events, and topology through one transport", async () => { + const owner = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + }); + await owner.connect(); + + const cdp = new ModCDPClient({ + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + injector: { + injector_mode: "discover", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + server_config: { + upstream: { upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + router: { router_routes: { "*.*": "loopback_cdp" } }, + }, + }); + + let targetId: string | null = null; + try { + await cdp.connect(); + const created = (await cdp.send("Target.createTarget", { url: topologyTestUrl("loopback") })) as { + targetId?: string; + }; + assert.equal(typeof created.targetId, "string"); + targetId = created.targetId; + await assertPageMarker(cdp, targetId, "loopback"); + + const topology = await cdp.Mod.getTopology({ targetId }); + assertTopology(topology, targetId); + assert.equal( + Object.values(topology.targets).some((target) => target.sessionId), + true, + ); + } finally { + if (targetId) await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); + await cdp.close(); + await owner.close(); + } +}, 90_000); + +test("chrome.debugger browser-target upstream routes commands, events, and topology through one transport", async () => { + const owner = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + }); + await owner.connect(); + + const cdp = new ModCDPClient({ + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + injector: { + injector_mode: "discover", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + server_config: { + router: { router_routes: { "*.*": "chromedebugger" } }, + }, + }); + + let targetId: string | null = null; + try { + await cdp.connect(); + const created = (await cdp.send("Target.createTarget", { url: topologyTestUrl("debugger") })) as { + targetId?: string; + }; + assert.equal(typeof created.targetId, "string"); + targetId = created.targetId; + await assertPageMarker(cdp, targetId, "debugger"); + + const topology = await cdp.Mod.getTopology({ targetId }); + assertTopology(topology, targetId); + assert.ok(topology.targets[targetId], "topology should include the created target"); + } finally { + if (targetId) await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); + await cdp.close(); + await owner.close(); + } +}, 90_000); + +async function assertPageMarker(cdp: ModCDPClient, targetId: string, label: string) { + await assert.doesNotReject(async () => { + await expectEventually(async () => { + const evaluated = (await cdp.send("Runtime.evaluate", { + targetId, + expression: "document.body?.dataset.modcdpMarker", + returnByValue: true, + })) as { result?: { value?: unknown } }; + assert.equal(evaluated.result?.value, label); + }); + }); +} + +function assertTopology(topology: Awaited>, targetId: string) { + assert.equal(typeof topology.objectGroup, "string"); + assert.equal(typeof topology.rootFrameId, "string"); + assert.ok(topology.frames[topology.rootFrameId], "topology should include the root frame"); + assert.equal(topology.frames[topology.rootFrameId]?.targetId, targetId); + assert.ok(topology.targets[targetId], "topology should include the requested page target"); + + const contexts = Object.values(topology.contexts); + assert.ok( + contexts.some((context) => context.frameId === topology.rootFrameId && context.world === "piercer"), + "topology should include a piercer execution context for the root frame", + ); + + const roots = Object.values(topology.roots); + assert.ok( + roots.some((root) => root.kind === "document" && root.frameId === topology.rootFrameId), + "topology should include the root document", + ); + assert.ok( + roots.some((root) => root.kind === "shadow" && root.mode === "open"), + "topology should include open shadow roots", + ); + assert.ok( + roots.some((root) => root.kind === "shadow" && root.mode === "closed"), + "topology should include closed shadow roots", + ); +} + +function topologyTestUrl(label: string) { + const html = ` + + +
+
+ + + + `; + return `data:text/html,${encodeURIComponent(html)}`; +} + +async function expectEventually(assertion: () => Promise | void, timeoutMs = 10_000) { + const deadline = Date.now() + timeoutMs; + let lastError: unknown = null; + while (Date.now() < deadline) { + try { + await assertion(); + return; + } catch (error) { + lastError = error; + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} diff --git a/js/test/test.BrowserbaseBrowserLauncher.ts b/js/test/test.BrowserbaseBrowserLauncher.ts deleted file mode 100644 index abe9260d..00000000 --- a/js/test/test.BrowserbaseBrowserLauncher.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { BrowserbaseBrowserLauncher } from "../src/launcher/BrowserbaseBrowserLauncher.js"; -import { CdpSocket, expectCdpBrowserSurface } from "./helpers.BrowserLauncher.js"; - -const LIVE_BROWSERBASE_TIMEOUT_MS = 120_000; - -function browserbaseApiUrl(pathname: string) { - return new URL( - pathname, - `${(process.env.BROWSERBASE_BASE_URL ?? "https://api.browserbase.com").replace(/\/$/, "")}/`, - ); -} - -async function retrieveBrowserbaseSession(session_id: string) { - const response = await fetch(browserbaseApiUrl(`/v1/sessions/${session_id}`), { - headers: { "x-bb-api-key": process.env.BROWSERBASE_API_KEY! }, - }); - expect(response.status).toBeGreaterThanOrEqual(200); - expect(response.status).toBeLessThan(300); - return (await response.json()) as Record; -} - -describe("BrowserbaseBrowserLauncher", () => { - it( - "creates, verifies, resumes, and releases a real Browserbase browser session", - { timeout: LIVE_BROWSERBASE_TIMEOUT_MS }, - async () => { - expect( - process.env.BROWSERBASE_API_KEY?.trim(), - "BROWSERBASE_API_KEY is required for live Browserbase tests", - ).toBeTruthy(); - const launcher = new BrowserbaseBrowserLauncher({ - timeout: 120, - ...(process.env.BROWSERBASE_REGION ? { region: process.env.BROWSERBASE_REGION } : {}), - browserbase_browser_settings: { - viewport: { width: 900, height: 700 }, - recordSession: false, - }, - browserbase_user_metadata: { - modcdp_launcher_test: "BrowserbaseBrowserLauncher", - }, - }); - const browser = await launcher.launch(); - let resumed: Awaited> | null = null; - let cdp: CdpSocket | null = null; - const session_id = browser.browserbase_session_id; - - try { - expect(session_id).toEqual(expect.any(String)); - expect(browser.browserbase_session_url).toContain(session_id); - expect(browser.cdp_url).toEqual(expect.stringMatching(/^wss:\/\//)); - cdp = await CdpSocket.connect(browser.cdp_url!); - await expectCdpBrowserSurface(cdp); - - const retrieved = await retrieveBrowserbaseSession(session_id!); - expect(retrieved.id).toBe(session_id); - expect(retrieved.status).toBe("RUNNING"); - - resumed = await new BrowserbaseBrowserLauncher({ - browserbase_session_id: session_id, - browserbase_close_session_on_close: false, - }).launch(); - expect(resumed.browserbase_session_id).toBe(session_id); - expect(resumed.cdp_url).toEqual(expect.stringMatching(/^wss:\/\//)); - await expectCdpBrowserSurface(cdp); - } finally { - await cdp?.close(); - await resumed?.close(); - await browser.close(); - await browser.close(); - } - - await expect - .poll(async () => (await retrieveBrowserbaseSession(session_id!)).status, { timeout: 30_000, interval: 1_000 }) - .not.toBe("RUNNING"); - }, - ); -}); diff --git a/js/test/test.CDPExtensionInjector.ts b/js/test/test.CDPExtensionInjector.ts new file mode 100644 index 00000000..0d5e177e --- /dev/null +++ b/js/test/test.CDPExtensionInjector.ts @@ -0,0 +1,27 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_CDPExtensionInjector.py +// - ./go/modcdp/injector/CDPExtensionInjector_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { test } from "vitest"; + +import { CDPExtensionInjector } from "../src/injector/CDPExtensionInjector.js"; + +test("CDPExtensionInjector prepares the default packaged extension zip", async () => { + const injector = new CDPExtensionInjector(); + + try { + await injector.prepare(); + const unpacked_extension_path = (injector as unknown as { unpacked_extension_path?: string | null }) + .unpacked_extension_path; + assert.equal(typeof unpacked_extension_path, "string"); + assert.match(unpacked_extension_path!, /modcdp-extension-/); + assert.equal(existsSync(path.join(unpacked_extension_path!, "manifest.json")), true); + } finally { + await injector.close(); + } +}); diff --git a/js/test/test.CDPTypes_payload_schema_normalization.ts b/js/test/test.CDPTypes_payload_schema_normalization.ts new file mode 100644 index 00000000..5541cf83 --- /dev/null +++ b/js/test/test.CDPTypes_payload_schema_normalization.ts @@ -0,0 +1,63 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_CDPTypes_payload_schema_normalization.py +// - ./go/modcdp/client/CDPTypes_payload_schema_normalization_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { test } from "vitest"; +import { z } from "zod"; + +import { CDPTypes } from "../src/types/CDPTypes.js"; +import { ModCDPConfigureParamsSchema, validateZodSchema } from "../src/types/modcdp.js"; + +test("validateZodSchema accepts empty zod shapes", () => { + const schema = validateZodSchema({}); + assert.deepEqual(schema?.parse({ value: 1 }), { value: 1 }); +}); + +test("validateZodSchema rejects unsupported schema specs", () => { + assert.throws(() => validateZodSchema("not-a-schema" as never), /Unsupported payload schema/); +}); + +test("validateZodSchema accepts non-empty zod shapes", () => { + const schema = validateZodSchema({ value: z.string() }); + assert.deepEqual(schema?.parse({ value: "ok", extra: true }), { value: "ok", extra: true }); +}); + +test("CDPTypes serializes builtin Mod command schemas through the same wire path", () => { + const types = new CDPTypes(); + + for (const name of ["Mod.configure", "Mod.addCustomCommand", "Mod.addCustomEvent"]) { + const registration = types.customCommandWireRegistrations().find((command) => command.name === name); + assert.notEqual(registration, undefined); + assert.equal(typeof registration?.params_schema, "object"); + assert.equal(typeof registration?.result_schema, "object"); + } + + const configure_registration = types + .customCommandWireRegistrations() + .find((command) => command.name === "Mod.configure"); + const configure_schema = validateZodSchema(configure_registration?.params_schema); + const parsed_configure_params = ModCDPConfigureParamsSchema.parse( + configure_schema?.parse({ + client_config: { client_hydrate_aliases: false }, + downstream: { + downstream_client_timeout_ms: 1234, + downstream_close_browser_on_disconnect: true, + }, + }), + ); + assert.equal(parsed_configure_params?.client_config.client_hydrate_aliases, false); + assert.equal(parsed_configure_params?.downstream.downstream_client_timeout_ms, 1234); + assert.equal(parsed_configure_params?.downstream.downstream_close_browser_on_disconnect, true); + assert.throws( + () => + configure_schema?.parse({ + downstream: { + closeBrowser: "not allowed over the wire", + }, + }), + /Unrecognized key/, + ); +}); diff --git a/js/test/test.LocalBrowserLaunchExtensionInjector.ts b/js/test/test.CLIExtensionInjector.ts similarity index 52% rename from js/test/test.LocalBrowserLaunchExtensionInjector.ts rename to js/test/test.CLIExtensionInjector.ts index 70800ba7..85985e8f 100644 --- a/js/test/test.LocalBrowserLaunchExtensionInjector.ts +++ b/js/test/test.CLIExtensionInjector.ts @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_CLIExtensionInjector.py +// - ./go/modcdp/injector/CLIExtensionInjector_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; @@ -6,10 +12,15 @@ import { fileURLToPath } from "node:url"; import { test } from "vitest"; import { DEFAULT_MODCDP_EXTENSION_ID } from "../src/injector/ExtensionInjector.js"; -import { LocalBrowserLaunchExtensionInjector } from "../src/injector/LocalBrowserLaunchExtensionInjector.js"; +import { CLIExtensionInjector } from "../src/injector/CLIExtensionInjector.js"; +import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const DOES_NOT_EXIST_EXTENSION_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); function crc32(data: Buffer) { let crc = 0xffffffff; @@ -48,12 +59,12 @@ function storedZipEntry(name: string, data: Buffer) { return Buffer.concat([local, name_buffer, data, central, name_buffer, end]); } -test("LocalBrowserLaunchExtensionInjector rejects zip entries outside extraction directory", async () => { +test("CLIExtensionInjector rejects zip entries outside extraction directory", async () => { const temp_dir = mkdtempSync(path.join(os.tmpdir(), "modcdp-bad-zip-")); const zip_path = path.join(temp_dir, "extension.zip"); writeFileSync(zip_path, storedZipEntry("../evil.txt", Buffer.from("evil"))); - const injector = new LocalBrowserLaunchExtensionInjector({ - injector_extension_path: zip_path, + const injector = new CLIExtensionInjector({ + injector_cli_extension_path: zip_path, }); try { @@ -65,9 +76,9 @@ test("LocalBrowserLaunchExtensionInjector rejects zip entries outside extraction } }); -test("LocalBrowserLaunchExtensionInjector prepares an unpacked extension directory for --load-extension", async () => { - const injector = new LocalBrowserLaunchExtensionInjector({ - injector_extension_path: EXTENSION_PATH, +test("CLIExtensionInjector prepares an unpacked extension directory for --load-extension", async () => { + const injector = new CLIExtensionInjector({ + injector_cli_extension_path: EXTENSION_PATH, }); try { @@ -77,17 +88,15 @@ test("LocalBrowserLaunchExtensionInjector prepares an unpacked extension directo assert.equal(typeof unpacked_extension_path, "string"); assert.notEqual(unpacked_extension_path, EXTENSION_PATH); assert.equal(existsSync(path.join(unpacked_extension_path!, "manifest.json")), true); - assert.deepEqual(injector.getLauncherConfig(), { - extra_args: [`--load-extension=${unpacked_extension_path}`], - }); - assert.equal(injector.options.injector_extension_id, DEFAULT_MODCDP_EXTENSION_ID); + assert.deepEqual(injector.extra_args, [`--load-extension=${unpacked_extension_path}`]); + assert.equal(injector.config.injector_service_worker_extension_id, DEFAULT_MODCDP_EXTENSION_ID); } finally { await injector.close(); } }); -test("LocalBrowserLaunchExtensionInjector prepares the default extension zip for --load-extension", async () => { - const injector = new LocalBrowserLaunchExtensionInjector(); +test("CLIExtensionInjector prepares the default extension zip for --load-extension", async () => { + const injector = new CLIExtensionInjector(); try { await injector.prepare(); @@ -96,36 +105,50 @@ test("LocalBrowserLaunchExtensionInjector prepares the default extension zip for assert.equal(typeof unpacked_extension_path, "string"); assert.match(unpacked_extension_path!, /modcdp-extension-/); assert.equal(existsSync(path.join(unpacked_extension_path!, "manifest.json")), true); - assert.deepEqual(injector.getLauncherConfig(), { - extra_args: [`--load-extension=${unpacked_extension_path}`], - }); - assert.equal(injector.options.injector_extension_id, DEFAULT_MODCDP_EXTENSION_ID); + assert.deepEqual(injector.extra_args, [`--load-extension=${unpacked_extension_path}`]); + assert.equal(injector.config.injector_service_worker_extension_id, DEFAULT_MODCDP_EXTENSION_ID); } finally { await injector.close(); } }); -test("LocalBrowserLaunchExtensionInjector returns immediately when the launched extension target is absent", async () => { - const methods: string[] = []; - const injector = new LocalBrowserLaunchExtensionInjector({ - injector_extension_path: EXTENSION_PATH, +test("CLIExtensionInjector returns null when a trusted does-not-exist extension id is absent in a real browser", async () => { + const injector = new CLIExtensionInjector({ + injector_cli_extension_path: EXTENSION_PATH, + injector_cli_extension_id: DOES_NOT_EXIST_EXTENSION_ID, injector_trust_service_worker_target: true, - send: async (method) => { - methods.push(method); - if (method === "Target.getTargets") return { targetInfos: [] }; - throw new Error(`unexpected ${method}`); - }, + injector_service_worker_ready_timeout_ms: 250, + injector_service_worker_poll_interval_ms: 25, }); + const launcher = new LocalBrowserLauncher({ + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }); + const upstream = new WSUpstreamTransport(); try { await injector.prepare(); - const started_at = performance.now(); + launcher.update(injector.configForLauncher()); + await launcher.launch(); + upstream.update(launcher.configForUpstream()); + await upstream.connect(); + injector.update({ send: upstream.send.bind(upstream) }); + + const targets = (await upstream.send("Target.getTargets", {})) as { + targetInfos?: { type?: string; url?: string }[]; + }; + assert.equal( + targets.targetInfos?.some((target) => + target.url?.startsWith(`chrome-extension://${DOES_NOT_EXIST_EXTENSION_ID}/`), + ), + false, + ); + const result = await injector.inject(); - const elapsed_ms = performance.now() - started_at; assert.equal(result, null); - assert.deepEqual(methods, ["Target.getTargets"]); - assert.equal(elapsed_ms < 200, true, `inject() took ${elapsed_ms}ms`); } finally { + await upstream.close(); + await launcher.close(); await injector.close(); } -}); +}, 60_000); diff --git a/js/test/test.DiscoverExtensionInjector.ts b/js/test/test.DiscoverExtensionInjector.ts new file mode 100644 index 00000000..15a1bc06 --- /dev/null +++ b/js/test/test.DiscoverExtensionInjector.ts @@ -0,0 +1,58 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_DiscoverExtensionInjector.py +// - ./go/modcdp/injector/DiscoverExtensionInjector_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "vitest"; + +import { ModCDPClient } from "../src/index.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); + +test("DiscoverExtensionInjector attaches to an already-loaded real ModCDP extension", async () => { + const owner = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + }); + let cdp: ModCDPClient | null = null; + + try { + await owner.connect(); + cdp = new ModCDPClient({ + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + injector: { + injector_mode: "discover", + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + }); + await cdp.connect(); + assert.equal(cdp.connect_timing?.injector_source, "discover"); + assert.equal(cdp.injector?.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf"); + assert.equal( + await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')" }), + "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", + ); + } finally { + await cdp?.close(); + await owner.close(); + } +}, 60_000); diff --git a/js/test/test.DiscoveredExtensionInjector.ts b/js/test/test.DiscoveredExtensionInjector.ts deleted file mode 100644 index 9c864e2a..00000000 --- a/js/test/test.DiscoveredExtensionInjector.ts +++ /dev/null @@ -1,49 +0,0 @@ -import assert from "node:assert/strict"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { test } from "vitest"; - -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); - -test("DiscoveredExtensionInjector attaches to an already-loaded real ModCDP extension", async () => { - const owner = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { headless: true }, - }, - upstream: { upstream_mode: "ws" }, - injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - }); - const cdp = new ModCDPClient({ - launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws" }, - injector: { - injector_mode: "discover", - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - }); - - try { - await owner.connect(); - cdp.upstream.upstream_cdp_url = owner.cdp_url; - await cdp.connect(); - assert.equal(cdp.connect_timing?.injector_source, "discovered"); - assert.equal(cdp.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf"); - assert.equal( - await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')" }), - "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", - ); - } finally { - await cdp.close(); - await owner.close(); - } -}, 60_000); diff --git a/js/test/test.DownstreamTransportSet.ts b/js/test/test.DownstreamTransportSet.ts new file mode 100644 index 00000000..60b626ba --- /dev/null +++ b/js/test/test.DownstreamTransportSet.ts @@ -0,0 +1,74 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// DownstreamTransportSet: TS-only service-worker downstream transport set coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { test } from "vitest"; + +import { DownstreamTransportSet } from "../src/transport/DownstreamTransportSet.js"; +import { ModCDPConfigureParamsSchema } from "../src/types/modcdp.js"; + +test("DownstreamTransportSet owns downstream client lease expiry", async () => { + let close_count = 0; + const downstream = new DownstreamTransportSet({ + downstream_client_timeout_ms: 10, + downstream_close_browser_on_disconnect: true, + closeBrowser: () => { + close_count += 1; + }, + }); + + assert.equal(downstream.hasClientLease(), false); + downstream.touchClientLease(); + assert.equal(downstream.hasClientLease(), true); + + await new Promise((resolve) => setTimeout(resolve, 30)); + + assert.equal(downstream.hasClientLease(), false); + assert.equal(close_count, 1); +}); + +test("DownstreamTransportSet clears downstream client lease", async () => { + let close_count = 0; + const downstream = new DownstreamTransportSet({ + downstream_client_timeout_ms: 10, + downstream_close_browser_on_disconnect: true, + closeBrowser: () => { + close_count += 1; + }, + }); + + downstream.touchClientLease(); + assert.equal(downstream.clearClientLease(), true); + assert.equal(downstream.clearClientLease(), false); + + await new Promise((resolve) => setTimeout(resolve, 30)); + + assert.equal(downstream.hasClientLease(), false); + assert.equal(close_count, 0); +}); + +test("DownstreamTransportSet preserves live closeBrowser handler when wire config omits it", async () => { + let close_count = 0; + const downstream = new DownstreamTransportSet({ + downstream_client_timeout_ms: 10, + downstream_close_browser_on_disconnect: true, + closeBrowser: () => { + close_count += 1; + }, + }); + const parsed_config = ModCDPConfigureParamsSchema.parse({ + downstream: { + downstream_client_timeout_ms: 10, + downstream_close_browser_on_disconnect: true, + }, + }); + + downstream.update(parsed_config.downstream); + downstream.touchClientLease(); + + await new Promise((resolve) => setTimeout(resolve, 30)); + + assert.equal(close_count, 1); +}); diff --git a/js/test/test.ExtensionInjector.ts b/js/test/test.ExtensionInjector.ts index 85f3bb8e..81b463e9 100644 --- a/js/test/test.ExtensionInjector.ts +++ b/js/test/test.ExtensionInjector.ts @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ExtensionInjector.py +// - ./go/modcdp/injector/ExtensionInjector_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { test } from "vitest"; @@ -11,13 +17,13 @@ class ProbeExtensionInjector extends ExtensionInjector { test("ExtensionInjector owns shared injector config", async () => { const injector = new ProbeExtensionInjector({ - injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + injector_service_worker_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], }); try { - assert.deepEqual(injector.getTransportConfig(), { injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }); - assert.deepEqual(injector.getLauncherConfig(), {}); + assert.equal(injector.config.injector_service_worker_extension_id, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + assert.deepEqual(injector.extra_args, []); assert.equal( injector.matches({ type: "service_worker", diff --git a/js/test/test.ExtensionsLoadUnpackedInjector.ts b/js/test/test.ExtensionsLoadUnpackedInjector.ts deleted file mode 100644 index 65f3c2fd..00000000 --- a/js/test/test.ExtensionsLoadUnpackedInjector.ts +++ /dev/null @@ -1,21 +0,0 @@ -import assert from "node:assert/strict"; -import { existsSync } from "node:fs"; -import path from "node:path"; -import { test } from "vitest"; - -import { ExtensionsLoadUnpackedInjector } from "../src/injector/ExtensionsLoadUnpackedInjector.js"; - -test("ExtensionsLoadUnpackedInjector prepares the default packaged extension zip", async () => { - const injector = new ExtensionsLoadUnpackedInjector(); - - try { - await injector.prepare(); - const unpacked_extension_path = (injector as unknown as { unpacked_extension_path?: string | null }) - .unpacked_extension_path; - assert.equal(typeof unpacked_extension_path, "string"); - assert.match(unpacked_extension_path!, /modcdp-extension-/); - assert.equal(existsSync(path.join(unpacked_extension_path!, "manifest.json")), true); - } finally { - await injector.close(); - } -}); diff --git a/js/test/test.LocalBrowserLauncher.ts b/js/test/test.LocalBrowserLauncher.ts index 2924ba77..60f43bac 100644 --- a/js/test/test.LocalBrowserLauncher.ts +++ b/js/test/test.LocalBrowserLauncher.ts @@ -1,15 +1,16 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_LocalBrowserLauncher.py +// - ./go/modcdp/launcher/LocalBrowserLauncher_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import { mkdtemp, rm, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { - CdpSocket, - expectCdpBrowserSurface, - expectHttpEndpointDown, - PipeCdpSocket, -} from "./helpers.BrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; const LIVE_BROWSER_TIMEOUT_MS = 60_000; @@ -20,63 +21,30 @@ describe("LocalBrowserLauncher", () => { }); it( - "launches a real browser over a chosen CDP port and honors launch options", + "launches a real browser over a chosen CDP port and explicit profile dir", { timeout: LIVE_BROWSER_TIMEOUT_MS }, async () => { const userDataDir = await mkdtemp(path.join(tmpdir(), "modcdp-local-profile-")); const port = await LocalBrowserLauncher.freePort(); const chrome = await new LocalBrowserLauncher({ - headless: true, - chrome_ready_timeout_ms: 45_000, - chrome_ready_poll_interval_ms: 50, + launcher_local_headless: true, + launcher_local_chrome_ready_timeout_ms: 45_000, + launcher_local_chrome_ready_poll_interval_ms: 50, }).launch({ - port, - user_data_dir: userDataDir, - extra_args: ["--window-size=900,700"], + launcher_local_cdp_listen_port: port, + launcher_local_user_data_dir: userDataDir, }); - let cdp: CdpSocket | null = null; + const cdp = new WSUpstreamTransport({ upstream_ws_cdp_url: chrome.cdp_url }); try { - expect(chrome.port).toBe(port); + expect(chrome.cdp_listen_port).toBe(port); expect(chrome.cdp_url).toEqual(expect.stringMatching(new RegExp(`^ws://127\\.0\\.0\\.1:${port}/`))); expect(chrome.profile_dir).toBe(userDataDir); - expect((chrome.proc as { spawnargs?: string[] }).spawnargs ?? []).toEqual( - expect.arrayContaining([ - "--enable-unsafe-extension-debugging", - "--remote-allow-origins=*", - "--no-first-run", - "--no-default-browser-check", - "--disable-default-apps", - "--disable-dev-shm-usage", - "--disable-background-networking", - "--disable-backgrounding-occluded-windows", - "--disable-renderer-backgrounding", - "--disable-background-timer-throttling", - "--disable-sync", - "--disable-features=DisableLoadExtensionCommandLineSwitch", - "--password-store=basic", - "--window-size=900,700", - ]), - ); - if (process.platform === "linux") { - expect((chrome.proc as { spawnargs?: string[] }).spawnargs ?? []).toContain("--no-sandbox"); - } else { - expect((chrome.proc as { spawnargs?: string[] }).spawnargs ?? []).not.toContain("--no-sandbox"); - } await expect(stat(userDataDir)).resolves.toBeTruthy(); - cdp = await CdpSocket.connect(chrome.cdp_url!); - const systemInfo = await cdp.send("SystemInfo.getInfo"); - const commandLine = systemInfo.commandLine; - expect(commandLine).toEqual(expect.any(String)); - expect(commandLine).toContain("--window-size=900,700"); - if (process.platform === "linux") { - expect(commandLine).toContain("--no-sandbox"); - } else { - expect(commandLine).not.toContain("--no-sandbox"); - } + await cdp.connect(); await expectCdpBrowserSurface(cdp); } finally { - await cdp?.close(); + await cdp.close(); await chrome.close(); await chrome.close(); await expectHttpEndpointDown(`http://127.0.0.1:${port}`); @@ -86,74 +54,17 @@ describe("LocalBrowserLauncher", () => { }, ); - it( - "launches a real browser over remote-debugging-pipe and speaks CDP over the returned pipes", - { timeout: LIVE_BROWSER_TIMEOUT_MS }, - async () => { - const chrome = await new LocalBrowserLauncher().launch({ - headless: true, - remote_debugging: "pipe", - chrome_ready_timeout_ms: 45_000, - }); - const profile_dir = chrome.profile_dir; - - try { - expect(chrome.port).toBeUndefined(); - expect(chrome.cdp_url).toEqual(expect.stringMatching(/^pipe:\/\/\d+/)); - expect(chrome.loopback_cdp_url).toBeUndefined(); - expect(chrome.pipe_read).toBeTruthy(); - expect(chrome.pipe_write).toBeTruthy(); - const pipeCdp = new PipeCdpSocket(chrome.pipe_read!, chrome.pipe_write!); - await expectCdpBrowserSurface(pipeCdp); - } finally { - await chrome.close(); - await chrome.close(); - } - - if (profile_dir) { - await expect(stat(profile_dir)).rejects.toMatchObject({ - code: "ENOENT", - }); - } - }, - ); - - it( - "launches a pipe browser with an auxiliary loopback CDP endpoint only when requested", - { timeout: LIVE_BROWSER_TIMEOUT_MS }, - async () => { - const chrome = await new LocalBrowserLauncher().launch({ - headless: true, - remote_debugging: "pipe", - loopback_cdp: true, - chrome_ready_timeout_ms: 45_000, - }); - let cdp: CdpSocket | null = null; - - try { - expect(chrome.cdp_url).toEqual(expect.stringMatching(/^pipe:\/\/\d+/)); - expect(chrome.port).toEqual(expect.any(Number)); - expect(chrome.loopback_cdp_url).toEqual(expect.stringMatching(/^ws:\/\/127\.0\.0\.1:\d+\//)); - cdp = await CdpSocket.connect(chrome.loopback_cdp_url!); - await expectCdpBrowserSurface(cdp); - } finally { - await cdp?.close(); - await chrome.close(); - } - }, - ); - it( "removes an explicit user data dir when cleanup_user_data_dir is set", { timeout: LIVE_BROWSER_TIMEOUT_MS }, async () => { const userDataDir = await mkdtemp(path.join(tmpdir(), "modcdp-local-profile-")); const chrome = await new LocalBrowserLauncher({ - headless: true, - chrome_ready_timeout_ms: 45_000, + launcher_local_headless: true, + launcher_local_chrome_ready_timeout_ms: 45_000, }).launch({ - user_data_dir: userDataDir, - cleanup_user_data_dir: true, + launcher_local_user_data_dir: userDataDir, + launcher_local_cleanup_user_data_dir: true, }); try { @@ -167,3 +78,46 @@ describe("LocalBrowserLauncher", () => { }, ); }); + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep the setup semantics above 1:1 with translated tests; helpers here only use real ModCDP transports against real browser endpoints. +async function expectCdpBrowserSurface(cdp: WSUpstreamTransport) { + const version = await cdp.send("Browser.getVersion"); + expect(version.product).toEqual(expect.stringMatching(/Chrome|Chromium/)); + expect(version.protocolVersion).toEqual(expect.any(String)); + + const created = await cdp.send("Target.createTarget", { url: "about:blank#modcdp-launcher-test" }); + expect(created.targetId).toEqual(expect.any(String)); + const targetId = created.targetId as string; + + try { + const attached = await cdp.send("Target.attachToTarget", { targetId, flatten: true }); + expect(attached.sessionId).toEqual(expect.any(String)); + const sessionId = attached.sessionId as string; + await cdp.send("Runtime.enable", {}, sessionId); + const evaluated = await cdp.send( + "Runtime.evaluate", + { expression: "(() => ({ ok: true, value: 42 }))()", returnByValue: true }, + sessionId, + ); + expect(evaluated.result).toMatchObject({ type: "object", value: { ok: true, value: 42 } }); + } finally { + await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); + } +} + +async function expectHttpEndpointDown(url: string) { + await expect + .poll( + async () => { + try { + await fetch(`${url}/json/version`); + return false; + } catch { + return true; + } + }, + { timeout: 5_000, interval: 100 }, + ) + .toBe(true); +} diff --git a/js/test/test.ModCDPClient.ts b/js/test/test.ModCDPClient.ts index afb40e64..b4d48413 100644 --- a/js/test/test.ModCDPClient.ts +++ b/js/test/test.ModCDPClient.ts @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ModCDPClient.py +// - ./go/modcdp/client/ModCDPClient_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { existsSync, readdirSync, statSync } from "node:fs"; import { homedir, platform } from "node:os"; @@ -7,40 +13,35 @@ import { test } from "vitest"; import { z } from "zod"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; -import { CdpSocket } from "./helpers.BrowserLauncher.js"; +import { ModCDPClient } from "../src/index.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; +import type { cdp as cdp_types } from "../src/types/generated/cdp.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); -const REVERSEWS_TEST_BROWSER_PATH = reversewsTestBrowserPath(); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } -test("ModCDPClient normalizes nested config owners", () => { +test("ModCDPClient uses flat owner-prefixed config", () => { const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_executable_path: "/tmp/chrome", - launcher_user_data_dir: "/tmp/profile", - launcher_options: { headless: true }, + launcher_local_executable_path: "/tmp/chrome", + launcher_local_user_data_dir: "/tmp/profile", + launcher_local_headless: true, }, upstream: { upstream_mode: "ws", - upstream_cdp_url: "http://127.0.0.1:9222", - upstream_nats_wait_timeout_ms: 345, - upstream_reversews_wait_timeout_ms: 456, - upstream_nativemessaging_manifest: "/tmp/native-host.json", - upstream_nativemessaging_manifests: ["/tmp/native-host-extra.json"], - upstream_nativemessaging_host_name: "com.modcdp.custom", - upstream_nativemessaging_wait_timeout_ms: 567, + upstream_ws_cdp_url: "http://127.0.0.1:9222", upstream_ws_connect_error_settle_timeout_ms: 321, }, injector: { injector_mode: "discover", - injector_extension_path: "/tmp/ext", - injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + injector_discover_extension_path: "/tmp/ext", + injector_service_worker_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", injector_service_worker_url_includes: ["modcdp"], injector_service_worker_url_suffixes: ["/custom/service_worker.js"], injector_trust_service_worker_target: true, @@ -51,63 +52,63 @@ test("ModCDPClient normalizes nested config owners", () => { injector_service_worker_poll_interval_ms: 76, injector_target_session_poll_interval_ms: 87, }, - client: { - client_routes: { "*.*": "direct_cdp" }, + router: { + router_routes: { "*.*": "direct_cdp" }, + loopback_execution_context_timeout_ms: 4321, + }, + client_config: { client_hydrate_aliases: false, client_mirror_upstream_events: false, client_cdp_send_timeout_ms: 1234, client_event_wait_timeout_ms: 2345, client_heartbeat_interval_ms: 3456, }, - server: { - server_routes: { "*.*": "loopback_cdp" }, + server_config: { + router: { router_routes: { "*.*": "loopback_cdp" } }, + client_config: { client_cdp_send_timeout_ms: 9876 }, + upstream: { upstream_ws_connect_error_settle_timeout_ms: 7654 }, + downstream: { downstream_client_timeout_ms: 4567 }, server_browser_token: "token-1", - server_cdp_send_timeout_ms: 9876, - server_loopback_execution_context_timeout_ms: 8765, - server_ws_connect_error_settle_timeout_ms: 7654, - server_downstream_client_timeout_ms: 4567, }, }); - assert.deepEqual(cdp.launcher.launcher_options, { headless: true }); - assert.equal(cdp._launcherOptions().executable_path, "/tmp/chrome"); - assert.equal(cdp._launcherOptions().user_data_dir, "/tmp/profile"); - assert.equal(cdp.upstream.upstream_nats_wait_timeout_ms, 345); - assert.equal(cdp.upstream.upstream_reversews_wait_timeout_ms, 456); - assert.equal(cdp.upstream.upstream_nativemessaging_manifest, "/tmp/native-host.json"); - assert.deepEqual(cdp.upstream.upstream_nativemessaging_manifests, ["/tmp/native-host-extra.json"]); - assert.equal(cdp.upstream.upstream_nativemessaging_host_name, "com.modcdp.custom"); - assert.equal(cdp.upstream.upstream_nativemessaging_wait_timeout_ms, 567); - assert.equal(cdp.upstream.upstream_ws_connect_error_settle_timeout_ms, 321); - assert.equal(cdp.injector.injector_execution_context_timeout_ms, 4321); - assert.equal(cdp.injector.injector_service_worker_probe_timeout_ms, 5432); - assert.equal(cdp.injector.injector_service_worker_ready_timeout_ms, 6543); - assert.equal(cdp.injector.injector_service_worker_poll_interval_ms, 76); - assert.equal(cdp.injector.injector_target_session_poll_interval_ms, 87); - assert.equal(cdp.client.client_routes["*.*"], "direct_cdp"); - assert.equal(cdp.client.client_hydrate_aliases, false); - assert.equal(cdp.client.client_mirror_upstream_events, false); - assert.equal(cdp.client.client_cdp_send_timeout_ms, 1234); - assert.equal(cdp.client.client_event_wait_timeout_ms, 2345); - assert.equal(cdp.client.client_heartbeat_interval_ms, 3456); + assert.equal(cdp.launcher.config.launcher_local_headless, true); + assert.equal(cdp.launcher.config.launcher_local_executable_path, "/tmp/chrome"); + assert.equal(cdp.launcher.config.launcher_local_user_data_dir, "/tmp/profile"); + assert.equal(cdp.upstream.config.upstream_ws_connect_error_settle_timeout_ms, 321); + assert.equal(cdp.injector.config.injector_execution_context_timeout_ms, 4321); + assert.equal(cdp.injector.config.injector_service_worker_probe_timeout_ms, 5432); + assert.equal(cdp.injector.config.injector_service_worker_ready_timeout_ms, 6543); + assert.equal(cdp.injector.config.injector_service_worker_poll_interval_ms, 76); + assert.equal(cdp.injector.config.injector_target_session_poll_interval_ms, 87); + assert.equal(cdp.router.config.router_routes["*.*"], "direct_cdp"); + assert.equal(cdp.config.client_hydrate_aliases, false); + assert.equal(cdp.config.client_mirror_upstream_events, false); + assert.equal(cdp.config.client_cdp_send_timeout_ms, 1234); + assert.equal(cdp.config.client_event_wait_timeout_ms, 2345); + assert.equal(cdp.config.client_heartbeat_interval_ms, 3456); assert.equal("routes" in cdp, false); assert.equal("cdp_send_timeout_ms" in cdp, false); assert.equal("service_worker_probe_timeout_ms" in cdp, false); + assert.equal("launcher_config" in cdp.launcher, false); + assert.equal("headless" in cdp.launcher, false); + assert.equal("executable_path" in cdp.launcher, false); + assert.equal("user_data_dir" in cdp.launcher, false); const params = cdp._serverConfigureParams(); - assert.equal(params.client.client_routes["*.*"], "direct_cdp"); - assert.equal(params.server.server_browser_token, "token-1"); - assert.equal(params.server.server_cdp_send_timeout_ms, 9876); - assert.equal(params.server.server_loopback_execution_context_timeout_ms, 8765); - assert.equal(params.server.server_ws_connect_error_settle_timeout_ms, 7654); - assert.equal(params.server.server_downstream_client_timeout_ms, 4567); + assert.equal(params.router?.router_routes?.["*.*"], "loopback_cdp"); + assert.equal(params.server_browser_token, "token-1"); + assert.equal(params.client_config?.client_cdp_send_timeout_ms, 9876); + assert.equal(params.router?.loopback_execution_context_timeout_ms, 4321); + assert.equal(params.upstream?.upstream_ws_connect_error_settle_timeout_ms, 7654); + assert.equal(params.downstream?.downstream_client_timeout_ms, 4567); }); test("ModCDPClient dispatches root events before extension session is attached", () => { const cdp = new ModCDPClient(); const seen: string[] = []; - cdp.on("Target.targetCreated", (payload: { targetInfo?: { targetId?: string } }) => { - seen.push(String(payload.targetInfo?.targetId)); + cdp.on(cdp.Target.targetCreated, (payload) => { + seen.push(payload.targetInfo.targetId); }); cdp._onRecv({ @@ -130,10 +131,10 @@ test("ModCDPClient dispatches root events before extension session is attached", test("ModCDPClient event dispatch snapshots handlers when once removes itself", () => { const cdp = new ModCDPClient(); const seen: string[] = []; - cdp.once("Target.targetCreated", () => { + cdp.once(cdp.Target.targetCreated, () => { seen.push("once"); }); - cdp.on("Target.targetCreated", () => { + cdp.on(cdp.Target.targetCreated, () => { seen.push("persistent"); }); @@ -194,69 +195,116 @@ test("ModCDPClient connects with nested launch/upstream/extension/client/server const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { - headless: true, - }, + launcher_local_headless: true, + launcher_local_chrome_ready_timeout_ms: 60_000, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, - client: { - client_routes: { + router: { + router_routes: { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp", }, + }, + client_config: { client_hydrate_aliases: true, client_mirror_upstream_events: true, client_cdp_send_timeout_ms: 10_000, client_event_wait_timeout_ms: 10_000, }, - server: { - server_routes: { "*.*": "loopback_cdp" }, - server_cdp_send_timeout_ms: 10_000, - server_loopback_execution_context_timeout_ms: 10_000, - server_ws_connect_error_settle_timeout_ms: 250, + server_config: { + client_config: { client_cdp_send_timeout_ms: 10_000 }, + router: { + router_routes: { "*.*": "loopback_cdp" }, + loopback_execution_context_timeout_ms: 10_000, + }, + upstream: { upstream_ws_connect_error_settle_timeout_ms: 250 }, }, }); let direct_session_target_id: string | null = null; try { await cdp.connect(); - assert.equal(cdp.launcher.launcher_mode, "local"); - assert.equal(cdp.upstream.upstream_mode, "ws"); - assert.equal(cdp.upstream.upstream_reversews_wait_timeout_ms, 10_000); - assert.equal(cdp.injector.injector_mode, "auto"); - assert.equal( - ["discovered", "local_launch", "extensions_load_unpacked", "borrowed"].includes( - String(cdp.connect_timing?.injector_source), - ), - true, - ); - assert.equal(cdp.client.client_routes["*.*"], "direct_cdp"); - assert.equal(cdp.upstream_endpoint_kind, "raw_cdp"); - assert.match(cdp.cdp_url ?? "", /^ws:\/\//); + assert.equal(cdp.launcher.config.launcher_mode, "local"); + assert.equal(cdp.upstream.config.upstream_mode, "ws"); + assert.equal(cdp.injector?.config.injector_mode, "cli"); + assert.equal(["discover", "cli", "cdp"].includes(String(cdp.connect_timing?.injector_source)), true); + assert.equal(cdp.router.config.router_routes["*.*"], "direct_cdp"); + assert.match(cdp.upstream.config.upstream_ws_cdp_url ?? "", /^ws:\/\//); const service_worker_url = await cdp.Mod.evaluate({ expression: "chrome.runtime.getURL('modcdp/service_worker.js')", }); - assert.equal(service_worker_url, `chrome-extension://${cdp.extension_id}/modcdp/service_worker.js`); - const contexts = (await cdp.Mod.evaluate({ - expression: - "chrome.runtime.getContexts({}).then((contexts) => contexts.map((context) => ({ type: context.contextType, url: context.documentUrl || context.origin || '' })))", - })) as { type?: string; url?: string }[]; + assert.equal(service_worker_url, `chrome-extension://${cdp.injector?.extension_id}/modcdp/service_worker.js`); assert.equal( - contexts.some( - (context) => - context.type === "OFFSCREEN_DOCUMENT" && - context.url === `chrome-extension://${cdp.extension_id}/offscreen/keepalive.html`, - ), + await cdp.Mod.evaluate({ + expression: + "chrome.runtime.getContexts({}).then((contexts) => contexts.some((context) => context.contextType === 'OFFSCREEN_DOCUMENT'))", + }), true, ); - assert.equal(typeof (await cdp.Browser.getVersion()).product, "string"); + const version = await cdp.Browser.getVersion(); + assert.match(version.product, /Chrome|Chromium/); + assert.equal(typeof version.protocolVersion, "string"); + const runtime_evaluation = await cdp.Runtime.evaluate({ + expression: "1 + 1", + returnByValue: true, + }); + assert.equal(runtime_evaluation.result.type, "number"); + assert.equal(runtime_evaluation.result.value, 2); + await assert.rejects( + // @ts-expect-error Runtime.evaluate requires expression in the public alias params. + () => cdp.Runtime.evaluate({ returnByValue: true }), + /expression/, + ); + await assert.rejects( + // @ts-expect-error Mod.ping sent_at is a number in the public alias params. + () => cdp.Mod.ping({ sent_at: "bad" }), + /number/, + ); + assert.deepEqual( + await cdp.Mod.addMiddleware({ + name: cdp.Mod.ping, + phase: cdp.RESPONSE, + expression: "async (payload, next) => next(payload)", + }), + { name: "Mod.ping", phase: "response", registered: true }, + ); + await assert.rejects( + () => + cdp.Mod.addMiddleware({ + name: cdp.Mod.ping, + // @ts-expect-error middleware phase is a narrow public union. + phase: "after", + expression: "async (payload, next) => next(payload)", + }), + /Invalid option/, + ); + const created_event = new Promise((resolve) => { + const listener = (payload: cdp_types.types.ts.Target.TargetCreatedEvent) => { + if (payload.targetInfo.url !== "about:blank#public-api-target-created") return; + cdp.off(cdp.Target.targetCreated, listener); + const targetId: string = payload.targetInfo.targetId; + resolve(targetId); + if (false) { + // @ts-expect-error Target.targetCreated targetInfo.targetId is a string. + const badTargetId: number = payload.targetInfo.targetId; + void badTargetId; + } + }; + cdp.on(cdp.Target.targetCreated, listener); + }); + const created_via_alias = await cdp.Target.createTarget({ + url: "about:blank#public-api-target-created", + }); + assert.equal(await created_event, created_via_alias.targetId); + await cdp.Target.closeTarget({ targetId: created_via_alias.targetId }); const direct_target = (await cdp.send("Target.createTarget", { url: "about:blank#direct-session-routing", })) as Record; @@ -297,16 +345,15 @@ test("ModCDPClient connects with nested launch/upstream/extension/client/server test("ModCDPClient preserves explicit empty service worker suffix config", async () => { const cdp = new ModCDPClient({ injector: { - injector_mode: "borrow", + injector_mode: "discover", injector_service_worker_url_suffixes: [], }, }); - assert.deepEqual(cdp.injector.injector_service_worker_url_suffixes, []); - assert.deepEqual((await cdp._baseInjectorConfig()).injector_service_worker_url_suffixes, []); + assert.deepEqual(cdp.injector.config.injector_service_worker_url_suffixes, []); }, 60_000); -function reversewsTestBrowserPath() { +function loadExtensionTestBrowserPath() { const explicit_candidates = [process.env.CHROME_PATH, platform() === "linux" ? "/usr/bin/chromium" : null].filter( (candidate): candidate is string => Boolean(candidate), ); @@ -342,7 +389,7 @@ function reversewsTestBrowserPath() { ]; const candidates = newestFirst(patterns.flatMap(expandGlob)); if (candidates[0]) return candidates[0]; - throw new Error("Reversews tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing."); + throw new Error("Extension loading tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing."); } function expandGlob(pattern: string) { @@ -393,114 +440,110 @@ function scorePath(candidate: string) { } test("ModCDPClient defaults service worker suffix config to the ModCDP worker", async () => { - const cdp = new ModCDPClient(); + const cdp = new ModCDPClient({ injector: { injector_mode: "discover" } }); - assert.deepEqual(cdp.injector.injector_service_worker_url_suffixes, ["/modcdp/service_worker.js"]); - assert.deepEqual((await cdp._baseInjectorConfig()).injector_service_worker_url_suffixes, [ - "/modcdp/service_worker.js", - ]); + assert.deepEqual(cdp.injector?.config.injector_service_worker_url_suffixes, ["/modcdp/service_worker.js"]); }); test("ModCDPClient preserves explicit null server config", () => { - const cdp = new ModCDPClient({ server: null }); + const cdp = new ModCDPClient({ server_config: null }); - assert.equal(cdp.server, null); + assert.equal(cdp.server_config, null); }); -test("ModCDPClient only exposes injector attach after CDP send is available", () => { - const cdp = new ModCDPClient(); - const disconnected_config = cdp._baseInjectorConfig(null); - assert.equal(disconnected_config.send, null); - assert.equal(disconnected_config.attachToTarget, null); +test("ModCDPClient uses no injector unless injector_mode is explicit", () => { + const launched = new ModCDPClient({ + launcher: { launcher_mode: "local" }, + upstream: { upstream_mode: "ws" }, + }); + assert.equal(launched.launcher.config.launcher_mode, "local"); + assert.equal(launched.injector, null); - const connected_config = cdp._baseInjectorConfig(async () => ({})); - assert.equal(typeof connected_config.send, "function"); - assert.equal(typeof connected_config.attachToTarget, "function"); + const attach_only = new ModCDPClient({ upstream: { upstream_mode: "ws" } }); + assert.equal(attach_only.launcher.config.launcher_mode, "none"); + assert.equal(attach_only.injector, null); }); -test("ModCDPClient defaults launched ModCDP-server upstreams to extension auto", () => { - for (const mode of ["nativemessaging", "reversews", "nats"] as const) { - const launched = new ModCDPClient({ - launcher: { launcher_mode: "local" }, - upstream: { upstream_mode: mode }, - }); - assert.equal(launched.launcher.launcher_mode, "local"); - assert.equal(launched.upstream_endpoint_kind, "modcdp_server"); - assert.equal(launched.injector.injector_mode, "auto"); - - const attach_only = new ModCDPClient({ upstream: { upstream_mode: mode } }); - assert.equal(attach_only.launcher.launcher_mode, "none"); - assert.equal(attach_only.upstream_endpoint_kind, "modcdp_server"); - assert.equal(attach_only.injector.injector_mode, "none"); - } -}); - -test("ModCDPClient orders local auto injection as launch flag then loadUnpacked fallback", async () => { +test("ModCDPClient selects exactly one injector from explicit injector_mode", async () => { const cdp = new ModCDPClient({ launcher: { launcher_mode: "local" }, - injector: { injector_mode: "auto" }, + injector: { injector_mode: "cli" }, }); - assert.deepEqual( - (await cdp._injectorsForConfig()).map((injector) => injector.constructor.name), - [ - "LocalBrowserLaunchExtensionInjector", - "ExtensionsLoadUnpackedInjector", - "DiscoveredExtensionInjector", - "BorrowedExtensionInjector", - ], + assert.equal(cdp.injector?.constructor.name, "CLIExtensionInjector"); + assert.equal( + new ModCDPClient({ + launcher: { launcher_mode: "remote" }, + injector: { injector_mode: "cdp" }, + }).injector?.constructor.name, + "CDPExtensionInjector", + ); + assert.equal( + new ModCDPClient({ + launcher: { launcher_mode: "bb" }, + injector: { injector_mode: "bb" }, + }).injector?.constructor.name, + "BBExtensionInjector", + ); + assert.equal( + new ModCDPClient({ + launcher: { launcher_mode: "remote" }, + injector: { injector_mode: "discover" }, + }).injector?.constructor.name, + "DiscoverExtensionInjector", ); }); test("ModCDPClient rejects unknown component modes at their owning factory boundary", async () => { - await assert.rejects( + assert.throws( () => new ModCDPClient({ upstream: { upstream_mode: "bogus" as any }, - })._upstreamTransport(), - /unknown upstream\.upstream_mode=bogus/, + }), + /unknown upstream_mode=bogus/, ); - await assert.rejects( + assert.throws( () => new ModCDPClient({ launcher: { launcher_mode: "bogus" as any }, - })._browserLauncher(), - /unknown launcher\.launcher_mode=bogus/, + }), + /Invalid option/, ); - await assert.rejects( + assert.throws( () => new ModCDPClient({ injector: { injector_mode: "bogus" as any }, - })._injectorsForConfig(), - /unknown injector\.injector_mode=bogus/, + }), + /Invalid option/, ); }); test("ModCDPClient.close does not close a remote browser it did not launch", async () => { const chrome = await new LocalBrowserLauncher({ - headless: true, - chrome_ready_timeout_ms: 60_000, + launcher_local_headless: true, + launcher_local_chrome_ready_timeout_ms: 60_000, // This test manually supplies --load-extension, so it intentionally uses // the launch-flag browser path instead of relying on the client fallback. - executable_path: REVERSEWS_TEST_BROWSER_PATH, - extra_args: [`--load-extension=${EXTENSION_PATH}`], + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + launcher_local_extra_args: [`--load-extension=${EXTENSION_PATH}`], }).launch(); - const raw_cdp = await CdpSocket.connect(chrome.cdp_url!); + const raw_cdp = new WSUpstreamTransport({ upstream_ws_cdp_url: chrome.cdp_url }); const cdp = new ModCDPClient({ - launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws", upstream_cdp_url: chrome.cdp_url }, + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: chrome.cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: chrome.cdp_url }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "discover", + injector_discover_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, injector_service_worker_ready_timeout_ms: 30_000, injector_service_worker_probe_timeout_ms: 30_000, }, - client: { client_routes: { "*.*": "direct_cdp" } }, + router: { router_routes: { "*.*": "direct_cdp" } }, }); try { + await raw_cdp.connect(); await cdp.connect(); await cdp.close(); await delay(500); @@ -517,38 +560,34 @@ test("ModCDPClient.close keeps injector files until after launched browser shutd const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { - headless: true, - // After explicit CHROME_PATH and CI /usr/bin/chromium, this test uses - // Chrome for Testing because Canary rejects --load-extension in this - // local launch injector path. - executable_path: REVERSEWS_TEST_BROWSER_PATH, - }, + launcher_local_headless: true, + // After explicit CHROME_PATH and CI /usr/bin/chromium, this test uses + // Chrome for Testing because Canary rejects --load-extension in this + // local launch injector path. + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws", }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, - server: { - server_routes: { "*.*": "loopback_cdp" }, + server_config: { + router: { router_routes: { "*.*": "loopback_cdp" } }, }, }); try { await cdp.connect(); - const injector = cdp._injectors.find( - (candidate) => candidate.constructor.name === "LocalBrowserLaunchExtensionInjector", - ) as unknown as { unpacked_extension_path?: string | null } | undefined; - const unpacked_extension_path = injector?.unpacked_extension_path; + const injector = cdp.injector as unknown as { unpacked_extension_path?: string | null }; + const unpacked_extension_path = injector.unpacked_extension_path; assert.equal(typeof unpacked_extension_path, "string"); assert.notEqual(unpacked_extension_path, EXTENSION_PATH); - const launched = cdp._launched; + const launched = cdp.launcher.launched; assert.ok(launched); const close_browser = launched.close; let browser_close_saw_extension = false; @@ -564,32 +603,25 @@ test("ModCDPClient.close keeps injector files until after launched browser shutd } finally { await cdp.close(); } - assert.equal(cdp.transport, null); - assert.equal(cdp._launched, null); - assert.deepEqual(cdp._injectors, []); + assert.equal(cdp.launcher.launched, null); }, 90_000); test("ModCDPClient.close clears top-level connection state", async () => { const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { - headless: true, - }, + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, }); await cdp.connect(); - assert.ok(cdp.transport); await cdp.close(); - - assert.equal(cdp.transport, null); - await assert.rejects(() => cdp.sendRaw("Browser.getVersion"), /ModCDP upstream is not connected/); }, 60_000); diff --git a/js/test/test.ModCDPClientCustomFlatNamespace.ts b/js/test/test.ModCDPClientCustomFlatNamespace.ts index 1f8f16fb..150f1975 100644 --- a/js/test/test.ModCDPClientCustomFlatNamespace.ts +++ b/js/test/test.ModCDPClientCustomFlatNamespace.ts @@ -1,57 +1,91 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ModCDPClientCustomFlatNamespace.py +// - ./go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { test } from "vitest"; import { z } from "zod"; -import { ModCDPClient, type ModCDPClientInstance } from "../src/client/ModCDPClient.js"; -import { installModCDPServer } from "../src/server/ModCDPServer.js"; -import type { - ModCDPCustomCommandRegistration, - ModCDPCustomEventRegistration, - ProtocolParams, - ProtocolPayload, - ProtocolResult, -} from "../src/types/modcdp.js"; +import { ModCDPClient } from "../src/index.js"; +import { ModCDPServer } from "../src/server/ModCDPServer.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); test("custom commands install flat namespace methods through a real service worker", async () => { - type CustomClient = ModCDPClientInstance<{ - "Custom.doSomething": { params: { id: string }; result: boolean }; - }>; + const params_schema = z.object({ id: z.string(), suffix: z.string().optional() }); + const result_schema = z.object({ success: z.boolean() }); const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { headless: true }, + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, - client: { client_routes: { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp" } }, - server: { server_routes: { "*.*": "loopback_cdp" } }, - }) as CustomClient; + router: { + router_routes: { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }, + }, + server_config: { router: { router_routes: { "*.*": "loopback_cdp" } } }, + types: { + custom_commands: { + "Custom.doSomething": { + params_schema, + result_schema, + expression: "async ({ id, suffix = '' }) => ({ success: `${id}${suffix}` === 'abcmiddleware' })", + }, + "Custom.badResult": { + params_schema: z.object({ id: z.string() }), + result_schema, + expression: "async () => ({ success: 'yes' })", + }, + }, + custom_middlewares: [ + { + name: "Custom.doSomething", + phase: "request", + expression: "async (payload, next) => next({ ...payload, suffix: 'middleware' })", + }, + ], + }, + }); try { await cdp.connect(); - await cdp.Mod.addCustomCommand("Custom.doSomething", { - params_schema: z.object({ id: z.string() }), - result_schema: z.object({ success: z.boolean() }), - expression: "async ({ id }) => ({ success: id === 'abc' })", - }); - const success: boolean = await cdp.Custom.doSomething({ id: "abc" }); - const rawSuccess: boolean = Boolean(await cdp.send("Custom.doSomething", { id: "abc" })); + const success: { success: boolean } = await cdp.Custom.doSomething({ id: "abc" }); + const rawSuccess = await cdp.send("Custom.doSomething", { id: "abc" }); - assert.equal(success, true); - assert.equal(rawSuccess, true); + assert.deepEqual(success, { success: true }); + assert.deepEqual(rawSuccess, { success: true }); + if (false) { + const typed_params: Parameters[0] = { id: "abc" }; + void typed_params; + const typed_result: Awaited> = { success: true }; + void typed_result; + // @ts-expect-error custom command results follow native CDP result-object shape. + const bad_result: Awaited> = true; + void bad_result; + } // @ts-expect-error typed custom command params reject non-string ids statically. await assert.rejects(() => cdp.Custom.doSomething({ id: 123 })); + await assert.rejects(() => cdp.send("Custom.doSomething", { id: 123 }), /string/); + await assert.rejects(() => cdp.Custom.badResult({ id: "abc" }), /boolean/); } finally { await cdp.close(); } @@ -59,37 +93,47 @@ test("custom commands install flat namespace methods through a real service work test("custom events validate raw string handlers through a real service worker", async () => { const EventSchema = z.object({ data: z.string() }); - type Event = z.infer; - type CustomClient = ModCDPClientInstance, { "Custom.someEvent": Event }>; const cdp = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { headless: true }, + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, - client: { client_routes: { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp" } }, - server: { server_routes: { "*.*": "loopback_cdp" } }, - }) as CustomClient; + router: { + router_routes: { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }, + }, + server_config: { router: { router_routes: { "*.*": "loopback_cdp" } } }, + types: { + custom_events: { + "Custom.someEvent": { event_schema: EventSchema }, + }, + }, + }); const seen: string[] = []; try { await cdp.connect(); - await cdp.Mod.addCustomEvent("Custom.someEvent", { event_schema: EventSchema }); const received = new Promise((resolve) => { - cdp.on("Custom.someEvent", (event: Event) => { + cdp.on("Custom.someEvent", (event) => { seen.push(event.data); resolve(); }); }); await cdp.Mod.evaluate({ - expression: "async () => await globalThis.ModCDP.emit('Custom.someEvent', { data: 'ok' })", + expression: + "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.someEvent', data: { data: 'ok' }, cdpSessionId: null }))", }); await received; assert.deepEqual(seen, ["ok"]); @@ -98,8 +142,225 @@ test("custom events validate raw string handlers through a real service worker", } }, 60_000); +test("dynamic custom command, event, and middleware registration validates through a real service worker", async () => { + const cdp = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + router: { + router_routes: { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }, + }, + server_config: { router: { router_routes: { "*.*": "loopback_cdp" } } }, + }); + const seen: string[] = []; + + try { + await cdp.connect(); + + if (false) { + cdp.Mod.addCustomCommand("Custom.dynamic", { + params_schema: z.object({ text: z.string() }), + result_schema: z.object({ ok: z.boolean() }), + expression: "async ({ text }) => ({ ok: text === 'live-dynamic' })", + }); + cdp.Mod.addCustomEvent("Custom.dynamicReady", { + event_schema: z.object({ id: z.string() }), + }); + cdp.Mod.addMiddleware({ + name: "Custom.dynamic", + phase: cdp.REQUEST, + expression: "async (payload, next) => next(payload)", + }); + cdp.Mod.addMiddleware({ + name: "Custom.dynamic", + // @ts-expect-error Mod.addMiddleware phase is request, response, or event. + phase: "after", + expression: "async (payload, next) => next(payload)", + }); + } + + assert.deepEqual( + await cdp.Mod.addCustomCommand("Custom.dynamic", { + params_schema: z.object({ text: z.string().min(1) }), + result_schema: z.object({ ok: z.boolean() }), + expression: "async ({ text }) => ({ ok: text === 'live-dynamic' })", + }), + { name: "Custom.dynamic", registered: true }, + ); + assert.deepEqual( + await cdp.Mod.addCustomCommand("Custom.dynamicBadResult", { + params_schema: z.object({ text: z.string() }), + result_schema: z.object({ ok: z.boolean() }), + expression: "async () => ({ ok: 'yes' })", + }), + { name: "Custom.dynamicBadResult", registered: true }, + ); + assert.deepEqual( + await cdp.Mod.addCustomEvent("Custom.dynamicReady", { + event_schema: z.object({ id: z.string().uuid() }), + }), + { name: "Custom.dynamicReady", registered: true }, + ); + assert.deepEqual( + await cdp.Mod.addMiddleware({ + name: "Custom.dynamic", + phase: cdp.REQUEST, + expression: "async (payload, next) => next({ ...payload, text: `${payload.text}-dynamic` })", + }), + { name: "Custom.dynamic", phase: "request", registered: true }, + ); + + assert.deepEqual(await cdp.send("Custom.dynamic", { text: "live" }), { ok: true }); + await assert.rejects(() => cdp.send("Custom.dynamic", { text: "" }), /Too small/); + await assert.rejects(() => cdp.send("Custom.dynamicBadResult", { text: "live" }), /boolean/); + await assert.rejects( + () => + cdp.Mod.addMiddleware({ + name: "Custom.dynamic", + // @ts-expect-error dynamic middleware registration rejects invalid phases statically. + phase: "after", + expression: "async (payload, next) => next(payload)", + }), + /Invalid option/, + ); + + const received = new Promise((resolve) => { + cdp.on("Custom.dynamicReady", () => { + seen.push("ready"); + resolve(); + }); + }); + await cdp.Mod.evaluate({ + expression: + "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.dynamicReady', data: { id: '550e8400-e29b-41d4-a716-446655440000' }, cdpSessionId: null }))", + }); + await received; + assert.deepEqual(seen, ["ready"]); + } finally { + await cdp.close(); + } +}, 60_000); + +test("assigned type registry validates updated custom command, event, and middleware schemas through a real service worker", async () => { + const cdp = new ModCDPClient({ + launcher: { + launcher_mode: "local", + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream: { upstream_mode: "ws" }, + injector: { + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, + injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], + injector_trust_service_worker_target: true, + }, + router: { + router_routes: { + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", + }, + }, + server_config: { router: { router_routes: { "*.*": "loopback_cdp" } } }, + }); + const updated_types = cdp.types.update({ + custom_commands: { + "Custom.updated": { + params_schema: z.object({ count: z.number().int().nonnegative() }), + result_schema: z.object({ done: z.boolean() }), + expression: "async ({ count }) => ({ done: count === 2 })", + }, + "Custom.updatedBadResult": { + params_schema: z.object({ count: z.number() }), + result_schema: z.object({ done: z.boolean() }), + expression: "async () => ({ done: 'yes' })", + }, + }, + custom_events: { + "Custom.updatedReady": { event_schema: z.object({ ready: z.boolean() }) }, + }, + custom_middlewares: [ + { + name: "Custom.updated", + phase: "request", + expression: "async (payload, next) => next({ ...payload, count: payload.count + 1 })", + }, + ], + }); + const typed_client = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + types: updated_types, + }); + cdp.types = updated_types; + const seen: boolean[] = []; + + if (false) { + const params: Parameters[0] = { count: 1 }; + void params; + const result: Awaited> = { done: true }; + void result; + typed_client.on("Custom.updatedReady", (event) => { + const ready: boolean = event.ready; + void ready; + // @ts-expect-error Custom.updatedReady.ready is boolean. + const badReady: string = event.ready; + void badReady; + }); + // @ts-expect-error Custom.updated count is required. + typed_client.Custom.updated({}); + // @ts-expect-error Custom.updated returns a CDP-style result object. + const badResult: Awaited> = true; + void badResult; + } + + try { + await cdp.connect(); + + assert.deepEqual(await cdp.send("Custom.updated", { count: 1 }), { done: true }); + await assert.rejects(() => cdp.send("Custom.updated", { count: -1 }), /Too small/); + await assert.rejects(() => cdp.send("Custom.updatedBadResult", { count: 1 }), /boolean/); + + const received = new Promise((resolve) => { + cdp.on("Custom.updatedReady", () => { + seen.push(true); + resolve(); + }); + }); + await cdp.Mod.evaluate({ + expression: + "async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.updatedReady', data: { ready: true }, cdpSessionId: null }))", + }); + await received; + assert.deepEqual(seen, [true]); + } finally { + await cdp.close(); + } +}, 60_000); + test("schema-only custom commands register without a websocket", async () => { - const cdp = new ModCDPClient(); + const cdp = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); const result = await cdp.send("Mod.addCustomCommand", { name: "Custom.echo", @@ -118,10 +379,8 @@ test("schema-only custom commands register without a websocket", async () => { }); assert.deepEqual(result, { name: "Custom.echo", registered: true }); - const command_params_schemas = (cdp as unknown as { command_params_schemas: Map }) - .command_params_schemas; - const command_result_schemas = (cdp as unknown as { command_result_schemas: Map }) - .command_result_schemas; + const command_params_schemas = cdp.types.command_params_schemas; + const command_result_schemas = cdp.types.command_result_schemas; assert.deepEqual(command_params_schemas.get("Custom.echo")?.parse({ text: "ok" }), { text: "ok" }); assert.throws(() => command_params_schemas.get("Custom.echo")?.parse({ text: "" })); assert.throws(() => command_params_schemas.get("Custom.echo")?.parse({ text: "ok", extra: true })); @@ -131,50 +390,55 @@ test("schema-only custom commands register without a websocket", async () => { test("constructor custom command and event schemas validate nested payloads", () => { const cdp = new ModCDPClient({ - custom_commands: [ - { - name: "Custom.collect", - params_schema: { - type: "object", - properties: { - items: { - type: "array", - minItems: 1, + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + types: { + custom_commands: [ + { + name: "Custom.collect", + params_schema: { + type: "object", + properties: { items: { - type: "object", - properties: { - id: { type: "string" }, - count: { type: "integer", minimum: 1 }, + type: "array", + minItems: 1, + items: { + type: "object", + properties: { + id: { type: "string" }, + count: { type: "integer", minimum: 1 }, + }, + required: ["id", "count"], + additionalProperties: false, }, - required: ["id", "count"], - additionalProperties: false, }, }, + required: ["items"], + additionalProperties: false, }, - required: ["items"], - additionalProperties: false, }, - }, - ], - custom_events: [ - { - name: "Custom.ready", - event_schema: { - type: "object", - properties: { - url: { type: "string", pattern: "^https://" }, - ready: { type: "boolean" }, + ], + custom_events: [ + { + name: "Custom.ready", + event_schema: { + type: "object", + properties: { + url: { type: "string", pattern: "^https://" }, + ready: { type: "boolean" }, + }, + required: ["url", "ready"], + additionalProperties: false, }, - required: ["url", "ready"], - additionalProperties: false, }, - }, - { name: "Custom.count", event_schema: { type: "integer", minimum: 1 } }, - ], + { name: "Custom.count", event_schema: { type: "integer", minimum: 1 } }, + ], + }, }); - const command_params_schemas = (cdp as unknown as { command_params_schemas: Map }) - .command_params_schemas; - const event_schemas = (cdp as unknown as { event_schemas: Map }).event_schemas; + const command_params_schemas = cdp.types.command_params_schemas; + const event_schemas = cdp.types.event_schemas; const valid_params = { items: [{ id: "a", count: 1 }] }; assert.deepEqual(command_params_schemas.get("Custom.collect")?.parse(valid_params), valid_params); @@ -185,35 +449,87 @@ test("constructor custom command and event schemas validate nested payloads", () ready: true, }); assert.throws(() => event_schemas.get("Custom.ready")?.parse({ url: "http://example.com", ready: true })); - assert.deepEqual(event_schemas.get("Custom.count")?.parse({ value: 3 }), { value: 3 }); + assert.deepEqual(event_schemas.get("Custom.count")?.parse({ value: 3 }), { + value: 3, + }); assert.throws(() => event_schemas.get("Custom.count")?.parse({ value: 0 })); }); +test("assigned type registry updates runtime validation and aliases", () => { + const cdp = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); + + cdp.types = cdp.types.update({ + custom_commands: { + "Custom.later": { + params_schema: z.object({ value: z.number() }), + result_schema: z.object({ ok: z.boolean() }), + }, + }, + custom_events: { + "Custom.laterReady": { event_schema: z.object({ value: z.string() }) }, + }, + }); + + assert.equal(typeof (cdp as unknown as { Custom: { later: unknown } }).Custom.later, "function"); + assert.deepEqual(cdp.types.command_params_schemas.get("Custom.later")?.parse({ value: 1 }), { value: 1 }); + assert.deepEqual(cdp.types.parseCommandResult("Custom.later", { ok: true }), { ok: true }); + assert.deepEqual(cdp.types.parseEventPayload("Custom.laterReady", { value: "ok" }), { value: "ok" }); +}); + test("service worker server validates registered custom command and event schemas", async () => { - const scope = {} as typeof globalThis; - const server = installModCDPServer(scope) as unknown as { - addCustomCommand(registration: ModCDPCustomCommandRegistration): ProtocolResult; - addCustomEvent(registration: ModCDPCustomEventRegistration): ProtocolResult; - handleCommand(method: string, params?: ProtocolParams, cdpSessionId?: string | null): Promise; - emit(eventName: string, payload?: ProtocolPayload, cdpSessionId?: string | null): Promise; + const modcdp_global = globalThis as typeof globalThis & { + ModCDP?: ModCDPServer; }; + const previous_modcdp = modcdp_global.ModCDP; + const server = new ModCDPServer(); + modcdp_global.ModCDP = server; - server.addCustomCommand({ - name: "Custom.double", - params_schema: z.object({ value: z.number() }), - result_schema: z.object({ value: z.number() }), - handler: async (params: { value: number }) => ({ value: params.value * 2 }), - }); - assert.deepEqual(await server.handleCommand("Custom.double", { value: 2 }), { value: 4 }); - await assert.rejects(() => server.handleCommand("Custom.double", { value: "2" })); + try { + await server.configure({ + router: { router_routes: { "*.*": "chromedebugger" } }, + }); - server.addCustomCommand({ - name: "Custom.badResult", - result_schema: z.object({ ok: z.boolean() }), - handler: async () => ({ ok: "yes" }), - }); - await assert.rejects(() => server.handleCommand("Custom.badResult", {})); + server.addCustomCommand({ + name: "Custom.double", + params_schema: z.object({ value: z.number() }), + result_schema: z.object({ value: z.number() }), + expression: "async (params) => ({ value: params.value * 2 })", + }); + assert.deepEqual(server.client?.types.command_params_schemas.get("Custom.double")?.parse({ value: 2 }), { + value: 2, + }); + assert.deepEqual(server.client?.types.command_result_schemas.get("Custom.double")?.parse({ value: 4 }), { + value: 4, + }); + assert.equal( + server.client?.types.custom_commands.get("Custom.double")?.expression, + "async (params) => ({ value: params.value * 2 })", + ); + + server.addCustomCommand({ + name: "Custom.badResult", + result_schema: z.object({ ok: z.boolean() }), + expression: 'async () => ({ ok: "yes" })', + }); + assert.equal( + server.client?.types.custom_commands.get("Custom.badResult")?.expression, + 'async () => ({ ok: "yes" })', + ); - server.addCustomEvent({ name: "Custom.ready", event_schema: z.object({ ok: z.boolean() }) }); - await assert.rejects(() => server.emit("Custom.ready", { ok: "yes" })); + server.addCustomEvent({ + name: "Custom.ready", + event_schema: z.object({ ok: z.boolean() }), + }); + assert.deepEqual(server.client?.types.event_schemas.get("Custom.ready")?.parse({ ok: true }), { ok: true }); + assert.throws(() => server.client?.types.parseEventPayload("Custom.ready", { ok: "yes" })); + } finally { + server.downstream.stop("test complete"); + if (previous_modcdp) modcdp_global.ModCDP = previous_modcdp; + else delete modcdp_global.ModCDP; + } }); diff --git a/js/test/test.ModCDPClientRoutedDefaultOverrides.ts b/js/test/test.ModCDPClientRoutedDefaultOverrides.ts index 24ba83a7..8e3ec30c 100644 --- a/js/test/test.ModCDPClientRoutedDefaultOverrides.ts +++ b/js/test/test.ModCDPClientRoutedDefaultOverrides.ts @@ -1,25 +1,27 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ModCDPClientRoutedDefaultOverrides.py +// - ./go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { test } from "vitest"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; -import { events } from "../src/types/generated/zod.js"; +import { ModCDPClient } from "../src/index.js"; +import type { cdp } from "../src/types/generated/cdp.js"; +import { loadExtensionTestBrowserPath } from "./browserPaths.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); const DEFAULT_ROUTED_OVERRIDES_TEST_TIMEOUT_MS = 45_000; - -function hasTargetInfo(value: unknown): value is { targetInfo: Record } { - if (value == null || typeof value !== "object" || Array.isArray(value)) return false; - const targetInfo = (value as Record).targetInfo; - return targetInfo != null && typeof targetInfo === "object" && !Array.isArray(targetInfo); -} +const LOAD_EXTENSION_TEST_BROWSER_PATH = loadExtensionTestBrowserPath(); const getTargetsOverride = String.raw` async (params) => { const [upstream, tabs] = await Promise.all([ - ModCDP.sendLoopback("Target.getTargets", params), + cdp.upstream.send("Target.getTargets", params), chrome.tabs.query({}), ]); @@ -57,7 +59,7 @@ async (payload, next) => { const visit = async value => { if (!value || typeof value !== "object" || seen.has(value)) return; seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { + if (!Array.isArray(value) && typeof value.targetId === "string" && typeof value.type === "string" && value.tabId == null) { const { tabId } = await cdp.send("Custom.tabIdFromTargetId", { targetId: value.targetId }); if (tabId != null) value.tabId = tabId; } @@ -75,49 +77,48 @@ test( const owner = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: { - headless: true, - }, + launcher_local_headless: true, + launcher_local_executable_path: LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, }); await owner.connect(); const cdp = new ModCDPClient({ - launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws", upstream_cdp_url: owner.cdp_url }, + launcher: { launcher_mode: "remote", launcher_remote_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, injector: { injector_mode: "discover", injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, - client: { - client_routes: { + router: { + router_routes: { "Target.getTargets": "service_worker", "Target.createTarget": "service_worker", "Target.setDiscoverTargets": "service_worker", }, }, - server: { - server_loopback_cdp_url: owner.cdp_url, - server_routes: { "*.*": "loopback_cdp" }, + server_config: { + upstream: { upstream_ws_cdp_url: owner.upstream.config.upstream_ws_cdp_url }, + router: { router_routes: { "*.*": "loopback_cdp" } }, }, }); try { await cdp.connect(); - assert.equal(cdp.cdp_url, owner.cdp_url); - assert.equal(cdp.server.server_loopback_cdp_url, owner.cdp_url); + assert.equal(cdp.upstream.config.upstream_ws_cdp_url, owner.upstream.config.upstream_ws_cdp_url); + assert.equal(cdp.server_config?.upstream?.upstream_ws_cdp_url, owner.upstream.config.upstream_ws_cdp_url); - const rawTargets = await cdp.send("Target.getTargets"); + const rawTargets = (await cdp.send("Target.getTargets")) as { targetInfos: { type?: string; tabId?: number }[] }; assert.ok(rawTargets.targetInfos?.length > 0, "expected raw Target.getTargets targetInfos"); assert.equal( - rawTargets.targetInfos.some((targetInfo) => Object.hasOwn(targetInfo, "tabId")), + rawTargets.targetInfos.some((targetInfo) => targetInfo.tabId != null), false, "raw CDP TargetInfo should not already contain tabId", ); @@ -131,7 +132,9 @@ test( phase: cdp.RESPONSE, expression: addTabIdMiddleware, }); - const middlewareTargets = await cdp.send("Target.getTargets"); + const middlewareTargets = (await cdp.send("Target.getTargets")) as { + targetInfos: { type?: string; tabId?: number }[]; + }; assert.ok( middlewareTargets.targetInfos.some( (targetInfo) => targetInfo.type === "page" && Number.isInteger(targetInfo.tabId), @@ -150,12 +153,14 @@ test( expression: getTargetsOverride, }); - const enrichedTargets = await cdp.send("Target.getTargets"); + const enrichedTargets = (await cdp.send("Target.getTargets")) as { + targetInfos: { type?: string; tabId?: number }[]; + }; assert.ok(enrichedTargets.targetInfos?.length > 0, "expected enriched Target.getTargets targetInfos"); assert.equal( - enrichedTargets.targetInfos.every((targetInfo) => Object.hasOwn(targetInfo, "tabId")), + enrichedTargets.targetInfos.every((targetInfo) => "tabId" in targetInfo), true, - "every routed TargetInfo should include a tabId property", + "the custom Target.getTargets override should add a tabId property", ); assert.ok( enrichedTargets.targetInfos.some( @@ -164,12 +169,23 @@ test( "expected at least one page target to be matched to a chrome.tabs tab id", ); + const topology = await cdp.Mod.getTopology(); + assert.equal(typeof topology.rootFrameId, "string"); + assert.ok(topology.frames[topology.rootFrameId], "Mod.getTopology should include its root frame"); + assert.ok( + Object.values(topology.roots).some((root) => root.kind === "document"), + "Mod.getTopology should include at least one document root", + ); + assert.ok( + Object.values(topology.contexts).some((context) => context.world === "piercer"), + "Mod.getTopology should include a piercer execution context", + ); + await cdp.Mod.addCustomEvent({ name: cdp.Target.targetCreated }); - const transformedEvents: unknown[] = []; - cdp.on("Target.targetCreated", (params) => { - if (!hasTargetInfo(params)) return; - if (!Object.hasOwn(params.targetInfo || {}, "tabId")) return; + const transformedEvents: cdp.types.ts.Target.TargetCreatedEvent[] = []; + cdp.on(cdp.Target.targetCreated, (params) => { + if (params.targetInfo.tabId == null) return; transformedEvents.push(params); }); @@ -181,16 +197,15 @@ test( const createdTarget = await cdp.Target.createTarget({ url: "about:blank#modcdp-target-created" }); await cdp.Target.getTargets(); assert.ok( - transformedEvents.some((params) => { - if (!hasTargetInfo(params)) return; - return params.targetInfo.targetId === createdTarget.targetId; - }), + transformedEvents.some((event) => event.targetInfo.targetId === createdTarget.targetId), `expected transformed Target.targetCreated for ${createdTarget.targetId}`, ); } - const event = events["Target.targetCreated"].parse(transformedEvents[0]); - assert.ok(Object.hasOwn(event.targetInfo, "tabId"), "transformed event targetInfo should include tabId"); + assert.ok( + transformedEvents.some((event) => event.targetInfo.tabId != null), + "transformed event targetInfo should include tabId", + ); } finally { try { await cdp.Target.setDiscoverTargets({ discover: false }); diff --git a/js/test/test.ModCDPClientTypedEventInference.ts b/js/test/test.ModCDPClientTypedEventInference.ts index 07c0258b..3df0e1c9 100644 --- a/js/test/test.ModCDPClientTypedEventInference.ts +++ b/js/test/test.ModCDPClientTypedEventInference.ts @@ -1,10 +1,21 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_ModCDPClientTypedEventInference.py +// - ./go/modcdp/client/ModCDPClientTypedEventInference_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { test } from "vitest"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; +import { ModCDPClient } from "../src/index.js"; test("typed CDP event tokens infer callback payloads without local type aliases", () => { - const cdp = new ModCDPClient(); + const cdp = new ModCDPClient({ + launcher: { launcher_mode: "none" }, + upstream: { upstream_mode: "ws" }, + injector: { injector_mode: "none" }, + server_config: null, + }); const seen: string[] = []; cdp.on(cdp.Target.targetCreated, (event) => { diff --git a/js/test/test.ModCDPPayloadSchemaNormalization.ts b/js/test/test.ModCDPPayloadSchemaNormalization.ts deleted file mode 100644 index 93103163..00000000 --- a/js/test/test.ModCDPPayloadSchemaNormalization.ts +++ /dev/null @@ -1,19 +0,0 @@ -import assert from "node:assert/strict"; -import { test } from "vitest"; -import { z } from "zod"; - -import { normalizeModCDPPayloadSchema } from "../src/types/modcdp.js"; - -test("payload schema normalization accepts empty zod shapes", () => { - const schema = normalizeModCDPPayloadSchema({}); - assert.deepEqual(schema?.parse({ value: 1 }), { value: 1 }); -}); - -test("payload schema normalization rejects unsupported schema specs", () => { - assert.throws(() => normalizeModCDPPayloadSchema("not-a-schema" as never), /Unsupported payload schema/); -}); - -test("payload schema normalization accepts non-empty zod shapes", () => { - const schema = normalizeModCDPPayloadSchema({ value: z.string() }); - assert.deepEqual(schema?.parse({ value: "ok", extra: true }), { value: "ok", extra: true }); -}); diff --git a/js/test/test.NatsUpstreamTransport.ts b/js/test/test.NATSUpstreamTransport.ts similarity index 76% rename from js/test/test.NatsUpstreamTransport.ts rename to js/test/test.NATSUpstreamTransport.ts index 2f9f50e4..04a149f7 100644 --- a/js/test/test.NatsUpstreamTransport.ts +++ b/js/test/test.NATSUpstreamTransport.ts @@ -1,3 +1,8 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// NATSUpstreamTransport: TS-only NATS upstream transport coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { spawn, type ChildProcess } from "node:child_process"; import { once } from "node:events"; @@ -8,19 +13,15 @@ import { getBinaryPath } from "@eplightning/nats-server"; import { test } from "vitest"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { NatsUpstreamTransport } from "../src/transport/NatsUpstreamTransport.js"; +import { NATSUpstreamTransport } from "../src/transport/NATSUpstreamTransport.js"; test("nats upstream config owns url, subject prefix, wait timeout, and injector config", async () => { - const transport = new NatsUpstreamTransport({ + const transport = new NATSUpstreamTransport({ upstream_nats_url: "ws://127.0.0.1:4223", upstream_nats_subject_prefix: "modcdp.one", }); - assert.equal(transport.url, "ws://127.0.0.1:4223/"); - assert.equal(transport.upstream_nats_subject_prefix, "modcdp.one"); - assert.deepEqual(transport.getInjectorConfig(), { - upstream_nats_url: "ws://127.0.0.1:4223/", - upstream_nats_subject_prefix: "modcdp.one", - }); + assert.equal(transport.config.upstream_nats_url, "ws://127.0.0.1:4223"); + assert.equal(transport.config.upstream_nats_subject_prefix, "modcdp.one"); assert.equal( transport.update({ upstream_nats_url: "nats://127.0.0.1:4222", @@ -30,13 +31,13 @@ test("nats upstream config owns url, subject prefix, wait timeout, and injector }), transport, ); - assert.equal(transport.url, "nats://127.0.0.1:4222"); - assert.equal(transport.upstream_nats_subject_prefix, "modcdp.two"); + assert.equal(transport.config.upstream_nats_url, "nats://127.0.0.1:4222"); + assert.equal(transport.config.upstream_nats_subject_prefix, "modcdp.two"); await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms for NATS ModCDP peer/); }); test("nats upstream close rejects pending peer waits", async () => { - const transport = new NatsUpstreamTransport({ + const transport = new NATSUpstreamTransport({ upstream_nats_url: "ws://127.0.0.1:4223", upstream_nats_subject_prefix: "modcdp.close", upstream_nats_wait_timeout_ms: 5_000, @@ -49,7 +50,7 @@ test("nats upstream close rejects pending peer waits", async () => { }); test("nats upstream close resets peer wait state", async () => { - const transport = new NatsUpstreamTransport({ + const transport = new NATSUpstreamTransport({ upstream_nats_wait_timeout_ms: 5, }); (transport as unknown as { handlePayload: (payload: string) => void }).handlePayload( @@ -64,18 +65,18 @@ test("nats upstream close resets peer wait state", async () => { test("nats upstream reconnects after close against a real NATS server", async () => { const nats = await startNatsServer(); - const transport = new NatsUpstreamTransport({ + const transport = new NATSUpstreamTransport({ upstream_nats_url: nats.url, upstream_nats_subject_prefix: `modcdp.reconnect.${Date.now()}`, }); try { await transport.connect(); - assert.equal((transport as unknown as { connected: boolean }).connected, true); + assert.doesNotThrow(() => transport.send({ id: 1, method: "Browser.getVersion" })); await transport.close(); - assert.equal((transport as unknown as { connected: boolean }).connected, false); + assert.throws(() => transport.send({ id: 2, method: "Browser.getVersion" }), /NATS transport is not connected/); await transport.connect(); - assert.equal((transport as unknown as { connected: boolean }).connected, true); + assert.doesNotThrow(() => transport.send({ id: 3, method: "Browser.getVersion" })); } finally { await transport.close(); await nats.close(); diff --git a/js/test/test.NativeMessagingUpstreamTransport.ts b/js/test/test.NativeMessagingUpstreamTransport.ts index f51c6002..a3a4fc61 100644 --- a/js/test/test.NativeMessagingUpstreamTransport.ts +++ b/js/test/test.NativeMessagingUpstreamTransport.ts @@ -1,316 +1,24 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// NativeMessagingUpstreamTransport: TS-only native messaging upstream transport coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; -import { once } from "node:events"; -import { existsSync, readdirSync, statSync } from "node:fs"; -import { mkdtemp, rm } from "node:fs/promises"; -import net from "node:net"; -import { homedir, platform, tmpdir } from "node:os"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { test } from "vitest"; +import { describe, test } from "vitest"; import { NativeMessagingUpstreamTransport } from "../src/transport/NativeMessagingUpstreamTransport.js"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); -const NATIVE_MESSAGING_TEST_BROWSER_PATH = extensionLaunchFlagTestBrowserPath(); -const upstreamNativeMessagingHostName = (label: string) => `com.modcdp.test.${label}.${process.pid}`; +describe.sequential("NativeMessagingUpstreamTransport", () => { + test("nativemessaging upstream connects to native messaging stdio directly", async () => { + const transport = new NativeMessagingUpstreamTransport(); + assert.equal(transport.config.upstream_nativemessaging_host_name, "com.modcdp.bridge"); -test("nativemessaging upstream config owns manifest, host, wait timeout, loopback, and injector config", async () => { - const transport = new NativeMessagingUpstreamTransport({ - upstream_nativemessaging_manifest: "/tmp/modcdp-native-host.json", - upstream_nativemessaging_manifests: ["/tmp/modcdp-native-host-extra.json"], - upstream_nativemessaging_host_name: "com.modcdp.test", - injector_extension_id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - upstream_nativemessaging_wait_timeout_ms: 10, - }); - assert.deepEqual(transport.getInjectorConfig(), { - upstream_nativemessaging_host_name: "com.modcdp.test", - }); - assert.deepEqual(transport.getServerConfig(), {}); - assert.equal( - transport.update({ - cdp_url: "ws://127.0.0.1:9222/devtools/browser/test", - upstream_nativemessaging_manifests: [], - upstream_nativemessaging_host_name: "com.modcdp.updated", - upstream_nativemessaging_wait_timeout_ms: 5, - }), - transport, - ); - assert.deepEqual(transport.getServerConfig(), { - server_loopback_cdp_url: "ws://127.0.0.1:9222/devtools/browser/test", - }); - assert.deepEqual(transport.getInjectorConfig(), { - upstream_nativemessaging_host_name: "com.modcdp.updated", - }); - assert.equal( - (transport as unknown as { include_default_manifest_paths: boolean }).include_default_manifest_paths, - false, - ); - transport.update({ upstream_nativemessaging_manifest: null }); - assert.equal( - (transport as unknown as { include_default_manifest_paths: boolean }).include_default_manifest_paths, - true, - ); - transport.update({ user_data_dir: "/tmp/modcdp-profile-one" }); - transport.update({ user_data_dir: "/tmp/modcdp-profile-one" }); - transport.update({ user_data_dir: "/tmp/modcdp-profile-two" }); - assert.deepEqual( - (transport as unknown as { upstream_nativemessaging_manifests: string[] }).upstream_nativemessaging_manifests, - [ - path.join("/tmp/modcdp-profile-two", "NativeMessagingHosts", "com.modcdp.updated.json"), - path.join("/tmp/modcdp-profile-two", "Default", "NativeMessagingHosts", "com.modcdp.updated.json"), - ], - ); - assert.throws(() => transport.send({ id: 1, method: "Browser.getVersion" }), /No native messaging peer is connected/); - await assert.rejects( - () => transport.waitForPeer(), - /Timed out waiting 5ms for native messaging host com\.modcdp\.updated/, - ); -}); - -test("nativemessaging upstream close rejects pending peer waits", async () => { - const transport = new NativeMessagingUpstreamTransport({ - upstream_nativemessaging_host_name: "com.modcdp.close", - upstream_nativemessaging_wait_timeout_ms: 5_000, - }); - const pending = transport.waitForPeer(); - - await transport.close(); - - await assert.rejects( - () => pending, - /Native messaging transport for com\.modcdp\.close closed before a peer connected/, - ); -}); - -test("nativemessaging upstream close resets peer wait state", async () => { - const upstream_nativemessaging_host_name = upstreamNativeMessagingHostName("close-reset"); - const transport = new NativeMessagingUpstreamTransport({ - upstream_nativemessaging_host_name, - upstream_nativemessaging_wait_timeout_ms: 5, - }); - await transport.connect(); - const port = Number(new URL(transport.url).port); - const peer = net.createConnection({ host: "127.0.0.1", port }); - await once(peer, "connect"); - - try { - await transport.waitForPeer(); - await transport.close(); - - await assert.rejects(() => transport.waitForPeer(), { - message: `Timed out waiting 5ms for native messaging host ${upstream_nativemessaging_host_name}.`, - }); - } finally { - peer.destroy(); - await transport.close(); - } -}); - -test("nativemessaging upstream waits again after a peer disconnects", async () => { - const upstream_nativemessaging_host_name = upstreamNativeMessagingHostName("disconnect-reset"); - const transport = new NativeMessagingUpstreamTransport({ - upstream_nativemessaging_host_name, - upstream_nativemessaging_wait_timeout_ms: 5, - }); - await transport.connect(); - const port = Number(new URL(transport.url).port); - const peer = net.createConnection({ host: "127.0.0.1", port }); - await once(peer, "connect"); - - try { - await transport.waitForPeer(); - peer.destroy(); - await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); - - await assert.rejects(() => transport.waitForPeer(), { - message: `Timed out waiting 5ms for native messaging host ${upstream_nativemessaging_host_name}.`, - }); - } finally { - peer.destroy(); - await transport.close(); - } -}); - -test("nativemessaging upstream accepts a replacement peer after disconnect", async () => { - const upstream_nativemessaging_host_name = upstreamNativeMessagingHostName("replacement"); - const transport = new NativeMessagingUpstreamTransport({ - upstream_nativemessaging_host_name, - upstream_nativemessaging_wait_timeout_ms: 500, - }); - await transport.connect(); - const port = Number(new URL(transport.url).port); - const first_peer = net.createConnection({ host: "127.0.0.1", port }); - await once(first_peer, "connect"); - - try { - await transport.waitForPeer(); - first_peer.destroy(); - await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); - - const second_peer = net.createConnection({ host: "127.0.0.1", port }); - await once(second_peer, "connect"); try { + await transport.connect(); await transport.waitForPeer(); + await transport.close(); } finally { - second_peer.destroy(); - } - } finally { - first_peer.destroy(); - await transport.close(); - } -}); - -test("nativemessaging upstream installs the launch-profile native host manifest and connects to a real extension", async () => { - const upstream_nativemessaging_host_name = "com.modcdp.bridge"; - const profile_dir = await mkdtemp(path.join(tmpdir(), "modcdp.native.")); - const native_client = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { - headless: true, - user_data_dir: profile_dir, - cleanup_user_data_dir: true, - // Native messaging is browser -> client only. After explicit CHROME_PATH - // and CI /usr/bin/chromium, this test uses Chrome for Testing because - // Canary rejects --load-extension in this local test path. - executable_path: NATIVE_MESSAGING_TEST_BROWSER_PATH, - }, - }, - upstream: { - upstream_mode: "nativemessaging", - upstream_nativemessaging_host_name: upstream_nativemessaging_host_name, - }, - injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - server: { - server_routes: { "*.*": "loopback_cdp" }, - }, - }); - - try { - await native_client.connect(); - assert.equal(native_client.transport?.mode, "nativemessaging"); - assert.equal(native_client.upstream_endpoint_kind, "modcdp_server"); - assert.match(native_client.transport?.url ?? "", /^native:\/\/.+@127\.0\.0\.1:\d+$/); - assert.equal(native_client.transport?.url?.startsWith(`native://${upstream_nativemessaging_host_name}@`), true); - assert.equal( - existsSync( - path.join( - native_client._launched?.profile_dir ?? "", - "NativeMessagingHosts", - `${upstream_nativemessaging_host_name}.json`, - ), - ), - true, - ); - const version = (await native_client.send("Browser.getVersion")) as Record; - assert.equal(typeof version.product, "string"); - await new Promise((resolve) => setTimeout(resolve, 1_500)); - const second_version = (await native_client.send("Browser.getVersion")) as Record; - assert.equal(typeof second_version.product, "string"); - } finally { - await native_client.close(); - await rm(profile_dir, { recursive: true, force: true }); - } -}, 90_000); - -async function waitFor(predicate: () => boolean, timeout_ms = 2_000) { - const deadline = Date.now() + timeout_ms; - while (Date.now() < deadline) { - if (predicate()) return; - await new Promise((resolve) => setTimeout(resolve, 20)); - } - throw new Error("Timed out waiting for condition"); -} - -function extensionLaunchFlagTestBrowserPath() { - const explicit_candidates = [process.env.CHROME_PATH, platform() === "linux" ? "/usr/bin/chromium" : null].filter( - (candidate): candidate is string => Boolean(candidate), - ); - for (const candidate of explicit_candidates) { - if (existsSync(candidate)) return candidate; - } - const home = homedir(); - const patterns = - platform() === "darwin" - ? [ - path.join( - home, - "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", - ), - path.join(home, "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), - path.join( - home, - "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", - ), - ] - : platform() === "win32" - ? [ - path.join( - process.env.LOCALAPPDATA || path.join(home, "AppData/Local"), - "ms-playwright/chromium-*/chrome-win*/chrome.exe", - ), - path.join(home, ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), - ] - : [ - path.join(home, ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), - "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", - path.join(home, ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), - ]; - const candidates = newestFirst(patterns.flatMap(expandGlob)); - if (candidates[0]) return candidates[0]; - throw new Error("Native messaging tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing."); -} - -function expandGlob(pattern: string) { - const normalized = path.normalize(pattern); - const { root } = path.parse(normalized); - const parts = normalized.slice(root.length).split(path.sep).filter(Boolean); - let candidates = [root || "."]; - for (const part of parts) { - const has_wildcard = part.includes("*"); - const matcher = has_wildcard ? wildcardToRegExp(part) : null; - const next: string[] = []; - for (const base of candidates) { - if (!existsSync(base)) continue; - if (!has_wildcard) { - const candidate = path.join(base, part); - if (existsSync(candidate)) next.push(candidate); - continue; - } - for (const child of readdirSync(base)) { - if (matcher!.test(child)) next.push(path.join(base, child)); - } + await transport.close(); } - candidates = next; - } - return candidates.filter((candidate) => existsSync(candidate)); -} - -function wildcardToRegExp(value: string) { - return new RegExp(`^${value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`); -} - -function newestFirst(candidates: string[]) { - return [...new Set(candidates)].sort((a, b) => { - const left = scorePath(a); - const right = scorePath(b); - return right.version - left.version || right.mtime - left.mtime || a.localeCompare(b); }); -} - -function scorePath(candidate: string) { - const numbers = candidate.match(/\d+/g)?.map(Number) ?? []; - const version = numbers.length > 0 ? Math.max(...numbers) : 0; - let mtime = 0; - try { - mtime = statSync(candidate).mtimeMs; - } catch {} - return { version, mtime }; -} +}); diff --git a/js/test/test.NoneBrowserLauncher.ts b/js/test/test.NoneBrowserLauncher.ts new file mode 100644 index 00000000..e9b7c9de --- /dev/null +++ b/js/test/test.NoneBrowserLauncher.ts @@ -0,0 +1,19 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_NoneBrowserLauncher.py +// - ./go/modcdp/launcher/NoneBrowserLauncher_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { test } from "vitest"; + +import { NoneBrowserLauncher } from "../src/launcher/NoneBrowserLauncher.js"; + +test("NoneBrowserLauncher records an empty launched browser", async () => { + const launcher = new NoneBrowserLauncher(); + const launched = await launcher.launch(); + + assert.equal(launched.cdp_url, null); + assert.equal(launcher.launched, launched); + await launched.close(); +}); diff --git a/js/test/test.NoopBrowserLauncher.ts b/js/test/test.NoopBrowserLauncher.ts deleted file mode 100644 index 92b19c20..00000000 --- a/js/test/test.NoopBrowserLauncher.ts +++ /dev/null @@ -1,19 +0,0 @@ -import assert from "node:assert/strict"; -import { test } from "vitest"; - -import { NoopBrowserLauncher } from "../src/launcher/NoopBrowserLauncher.js"; - -test("NoopBrowserLauncher records an empty launched browser", async () => { - const launcher = new NoopBrowserLauncher(); - const launched = await launcher.launch(); - - assert.equal(launched.cdp_url, null); - assert.equal(launcher.launched, launched); - assert.deepEqual(launcher.getTransportConfig(), { - cdp_url: null, - user_data_dir: null, - pipe_read: null, - pipe_write: null, - }); - await launched.close(); -}); diff --git a/js/test/test.PipeUpstreamTransport.ts b/js/test/test.PipeUpstreamTransport.ts index 915e8a2b..7b1a15ff 100644 --- a/js/test/test.PipeUpstreamTransport.ts +++ b/js/test/test.PipeUpstreamTransport.ts @@ -1,24 +1,19 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// PipeUpstreamTransport: TS-only pipe upstream transport coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; -import path from "node:path"; import { PassThrough } from "node:stream"; -import { fileURLToPath } from "node:url"; import { test } from "vitest"; import { PipeUpstreamTransport } from "../src/transport/PipeUpstreamTransport.js"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); test("pipe upstream constructor, update, launcher config, and unconnected errors match the transport surface", async () => { - const transport = new PipeUpstreamTransport({ cdp_url: "pipe://constructor" }); - assert.equal(transport.mode, "pipe"); - assert.equal(transport.endpoint_kind, "raw_cdp"); - assert.equal(transport.url, "pipe://constructor"); - assert.deepEqual(transport.getLauncherConfig(), { remote_debugging: "pipe" }); - assert.equal(transport.update({ cdp_url: "pipe://1234" }), transport); - assert.equal(transport.url, "pipe://1234"); - await assert.rejects(() => transport.connect(), /upstream\.upstream_mode=pipe requires/); + const transport = new PipeUpstreamTransport(); + assert.equal(transport.config.upstream_mode, "pipe"); + assert.equal(transport.update(), transport); + await assert.rejects(() => transport.connect(), /upstream_mode=pipe requires/); assert.throws(() => transport.send({ id: 1, method: "Runtime.evaluate" }), /CDP pipe is not connected/); }); @@ -26,12 +21,19 @@ test("pipe upstream resets connection state after pipe end and errors", async () for (const event_name of ["end", "read_error", "write_error"] as const) { const pipe_read = new PassThrough(); const pipe_write = new PassThrough(); - const transport = new PipeUpstreamTransport({ pipe_read, pipe_write, cdp_url: "pipe://test" }); + const transport = new PipeUpstreamTransport({ + upstream_pipe_read: pipe_read, + upstream_pipe_write: pipe_write, + }); const closed: Error[] = []; transport.onClose((error) => closed.push(error)); await transport.connect(); - transport.send({ id: 1, method: "Runtime.evaluate", params: { expression: "1" } }); + transport.send({ + id: 1, + method: "Runtime.evaluate", + params: { expression: "1" }, + }); if (event_name === "end") pipe_read.emit("end"); else if (event_name === "read_error") pipe_read.emit("error", new Error("read failed")); @@ -39,42 +41,14 @@ test("pipe upstream resets connection state after pipe end and errors", async () assert.equal(closed.length, 1); assert.throws( - () => transport.send({ id: 2, method: "Runtime.evaluate", params: { expression: "1" } }), + () => + transport.send({ + id: 2, + method: "Runtime.evaluate", + params: { expression: "1" }, + }), /CDP pipe is not connected/, ); await transport.close(); } }); - -test("pipe upstream launches a real browser and uses a pid-scoped pipe URL", async () => { - const cdp = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { headless: true }, - }, - upstream: { upstream_mode: "pipe" }, - injector: { - injector_mode: "inject", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - server: { server_routes: { "*.*": "chrome_debugger" } }, - }); - - try { - await cdp.connect(); - assert.equal(cdp.transport?.mode, "pipe"); - assert.equal(cdp.upstream_endpoint_kind, "raw_cdp"); - assert.match(cdp.cdp_url ?? "", /^pipe:\/\/\d+$/); - assert.equal(cdp.transport?.url, cdp.cdp_url); - await cdp.Mod.addCustomCommand("Custom.runtimeReadyState", { - expression: - "async () => await cdp.send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true })", - }); - const runtime = (await cdp.send("Custom.runtimeReadyState")) as { result?: { value?: unknown } }; - assert.equal(runtime.result?.value, "complete"); - } finally { - await cdp.close(); - } -}, 60_000); diff --git a/js/test/test.RemoteBrowserLauncher.ts b/js/test/test.RemoteBrowserLauncher.ts index b9bcb641..29f6edbd 100644 --- a/js/test/test.RemoteBrowserLauncher.ts +++ b/js/test/test.RemoteBrowserLauncher.ts @@ -1,15 +1,21 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_RemoteBrowserLauncher.py +// - ./go/modcdp/launcher/RemoteBrowserLauncher_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import { describe, expect, it } from "vitest"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; import { RemoteBrowserLauncher } from "../src/launcher/RemoteBrowserLauncher.js"; -import { CdpSocket, expectCdpBrowserSurface } from "./helpers.BrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; const LIVE_BROWSER_TIMEOUT_MS = 60_000; describe("RemoteBrowserLauncher", () => { - it("requires an upstream cdp_url", async () => { + it("requires launcher_remote_cdp_url", async () => { await expect(new RemoteBrowserLauncher().launch()).rejects.toThrow( - "launcher.launcher_mode=remote requires upstream_cdp_url.", + "launcher_mode=remote requires launcher_remote_cdp_url.", ); }); @@ -18,57 +24,58 @@ describe("RemoteBrowserLauncher", () => { { timeout: LIVE_BROWSER_TIMEOUT_MS }, async () => { const local = await new LocalBrowserLauncher().launch({ - port: await LocalBrowserLauncher.freePort(), - headless: true, - chrome_ready_timeout_ms: 45_000, + launcher_local_cdp_listen_port: await LocalBrowserLauncher.freePort(), + launcher_local_headless: true, + launcher_local_chrome_ready_timeout_ms: 45_000, }); - let cdp: CdpSocket | null = null; + const cdp = new WSUpstreamTransport(); try { - const http_endpoint = `http://127.0.0.1:${local.port}`; - const bare_endpoint = `127.0.0.1:${local.port}`; - const fromHttp = await new RemoteBrowserLauncher({}, http_endpoint).launch(); + const http_endpoint = `http://127.0.0.1:${local.cdp_listen_port}`; + const bare_endpoint = `127.0.0.1:${local.cdp_listen_port}`; + const fromHttp = await new RemoteBrowserLauncher({ launcher_remote_cdp_url: http_endpoint }).launch(); expect(fromHttp.cdp_url).toBe(local.cdp_url); - cdp = await CdpSocket.connect(fromHttp.cdp_url!); + cdp.update({ upstream_ws_cdp_url: fromHttp.cdp_url }); + await cdp.connect(); await expectCdpBrowserSurface(cdp); await fromHttp.close(); - const fromBare = await new RemoteBrowserLauncher({}, bare_endpoint).launch(); + const fromBare = await new RemoteBrowserLauncher({ launcher_remote_cdp_url: bare_endpoint }).launch(); expect(fromBare.cdp_url).toBe(local.cdp_url); await fromBare.close(); - const fromOptions = await new RemoteBrowserLauncher({ - cdp_url: local.cdp_url, + const fromConfig = await new RemoteBrowserLauncher({ + launcher_remote_cdp_url: local.cdp_url, }).launch(); - expect(fromOptions.cdp_url).toBe(local.cdp_url); - await fromOptions.close(); + expect(fromConfig.cdp_url).toBe(local.cdp_url); + await fromConfig.close(); const fromWs = await new RemoteBrowserLauncher().launch({ - cdp_url: local.cdp_url, + launcher_remote_cdp_url: local.cdp_url, }); expect(fromWs.cdp_url).toBe(local.cdp_url); await expectCdpBrowserSurface(cdp); await fromWs.close(); } finally { - await cdp?.close(); + await cdp.close(); await local.close(); } }, ); - it("lets launch options override constructor cdp_url", { timeout: LIVE_BROWSER_TIMEOUT_MS }, async () => { + it("lets launch config override constructor cdp_url", { timeout: LIVE_BROWSER_TIMEOUT_MS }, async () => { const first = await new LocalBrowserLauncher().launch({ - port: await LocalBrowserLauncher.freePort(), - headless: true, + launcher_local_cdp_listen_port: await LocalBrowserLauncher.freePort(), + launcher_local_headless: true, }); const second = await new LocalBrowserLauncher().launch({ - port: await LocalBrowserLauncher.freePort(), - headless: true, + launcher_local_cdp_listen_port: await LocalBrowserLauncher.freePort(), + launcher_local_headless: true, }); try { - const launched = await new RemoteBrowserLauncher({ cdp_url: first.cdp_url }).launch({ - cdp_url: `127.0.0.1:${second.port}`, + const launched = await new RemoteBrowserLauncher({ launcher_remote_cdp_url: first.cdp_url }).launch({ + launcher_remote_cdp_url: `127.0.0.1:${second.cdp_listen_port}`, }); expect(launched.cdp_url).toBe(second.cdp_url); await launched.close(); @@ -78,3 +85,30 @@ describe("RemoteBrowserLauncher", () => { } }); }); + +// MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +// Keep the setup semantics above 1:1 with translated tests; helpers here only use real ModCDP transports against real browser endpoints. +async function expectCdpBrowserSurface(cdp: WSUpstreamTransport) { + const version = await cdp.send("Browser.getVersion"); + expect(version.product).toEqual(expect.stringMatching(/Chrome|Chromium/)); + expect(version.protocolVersion).toEqual(expect.any(String)); + + const created = await cdp.send("Target.createTarget", { url: "about:blank#modcdp-launcher-test" }); + expect(created.targetId).toEqual(expect.any(String)); + const targetId = created.targetId as string; + + try { + const attached = await cdp.send("Target.attachToTarget", { targetId, flatten: true }); + expect(attached.sessionId).toEqual(expect.any(String)); + const sessionId = attached.sessionId as string; + await cdp.send("Runtime.enable", {}, sessionId); + const evaluated = await cdp.send( + "Runtime.evaluate", + { expression: "(() => ({ ok: true, value: 42 }))()", returnByValue: true }, + sessionId, + ); + expect(evaluated.result).toMatchObject({ type: "object", value: { ok: true, value: 42 } }); + } finally { + await cdp.send("Target.closeTarget", { targetId }).catch(() => ({})); + } +} diff --git a/js/test/test.ReverseWSUpstreamTransport.ts b/js/test/test.ReverseWSUpstreamTransport.ts new file mode 100644 index 00000000..2082d417 --- /dev/null +++ b/js/test/test.ReverseWSUpstreamTransport.ts @@ -0,0 +1,136 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// ReverseWSUpstreamTransport: TS-only reverse websocket upstream transport coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { once } from "node:events"; +import WebSocket from "ws"; +import { test } from "vitest"; + +import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; +import { ReverseWSUpstreamTransport } from "../src/transport/ReverseWSUpstreamTransport.js"; + +test("reversews client transport config owns bind updates and wait timeout", async () => { + const transport = new ReverseWSUpstreamTransport({ + upstream_reversews_bind: "127.0.0.1:29292", + upstream_reversews_wait_timeout_ms: 10, + }); + assert.equal(transport.endpoint_url, "ws://127.0.0.1:29292"); + assert.equal( + transport.update({ + upstream_reversews_bind: "127.0.0.1:29293", + upstream_reversews_wait_timeout_ms: 5, + }), + transport, + ); + assert.equal(transport.endpoint_url, "ws://127.0.0.1:29293"); + assert.throws( + () => transport.send({ id: 1, method: "Browser.getVersion" }), + /No reverse ModCDP extension peer is connected/, + ); + await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); +}); + +test("reversews client transport close rejects pending peer waits", async () => { + const reverse_port = await LocalBrowserLauncher.freePort(); + const transport = new ReverseWSUpstreamTransport({ + upstream_reversews_bind: `127.0.0.1:${reverse_port}`, + upstream_reversews_wait_timeout_ms: 5_000, + }); + const pending = transport.waitForPeer(); + + await transport.close(); + + await assert.rejects( + () => pending, + new RegExp(`Reverse websocket transport at ws://127\\.0\\.0\\.1:${reverse_port} closed before a peer connected`), + ); +}); + +test("reversews client transport close resets peer wait state", async () => { + const reverse_port = await LocalBrowserLauncher.freePort(); + const transport = new ReverseWSUpstreamTransport({ + upstream_reversews_bind: `127.0.0.1:${reverse_port}`, + upstream_reversews_wait_timeout_ms: 5, + }); + await transport.connect(); + const peer = new WebSocket(transport.endpoint_url); + await once(peer, "open"); + peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "test-peer", version: 1 })); + + try { + await transport.waitForPeer(); + assert.deepEqual(transport.peer_info, { type: "modcdp.reverse.hello", role: "test-peer", version: 1 }); + await transport.close(); + + await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); + assert.equal(transport.peer_info, null); + } finally { + peer.close(); + await transport.close(); + } +}); + +test("reversews client transport waits again after a peer disconnects", async () => { + const reverse_port = await LocalBrowserLauncher.freePort(); + const transport = new ReverseWSUpstreamTransport({ + upstream_reversews_bind: `127.0.0.1:${reverse_port}`, + upstream_reversews_wait_timeout_ms: 5, + }); + await transport.connect(); + const peer = new WebSocket(transport.endpoint_url); + await once(peer, "open"); + peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "test-peer", version: 1 })); + + try { + await transport.waitForPeer(); + peer.close(); + await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); + + await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); + } finally { + peer.close(); + await transport.close(); + } +}); + +test("reversews client transport accepts a replacement peer after disconnect", async () => { + const reverse_port = await LocalBrowserLauncher.freePort(); + const transport = new ReverseWSUpstreamTransport({ + upstream_reversews_bind: `127.0.0.1:${reverse_port}`, + upstream_reversews_wait_timeout_ms: 500, + }); + await transport.connect(); + const first_peer = new WebSocket(transport.endpoint_url); + await once(first_peer, "open"); + first_peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "first-peer", version: 1 })); + + try { + await transport.waitForPeer(); + first_peer.close(); + await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); + + const second_peer = new WebSocket(transport.endpoint_url); + await once(second_peer, "open"); + second_peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "second-peer", version: 1 })); + try { + await transport.waitForPeer(); + assert.equal(transport.peer_info?.role, "second-peer"); + } finally { + second_peer.close(); + } + } finally { + first_peer.close(); + await transport.close(); + } +}); + +async function waitFor(predicate: () => boolean, timeout_ms = 2_000) { + const deadline = Date.now() + timeout_ms; + while (Date.now() < deadline) { + if (predicate()) return; + await new Promise((resolve) => setTimeout(resolve, 20)); + } + throw new Error("Timed out waiting for condition"); +} diff --git a/js/test/test.ReverseWebSocketUpstreamTransport.ts b/js/test/test.ReverseWebSocketUpstreamTransport.ts deleted file mode 100644 index 31042559..00000000 --- a/js/test/test.ReverseWebSocketUpstreamTransport.ts +++ /dev/null @@ -1,275 +0,0 @@ -import assert from "node:assert/strict"; -import { once } from "node:events"; -import { existsSync, readdirSync, statSync } from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { homedir, platform } from "node:os"; -import WebSocket from "ws"; -import { test } from "vitest"; - -import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { ReverseWebSocketUpstreamTransport } from "../src/transport/ReverseWebSocketUpstreamTransport.js"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); -const REVERSEWS_TEST_BROWSER_PATH = reversewsTestBrowserPath(); - -test("reversews upstream config owns bind updates and wait timeout", async () => { - const transport = new ReverseWebSocketUpstreamTransport({ - upstream_reversews_bind: "127.0.0.1:29292", - upstream_reversews_wait_timeout_ms: 10, - }); - assert.equal(transport.url, "ws://127.0.0.1:29292"); - assert.deepEqual(transport.getInjectorConfig(), {}); - assert.equal( - transport.update({ - upstream_reversews_bind: "127.0.0.1:29293", - upstream_reversews_wait_timeout_ms: 5, - }), - transport, - ); - assert.equal(transport.url, "ws://127.0.0.1:29293"); - assert.deepEqual(transport.getInjectorConfig(), {}); - assert.throws( - () => transport.send({ id: 1, method: "Browser.getVersion" }), - /No reverse ModCDP extension peer is connected/, - ); - await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); -}); - -test("reversews upstream close rejects pending peer waits", async () => { - const reverse_port = await LocalBrowserLauncher.freePort(); - const transport = new ReverseWebSocketUpstreamTransport({ - upstream_reversews_bind: `127.0.0.1:${reverse_port}`, - upstream_reversews_wait_timeout_ms: 5_000, - }); - const pending = transport.waitForPeer(); - - await transport.close(); - - await assert.rejects( - () => pending, - new RegExp(`Reverse websocket transport at ws://127\\.0\\.0\\.1:${reverse_port} closed before a peer connected`), - ); -}); - -test("reversews upstream close resets peer wait state", async () => { - const reverse_port = await LocalBrowserLauncher.freePort(); - const transport = new ReverseWebSocketUpstreamTransport({ - upstream_reversews_bind: `127.0.0.1:${reverse_port}`, - upstream_reversews_wait_timeout_ms: 5, - }); - await transport.connect(); - const peer = new WebSocket(transport.url); - await once(peer, "open"); - peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "test-peer", version: 1 })); - - try { - await transport.waitForPeer(); - assert.deepEqual(transport.peer_info, { type: "modcdp.reverse.hello", role: "test-peer", version: 1 }); - await transport.close(); - - await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); - assert.equal(transport.peer_info, null); - } finally { - peer.close(); - await transport.close(); - } -}); - -test("reversews upstream waits again after a peer disconnects", async () => { - const reverse_port = await LocalBrowserLauncher.freePort(); - const transport = new ReverseWebSocketUpstreamTransport({ - upstream_reversews_bind: `127.0.0.1:${reverse_port}`, - upstream_reversews_wait_timeout_ms: 5, - }); - await transport.connect(); - const peer = new WebSocket(transport.url); - await once(peer, "open"); - peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "test-peer", version: 1 })); - - try { - await transport.waitForPeer(); - peer.close(); - await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); - - await assert.rejects(() => transport.waitForPeer(), /Timed out waiting 5ms/); - } finally { - peer.close(); - await transport.close(); - } -}); - -test("reversews upstream accepts a replacement peer after disconnect", async () => { - const reverse_port = await LocalBrowserLauncher.freePort(); - const transport = new ReverseWebSocketUpstreamTransport({ - upstream_reversews_bind: `127.0.0.1:${reverse_port}`, - upstream_reversews_wait_timeout_ms: 500, - }); - await transport.connect(); - const first_peer = new WebSocket(transport.url); - await once(first_peer, "open"); - first_peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "first-peer", version: 1 })); - - try { - await transport.waitForPeer(); - first_peer.close(); - await waitFor(() => (transport as unknown as { socket: unknown | null }).socket === null); - - const second_peer = new WebSocket(transport.url); - await once(second_peer, "open"); - second_peer.send(JSON.stringify({ type: "modcdp.reverse.hello", role: "second-peer", version: 1 })); - try { - await transport.waitForPeer(); - assert.equal(transport.peer_info?.role, "second-peer"); - } finally { - second_peer.close(); - } - } finally { - first_peer.close(); - await transport.close(); - } -}); - -test("reversews upstream accepts a real extension reverse connection and routes CDP through chrome debugger", async () => { - const cdp = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { - headless: process.platform === "linux" && !process.env.DISPLAY, - // Reversews is browser -> client only. After explicit CHROME_PATH and - // CI /usr/bin/chromium, these tests use Chrome for Testing because - // Canary rejects --load-extension in this local test path. - executable_path: REVERSEWS_TEST_BROWSER_PATH, - }, - }, - upstream: { upstream_mode: "reversews" }, - injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - injector_service_worker_probe_timeout_ms: 1_000, - }, - }); - - try { - await cdp.connect(); - assert.equal(cdp.transport?.mode, "reversews"); - assert.equal(cdp.upstream_endpoint_kind, "modcdp_server"); - assert.equal(cdp.transport?.url, "ws://127.0.0.1:29292"); - assert.equal( - (cdp.transport as ReverseWebSocketUpstreamTransport).peer_info?.extension_id, - "mdedooklbnfejodmnhmkdpkaedafkehf", - ); - const evaluated = (await cdp.send("Runtime.evaluate", { - expression: "location.href", - returnByValue: true, - })) as { result?: { value?: unknown } }; - assert.equal(evaluated.result?.value, "about:blank"); - await new Promise((resolve) => setTimeout(resolve, 1_500)); - const second_evaluated = (await cdp.send("Runtime.evaluate", { - expression: "document.readyState", - returnByValue: true, - })) as { result?: { value?: unknown } }; - assert.equal(second_evaluated.result?.value, "complete"); - } finally { - await cdp.close(); - } -}, 60_000); - -function reversewsTestBrowserPath() { - const explicit_candidates = [process.env.CHROME_PATH, platform() === "linux" ? "/usr/bin/chromium" : null].filter( - (candidate): candidate is string => Boolean(candidate), - ); - for (const candidate of explicit_candidates) { - if (existsSync(candidate)) return candidate; - } - const home = homedir(); - const patterns = - platform() === "darwin" - ? [ - path.join( - home, - "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", - ), - path.join(home, "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), - path.join( - home, - "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", - ), - ] - : platform() === "win32" - ? [ - path.join( - process.env.LOCALAPPDATA || path.join(home, "AppData/Local"), - "ms-playwright/chromium-*/chrome-win*/chrome.exe", - ), - path.join(home, ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), - ] - : [ - path.join(home, ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), - "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", - path.join(home, ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), - ]; - const candidates = newestFirst(patterns.flatMap(expandGlob)); - if (candidates[0]) return candidates[0]; - throw new Error("Reversews tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing."); -} - -function expandGlob(pattern: string) { - const normalized = path.normalize(pattern); - const { root } = path.parse(normalized); - const parts = normalized.slice(root.length).split(path.sep).filter(Boolean); - let candidates = [root || "."]; - for (const part of parts) { - const has_wildcard = part.includes("*"); - const matcher = has_wildcard ? wildcardToRegExp(part) : null; - const next: string[] = []; - for (const base of candidates) { - if (!existsSync(base)) continue; - if (!has_wildcard) { - const candidate = path.join(base, part); - if (existsSync(candidate)) next.push(candidate); - continue; - } - for (const child of readdirSync(base)) { - if (matcher!.test(child)) next.push(path.join(base, child)); - } - } - candidates = next; - } - return candidates.filter((candidate) => existsSync(candidate)); -} - -function wildcardToRegExp(value: string) { - return new RegExp(`^${value.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`); -} - -function newestFirst(candidates: string[]) { - return [...new Set(candidates)].sort((a, b) => { - const left = scorePath(a); - const right = scorePath(b); - return right.version - left.version || right.mtime - left.mtime || a.localeCompare(b); - }); -} - -function scorePath(candidate: string) { - const numbers = candidate.match(/\d+/g)?.map(Number) ?? []; - const version = numbers.length > 0 ? Math.max(...numbers) : 0; - let mtime = 0; - try { - mtime = statSync(candidate).mtimeMs; - } catch {} - return { version, mtime }; -} - -async function waitFor(predicate: () => boolean, timeout_ms = 2_000) { - const deadline = Date.now() + timeout_ms; - while (Date.now() < deadline) { - if (predicate()) return; - await new Promise((resolve) => setTimeout(resolve, 20)); - } - throw new Error("Timed out waiting for condition"); -} diff --git a/js/test/test.UpstreamTransport.ts b/js/test/test.UpstreamTransport.ts index a5551221..db616e69 100644 --- a/js/test/test.UpstreamTransport.ts +++ b/js/test/test.UpstreamTransport.ts @@ -1,23 +1,21 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_UpstreamTransport.py +// - ./go/modcdp/transport/UpstreamTransport_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import { describe, expect, it } from "vitest"; -import { UpstreamTransport, endpointKindForUpstream, parseHostPort } from "../src/transport/UpstreamTransport.js"; +import { UpstreamTransport, parseHostPort } from "../src/transport/UpstreamTransport.js"; describe("UpstreamTransport", () => { - it("owns shared transport config, endpoint classification, and recv callbacks", async () => { + it("owns shared transport config and recv callbacks", async () => { const transport = new UpstreamTransport(); const received: unknown[] = []; const stop = transport.onRecv((message) => received.push(message)); - expect(endpointKindForUpstream("ws")).toBe("raw_cdp"); - expect(endpointKindForUpstream("pipe")).toBe("raw_cdp"); - expect(endpointKindForUpstream("nativemessaging")).toBe("modcdp_server"); - expect(endpointKindForUpstream("reversews")).toBe("modcdp_server"); - expect(endpointKindForUpstream("nats")).toBe("modcdp_server"); expect(parseHostPort("127.0.0.1:29292", "0.0.0.0", 80)).toEqual({ host: "127.0.0.1", port: 29292 }); expect(transport.update()).toBe(transport); - expect(transport.getLauncherConfig()).toEqual({}); - expect(transport.getInjectorConfig()).toEqual({}); - expect(transport.getServerConfig()).toEqual({}); class TestTransport extends UpstreamTransport { emit(value: unknown) { diff --git a/js/test/test.WSUpstreamTransport.ts b/js/test/test.WSUpstreamTransport.ts new file mode 100644 index 00000000..24109f97 --- /dev/null +++ b/js/test/test.WSUpstreamTransport.ts @@ -0,0 +1,77 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_WSUpstreamTransport.py +// - ./go/modcdp/transport/WSUpstreamTransport_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +import assert from "node:assert/strict"; +import { test } from "vitest"; + +import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; + +test("ws upstream constructor, update, server config, and unconnected errors match the transport surface", async () => { + const transport = new WSUpstreamTransport(); + assert.equal(transport.config.upstream_ws_cdp_url, undefined); + assert.equal(transport.update({ upstream_ws_cdp_url: "ws://127.0.0.1:1/devtools/browser/test" }), transport); + assert.equal(transport.config.upstream_ws_cdp_url, "ws://127.0.0.1:1/devtools/browser/test"); + const unconfigured = new WSUpstreamTransport(); + await assert.rejects(() => unconfigured.connect(), /WSUpstreamTransport requires/); + assert.throws(() => unconfigured.send({ id: 1, method: "Browser.getVersion" }), /CDP websocket is not connected/); +}); + +test("ws upstream launches a real browser and speaks raw CDP", async () => { + const chrome = await new LocalBrowserLauncher({ launcher_local_headless: true }).launch(); + const transport = new WSUpstreamTransport({ upstream_ws_cdp_url: chrome.cdp_url }); + + try { + await transport.connect(); + assert.match(transport.config.upstream_ws_cdp_url ?? "", /^ws:\/\//); + const version = (await transport.send("Browser.getVersion")) as Record; + assert.equal(typeof version.product, "string"); + } finally { + await transport.close(); + await chrome.close(); + } +}, 60_000); + +test("ws upstream resolves a bare host:port CDP endpoint to the browser websocket", async () => { + const chrome = await new LocalBrowserLauncher({ + launcher_local_headless: true, + }).launch(); + const transport = new WSUpstreamTransport({ upstream_ws_cdp_url: `127.0.0.1:${chrome.cdp_listen_port}` }); + + try { + const response = new Promise>((resolve) => { + transport.onRecv((message) => { + if ("id" in message && message.id === 1) resolve(message as Record); + }); + }); + await transport.connect(); + assert.match(transport.config.upstream_ws_cdp_url ?? "", /^ws:\/\//); + transport.send({ id: 1, method: "Browser.getVersion", params: {} }); + const message = await response; + assert.equal(typeof (message.result as Record).product, "string"); + } finally { + await transport.close(); + await chrome.close(); + } +}, 60_000); + +test("ws upstream close clears connection state", async () => { + const chrome = await new LocalBrowserLauncher({ + launcher_local_headless: true, + }).launch(); + const transport = new WSUpstreamTransport({ upstream_ws_cdp_url: chrome.cdp_url }); + + try { + await transport.connect(); + assert.ok(transport.ws); + await transport.close(); + assert.equal(transport.ws, null); + assert.throws(() => transport.send({ id: 1, method: "Browser.getVersion" }), /CDP websocket is not connected/); + } finally { + await transport.close(); + await chrome.close(); + } +}, 60_000); diff --git a/js/test/test.WebSocketUpstreamTransport.ts b/js/test/test.WebSocketUpstreamTransport.ts deleted file mode 100644 index 1f2ef988..00000000 --- a/js/test/test.WebSocketUpstreamTransport.ts +++ /dev/null @@ -1,120 +0,0 @@ -import assert from "node:assert/strict"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { test } from "vitest"; - -import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; -import { WebSocketUpstreamTransport } from "../src/transport/WebSocketUpstreamTransport.js"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; - -const HERE = path.dirname(fileURLToPath(import.meta.url)); -const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); - -test("ws upstream constructor, update, server config, and unconnected errors match the transport surface", async () => { - const transport = new WebSocketUpstreamTransport(); - assert.equal(transport.url, ""); - assert.deepEqual(transport.getServerConfig(), {}); - assert.equal(transport.update({ cdp_url: "ws://127.0.0.1:1/devtools/browser/test" }), transport); - assert.equal(transport.url, "ws://127.0.0.1:1/devtools/browser/test"); - assert.deepEqual(transport.getServerConfig(), { server_loopback_cdp_url: "ws://127.0.0.1:1/devtools/browser/test" }); - const unconfigured = new WebSocketUpstreamTransport(); - await assert.rejects(() => unconfigured.connect(), /upstream\.upstream_mode=ws requires/); - assert.throws(() => unconfigured.send({ id: 1, method: "Browser.getVersion" }), /CDP websocket is not connected/); -}); - -test("ws upstream launches a real browser and speaks raw CDP", async () => { - const cdp = new ModCDPClient({ - launcher: { - launcher_mode: "local", - launcher_options: { headless: true }, - }, - upstream: { upstream_mode: "ws" }, - injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, - injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], - injector_trust_service_worker_target: true, - }, - }); - - try { - await cdp.connect(); - assert.equal(cdp.transport?.mode, "ws"); - assert.equal(cdp.upstream_endpoint_kind, "raw_cdp"); - assert.equal(cdp.connect_timing?.upstream_mode, "ws"); - assert.equal(cdp.connect_timing?.upstream_endpoint_kind, "raw_cdp"); - const connect_timing = cdp.connect_timing as - | { - transport_connected_at: number; - transport_duration_ms: number; - transport_started_at: number; - } - | undefined; - assert.equal( - connect_timing?.transport_duration_ms, - (connect_timing?.transport_connected_at ?? 0) - (connect_timing?.transport_started_at ?? 0), - ); - assert.match(cdp.cdp_url ?? "", /^ws:\/\//); - const version = (await cdp.sendRaw("Browser.getVersion")) as Record; - assert.equal(typeof version.product, "string"); - await new Promise((resolve) => setTimeout(resolve, 1_500)); - const target_infos = ( - (await cdp.sendRaw("Target.getTargets")) as { targetInfos?: { type?: string; url?: string }[] } - ).targetInfos; - assert.equal( - target_infos?.some( - (target) => target.type === "service_worker" && target.url?.endsWith("/modcdp/service_worker.js"), - ), - true, - ); - assert.equal( - await cdp.Mod.evaluate({ - expression: "Boolean(globalThis.ModCDP?.handleCommand && chrome.runtime.getURL('modcdp/service_worker.js'))", - }), - true, - ); - } finally { - await cdp.close(); - } -}, 60_000); - -test("ws upstream resolves a bare host:port CDP endpoint to the browser websocket", async () => { - const chrome = await new LocalBrowserLauncher({ - headless: true, - }).launch(); - const transport = new WebSocketUpstreamTransport({ cdp_url: `127.0.0.1:${chrome.port}` }); - - try { - const response = new Promise>((resolve) => { - transport.onRecv((message) => { - if ("id" in message && message.id === 1) resolve(message as Record); - }); - }); - await transport.connect(); - assert.match(transport.url ?? "", /^ws:\/\//); - transport.send({ id: 1, method: "Browser.getVersion", params: {} }); - const message = await response; - assert.equal(typeof (message.result as Record).product, "string"); - } finally { - await transport.close(); - await chrome.close(); - } -}, 60_000); - -test("ws upstream close clears connection state", async () => { - const chrome = await new LocalBrowserLauncher({ - headless: true, - }).launch(); - const transport = new WebSocketUpstreamTransport({ cdp_url: chrome.cdp_url }); - - try { - await transport.connect(); - assert.ok(transport.ws); - await transport.close(); - assert.equal(transport.ws, null); - assert.throws(() => transport.send({ id: 1, method: "Browser.getVersion" }), /CDP websocket is not connected/); - } finally { - await transport.close(); - await chrome.close(); - } -}, 60_000); diff --git a/js/test/test.proxy.ts b/js/test/test.proxy.ts index e0c9190c..40385f0d 100644 --- a/js/test/test.proxy.ts +++ b/js/test/test.proxy.ts @@ -1,3 +1,8 @@ +// MODCDP_TS_ONLY_TEST: DO NOT TRANSLATE THIS TEST FILE TO OTHER LANGUAGES. +// proxy: TS-only proxy process coverage. +// If a translated sibling is added, all test cases, descriptions, covered edge cases, and setup must be kept perfectly 1:1 in sync. +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { spawn, type ChildProcess } from "node:child_process"; import { once } from "node:events"; @@ -8,15 +13,15 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { test } from "vitest"; -import { ModCDPClient } from "../src/client/ModCDPClient.js"; +import { ModCDPClient } from "../src/index.js"; import { LocalBrowserLauncher } from "../src/launcher/LocalBrowserLauncher.js"; import { startProxy } from "../src/proxy/proxy.js"; -import { CdpSocket } from "./helpers.BrowserLauncher.js"; +import { WSUpstreamTransport } from "../src/transport/WSUpstreamTransport.js"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const EXTENSION_PATH = path.resolve(HERE, "..", "..", "dist", "extension"); const LOCAL_TEST_LAUNCH_OPTIONS = { - headless: true, + launcher_local_headless: true, }; const REVERSEWS_TEST_BROWSER_PATH = reversewsTestBrowserPath(); @@ -62,9 +67,10 @@ async function removeTree(pathname: string) { } async function expectProxyCdpWorks(proxy_url: string, transport: string) { - const cdp = await CdpSocket.connect(proxy_url); + const cdp = new WSUpstreamTransport({ upstream_ws_cdp_url: proxy_url }); let target_id: string | null = null; try { + await cdp.connect(); const evaluated = await cdp.send("Mod.evaluate", { expression: `({ ok: true, transport: ${JSON.stringify(transport)} })`, }); @@ -83,7 +89,7 @@ async function expectProxyCdpWorks(proxy_url: string, transport: string) { await cdp.send("Mod.addCustomCommand", { name: "Custom.runtimeReadyState", expression: - "async () => await cdp.send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true })", + "async () => await upstream.send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true })", }); const runtime = await cdp.send("Custom.runtimeReadyState"); assert.equal((runtime.result as { value?: unknown } | undefined)?.value, "complete"); @@ -107,18 +113,19 @@ async function expectProxyCdpWorks(proxy_url: string, transport: string) { test("proxy upgrades a vanilla CDP websocket to ModCDP against a real browser over ws upstream", async () => { const proxy_port = await LocalBrowserLauncher.freePort(); const proxy = await startProxy({ - port: proxy_port, + proxy_listen_port: proxy_port, launcher: { launcher_mode: "local", - launcher_options: LOCAL_TEST_LAUNCH_OPTIONS, + ...LOCAL_TEST_LAUNCH_OPTIONS, + launcher_local_executable_path: REVERSEWS_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, }, - server: { - server_routes: { "*.*": "loopback_cdp" }, + server_config: { + router: { router_routes: { "*.*": "loopback_cdp" } }, }, }); @@ -132,18 +139,19 @@ test("proxy upgrades a vanilla CDP websocket to ModCDP against a real browser ov test("proxy upgrades a vanilla CDP websocket to ModCDP against a real browser over pipe upstream", async () => { const proxy_port = await LocalBrowserLauncher.freePort(); const proxy = await startProxy({ - port: proxy_port, + proxy_listen_port: proxy_port, launcher: { launcher_mode: "local", - launcher_options: LOCAL_TEST_LAUNCH_OPTIONS, + ...LOCAL_TEST_LAUNCH_OPTIONS, + launcher_local_executable_path: REVERSEWS_TEST_BROWSER_PATH, }, - upstream: { upstream_mode: "pipe" }, + upstream: { upstream_mode: "pipe" } as any, injector: { - injector_mode: "inject", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, }, - server: { - server_routes: { "*.*": "chrome_debugger" }, + server_config: { + router: { router_routes: { "*.*": "chromedebugger" } }, }, }); @@ -161,21 +169,23 @@ test("proxy CLI maps user-facing flags into a real pipe upstream browser session process.execPath, [ proxy_script, - "--port", - String(proxy_port), + "--bind", + `127.0.0.1:${proxy_port}`, "--launcher-mode=local", - "--launcher-options", - JSON.stringify({ headless: true }), + "--launcher-local-executable-path", + REVERSEWS_TEST_BROWSER_PATH, + "--launcher-local-headless", + "true", "--upstream-mode=pipe", - "--injector-mode=inject", - "--injector-extension-path", + "--injector-mode=cli", + "--injector-cli-extension-path", EXTENSION_PATH, "--injector-service-worker-url-suffixes", JSON.stringify(["/modcdp/service_worker.js"]), "--injector-trust-service-worker-target", "true", - "--server-routes", - JSON.stringify({ "*.*": "chrome_debugger" }), + "--server-config", + JSON.stringify({ router: { router_routes: { "*.*": "chromedebugger" } } }), ], { stdio: ["ignore", "pipe", "pipe"] }, ); @@ -192,34 +202,31 @@ test("proxy CLI maps local ws launch without requiring upstream ws url", async ( const proxy_port = await LocalBrowserLauncher.freePort(); const proxy_script = path.resolve(HERE, "..", "..", "dist", "js", "src", "proxy", "proxy.js"); const user_data_dir = await mkdtemp(path.join(tmpdir(), "modcdp-proxy-profile-")); - const executable_path = LocalBrowserLauncher.findChromeBinary(); const proc = spawn( process.execPath, [ proxy_script, - "--port", - String(proxy_port), + "--bind", + `127.0.0.1:${proxy_port}`, "--launcher-mode=local", - "--launcher-executable-path", - executable_path, - "--launcher-user-data-dir", + "--launcher-local-executable-path", + REVERSEWS_TEST_BROWSER_PATH, + "--launcher-local-user-data-dir", user_data_dir, - "--launcher-options", - JSON.stringify({ headless: true }), + "--launcher-local-headless", + "true", "--upstream-mode=ws", - "--injector-mode=auto", - "--injector-extension-path", + "--injector-mode=cli", + "--injector-cli-extension-path", EXTENSION_PATH, - "--client", + "--router-routes", JSON.stringify({ - client_routes: { - "Mod.*": "service_worker", - "Custom.*": "service_worker", - "*.*": "direct_cdp", - }, + "Mod.*": "service_worker", + "Custom.*": "service_worker", + "*.*": "direct_cdp", }), - "--server", - JSON.stringify({ server_routes: { "*.*": "loopback_cdp" } }), + "--server-config", + JSON.stringify({ router: { router_routes: { "*.*": "loopback_cdp" } } }), ], { stdio: ["ignore", "pipe", "pipe"] }, ); @@ -237,12 +244,13 @@ test("proxy CLI maps ws upstream URL and route shorthands into an existing real const owner = new ModCDPClient({ launcher: { launcher_mode: "local", - launcher_options: LOCAL_TEST_LAUNCH_OPTIONS, + ...LOCAL_TEST_LAUNCH_OPTIONS, + launcher_local_executable_path: REVERSEWS_TEST_BROWSER_PATH, }, upstream: { upstream_mode: "ws" }, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, }, @@ -254,21 +262,23 @@ test("proxy CLI maps ws upstream URL and route shorthands into an existing real process.execPath, [ proxy_script, - "--port", - String(proxy_port), + "--bind", + `127.0.0.1:${proxy_port}`, "--launcher-mode=remote", + "--launcher-remote-cdp-url", + owner.upstream.config.upstream_ws_cdp_url!, "--upstream-mode=ws", - "--upstream-cdp-url", - owner.cdp_url!, + "--upstream-ws-cdp-url", + owner.upstream.config.upstream_ws_cdp_url!, "--injector-mode=discover", - "--client-routes", + "--router-routes", JSON.stringify({ "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp", }), - "--server-routes", - JSON.stringify({ "*.*": "loopback_cdp" }), + "--server-config", + JSON.stringify({ router: { router_routes: { "*.*": "loopback_cdp" } } }), ], { stdio: ["ignore", "pipe", "pipe"] }, ); @@ -289,22 +299,18 @@ test("proxy CLI maps user-facing flags into a real reversews browser session", a process.execPath, [ proxy_script, - "--port", - String(proxy_port), + "--bind", + `127.0.0.1:${proxy_port}`, "--launcher-mode=local", - "--launcher-options", - JSON.stringify({ - ...LOCAL_TEST_LAUNCH_OPTIONS, - // Reversews is browser -> client only. After explicit CHROME_PATH and - // CI /usr/bin/chromium, these tests use Chrome for Testing because - // Canary rejects --load-extension in this local test path. - executable_path: REVERSEWS_TEST_BROWSER_PATH, - }), + "--launcher-local-headless", + String(LOCAL_TEST_LAUNCH_OPTIONS.launcher_local_headless), + "--launcher-local-executable-path", + REVERSEWS_TEST_BROWSER_PATH, "--upstream-mode=reversews", "--upstream-reversews-wait-timeout-ms", "10000", - "--injector-mode=auto", - "--injector-extension-path", + "--injector-mode=cli", + "--injector-cli-extension-path", EXTENSION_PATH, "--injector-service-worker-url-suffixes", JSON.stringify(["/modcdp/service_worker.js"]), @@ -324,24 +330,25 @@ test("proxy CLI maps user-facing flags into a real reversews browser session", a } }, 90_000); -test("proxy upgrades a vanilla CDP websocket to ModCDP against a real browser over reversews upstream", async () => { +test("proxy upgrades a vanilla CDP websocket to ModCDP against a real browser over reversews client transport", async () => { const proxy_port = await LocalBrowserLauncher.freePort(); const proxy = await startProxy({ - port: proxy_port, + proxy_listen_port: proxy_port, launcher: { launcher_mode: "local", - launcher_options: { - ...LOCAL_TEST_LAUNCH_OPTIONS, - // Reversews is browser -> client only. After explicit CHROME_PATH and - // CI /usr/bin/chromium, these tests use Chrome for Testing because - // Canary rejects --load-extension in this local test path. - executable_path: REVERSEWS_TEST_BROWSER_PATH, - }, + ...LOCAL_TEST_LAUNCH_OPTIONS, + // Reversews is browser -> client only. After explicit CHROME_PATH and + // CI /usr/bin/chromium, these tests use Chrome for Testing because + // Canary rejects --load-extension in this local test path. + launcher_local_executable_path: REVERSEWS_TEST_BROWSER_PATH, }, - upstream: { upstream_mode: "reversews", upstream_reversews_wait_timeout_ms: 10_000 }, + upstream: { + upstream_mode: "reversews", + upstream_reversews_wait_timeout_ms: 10_000, + } as any, injector: { - injector_mode: "auto", - injector_extension_path: EXTENSION_PATH, + injector_mode: "cli", + injector_cli_extension_path: EXTENSION_PATH, injector_service_worker_url_suffixes: ["/modcdp/service_worker.js"], injector_trust_service_worker_target: true, injector_service_worker_probe_timeout_ms: 1_000, diff --git a/js/test/test.translate.ts b/js/test/test.translate.ts index df9981fd..d6b4fe50 100644 --- a/js/test/test.translate.ts +++ b/js/test/test.translate.ts @@ -1,3 +1,9 @@ +// MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +// All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +// - ./python/tests/test_translate.py +// - ./go/modcdp/translate/translate_test.go +// NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +// USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. import assert from "node:assert/strict"; import { test } from "vitest"; @@ -11,8 +17,20 @@ import { } from "../src/translate/translate.js"; test("translate routes, wraps, and unwraps ModCDP protocol messages deterministically", () => { - assert.equal(routeFor("Browser.getVersion", { "Browser.*": "direct_cdp", "*.*": "service_worker" }), "direct_cdp"); - assert.equal(routeFor("Target.getTargets", { "Browser.*": "direct_cdp", "*.*": "service_worker" }), "service_worker"); + assert.equal( + routeFor("Browser.getVersion", { + "Browser.*": "direct_cdp", + "*.*": "service_worker", + }), + "direct_cdp", + ); + assert.equal( + routeFor("Target.getTargets", { + "Browser.*": "direct_cdp", + "*.*": "service_worker", + }), + "service_worker", + ); const direct = wrapCommandIfNeeded("Browser.getVersion", {}, { routes: { "*.*": "direct_cdp" } }); assert.equal(direct.target, "direct_cdp"); @@ -25,13 +43,26 @@ test("translate routes, wraps, and unwraps ModCDP protocol messages deterministi ); assert.equal(wrapped.target, "service_worker"); assert.equal(wrapped.steps[0]?.method, "Runtime.callFunctionOn"); - const wrapped_step_params = wrapped.steps[0]?.params as { functionDeclaration?: unknown } | undefined; - assert.match(String(wrapped_step_params?.functionDeclaration), /attachToSession\("session-1"\)/); - assert.equal(wrapped.steps[0]?.unwrap, "runtime"); + const wrapped_step_params = wrapped.steps[0]?.params as + | { arguments?: Array<{ value?: unknown }>; functionDeclaration?: unknown } + | undefined; + assert.match(String(wrapped_step_params?.functionDeclaration), /globalThis\.ModCDP\.handleCommand/); + assert.deepEqual(JSON.parse(String(wrapped_step_params?.arguments?.[1]?.value)), { + expression: "({ ok: true })", + params: { value: 1 }, + }); + assert.equal(wrapped_step_params?.arguments?.[2]?.value, "session-1"); + assert.equal(wrapped.steps[0]?.unwrap, "runtime_json"); - const configured = wrapCommandIfNeeded("Mod.configure", { server: { server_routes: { "*.*": "loopback_cdp" } } }); + const configured = wrapCommandIfNeeded("Mod.configure", { + router: { router_routes: { "*.*": "loopback_cdp" } }, + }); assert.equal(configured.steps[0]?.unwrap, "runtime_json"); + const ping = wrapCommandIfNeeded("Mod.ping", {}); + const ping_step_params = ping.steps[0]?.params as { arguments?: Array<{ value?: unknown }> } | undefined; + assert.deepEqual(JSON.parse(String(ping_step_params?.arguments?.[1]?.value)), {}); + const custom = wrapCommandIfNeeded( "Custom.echo", { secret: "x".repeat(100), nested: { ok: true } }, @@ -49,10 +80,22 @@ test("translate routes, wraps, and unwraps ModCDP protocol messages deterministi }); assert.equal(custom_step_params?.arguments?.[2]?.value, "session-1"); + const customWithSession = wrapCommandIfNeeded( + "Custom.echo", + { secret: "targeted" }, + { cdpSessionId: "target-session-1" }, + ); + const custom_with_session_params = customWithSession.steps[0]?.params as + | { arguments?: Array<{ value?: unknown }> } + | undefined; + assert.equal(custom_with_session_params?.arguments?.[2]?.value, "target-session-1"); + assert.deepEqual(unwrapResponseIfNeeded({ result: { type: "object", value: { ok: true } } }, "runtime"), { ok: true, }); - assert.deepEqual(unwrapResponseIfNeeded({ product: "Chrome/1" }, null), { product: "Chrome/1" }); + assert.deepEqual(unwrapResponseIfNeeded({ product: "Chrome/1" }, null), { + product: "Chrome/1", + }); const payload = encodeBindingPayload({ event: "Custom.ready", @@ -68,5 +111,11 @@ test("translate routes, wraps, and unwraps ModCDP protocol messages deterministi ), { event: "Custom.ready", data: { ready: true }, sessionId: "session-2" }, ); - assert.equal(unwrapEventIfNeeded("Runtime.consoleAPICalled", { name: CUSTOM_EVENT_BINDING_NAME, payload }), null); + assert.equal( + unwrapEventIfNeeded("Runtime.consoleAPICalled", { + name: CUSTOM_EVENT_BINDING_NAME, + payload, + }), + null, + ); }); diff --git a/package.json b/package.json index 4e754f56..90e974e6 100644 --- a/package.json +++ b/package.json @@ -49,25 +49,33 @@ "types": "./dist/js/src/launcher/RemoteBrowserLauncher.d.ts", "import": "./dist/js/src/launcher/RemoteBrowserLauncher.js" }, - "./launcher/BrowserbaseBrowserLauncher.js": { - "types": "./dist/js/src/launcher/BrowserbaseBrowserLauncher.d.ts", - "import": "./dist/js/src/launcher/BrowserbaseBrowserLauncher.js" + "./launcher/BBBrowserLauncher.js": { + "types": "./dist/js/src/launcher/BBBrowserLauncher.d.ts", + "import": "./dist/js/src/launcher/BBBrowserLauncher.js" }, - "./launcher/NoopBrowserLauncher.js": { - "types": "./dist/js/src/launcher/NoopBrowserLauncher.d.ts", - "import": "./dist/js/src/launcher/NoopBrowserLauncher.js" + "./launcher/NoneBrowserLauncher.js": { + "types": "./dist/js/src/launcher/NoneBrowserLauncher.d.ts", + "import": "./dist/js/src/launcher/NoneBrowserLauncher.js" }, "./transport/UpstreamTransport.js": { "types": "./dist/js/src/transport/UpstreamTransport.d.ts", "import": "./dist/js/src/transport/UpstreamTransport.js" }, - "./transport/WebSocketUpstreamTransport.js": { - "types": "./dist/js/src/transport/WebSocketUpstreamTransport.d.ts", - "import": "./dist/js/src/transport/WebSocketUpstreamTransport.js" + "./transport/DownstreamTransport.js": { + "types": "./dist/js/src/transport/DownstreamTransport.d.ts", + "import": "./dist/js/src/transport/DownstreamTransport.js" }, - "./transport/ReverseWebSocketUpstreamTransport.js": { - "types": "./dist/js/src/transport/ReverseWebSocketUpstreamTransport.d.ts", - "import": "./dist/js/src/transport/ReverseWebSocketUpstreamTransport.js" + "./transport/DownstreamTransportSet.js": { + "types": "./dist/js/src/transport/DownstreamTransportSet.d.ts", + "import": "./dist/js/src/transport/DownstreamTransportSet.js" + }, + "./transport/WSUpstreamTransport.js": { + "types": "./dist/js/src/transport/WSUpstreamTransport.d.ts", + "import": "./dist/js/src/transport/WSUpstreamTransport.js" + }, + "./transport/ReverseWSUpstreamTransport.js": { + "types": "./dist/js/src/transport/ReverseWSUpstreamTransport.d.ts", + "import": "./dist/js/src/transport/ReverseWSUpstreamTransport.js" }, "./transport/NativeMessagingUpstreamTransport.js": { "types": "./dist/js/src/transport/NativeMessagingUpstreamTransport.d.ts", @@ -77,33 +85,49 @@ "types": "./dist/js/src/transport/PipeUpstreamTransport.d.ts", "import": "./dist/js/src/transport/PipeUpstreamTransport.js" }, - "./transport/NatsUpstreamTransport.js": { - "types": "./dist/js/src/transport/NatsUpstreamTransport.d.ts", - "import": "./dist/js/src/transport/NatsUpstreamTransport.js" + "./transport/NATSUpstreamTransport.js": { + "types": "./dist/js/src/transport/NATSUpstreamTransport.d.ts", + "import": "./dist/js/src/transport/NATSUpstreamTransport.js" + }, + "./transport/ChromeDebuggerUpstreamTransport.js": { + "types": "./dist/js/src/transport/ChromeDebuggerUpstreamTransport.d.ts", + "import": "./dist/js/src/transport/ChromeDebuggerUpstreamTransport.js" + }, + "./transport/ReverseWSDownstreamTransport.js": { + "types": "./dist/js/src/transport/ReverseWSDownstreamTransport.d.ts", + "import": "./dist/js/src/transport/ReverseWSDownstreamTransport.js" + }, + "./transport/NativeMessagingDownstreamTransport.js": { + "types": "./dist/js/src/transport/NativeMessagingDownstreamTransport.d.ts", + "import": "./dist/js/src/transport/NativeMessagingDownstreamTransport.js" + }, + "./transport/NATSDownstreamTransport.js": { + "types": "./dist/js/src/transport/NATSDownstreamTransport.d.ts", + "import": "./dist/js/src/transport/NATSDownstreamTransport.js" }, "./injector/ExtensionInjector.js": { "types": "./dist/js/src/injector/ExtensionInjector.d.ts", "import": "./dist/js/src/injector/ExtensionInjector.js" }, - "./injector/DiscoveredExtensionInjector.js": { - "types": "./dist/js/src/injector/DiscoveredExtensionInjector.d.ts", - "import": "./dist/js/src/injector/DiscoveredExtensionInjector.js" + "./injector/DiscoverExtensionInjector.js": { + "types": "./dist/js/src/injector/DiscoverExtensionInjector.d.ts", + "import": "./dist/js/src/injector/DiscoverExtensionInjector.js" }, - "./injector/LocalBrowserLaunchExtensionInjector.js": { - "types": "./dist/js/src/injector/LocalBrowserLaunchExtensionInjector.d.ts", - "import": "./dist/js/src/injector/LocalBrowserLaunchExtensionInjector.js" + "./injector/CLIExtensionInjector.js": { + "types": "./dist/js/src/injector/CLIExtensionInjector.d.ts", + "import": "./dist/js/src/injector/CLIExtensionInjector.js" }, - "./injector/BBBrowserExtensionInjector.js": { - "types": "./dist/js/src/injector/BBBrowserExtensionInjector.d.ts", - "import": "./dist/js/src/injector/BBBrowserExtensionInjector.js" + "./injector/BBExtensionInjector.js": { + "types": "./dist/js/src/injector/BBExtensionInjector.d.ts", + "import": "./dist/js/src/injector/BBExtensionInjector.js" }, - "./injector/ExtensionsLoadUnpackedInjector.js": { - "types": "./dist/js/src/injector/ExtensionsLoadUnpackedInjector.d.ts", - "import": "./dist/js/src/injector/ExtensionsLoadUnpackedInjector.js" + "./injector/CDPExtensionInjector.js": { + "types": "./dist/js/src/injector/CDPExtensionInjector.d.ts", + "import": "./dist/js/src/injector/CDPExtensionInjector.js" }, - "./injector/BorrowedExtensionInjector.js": { - "types": "./dist/js/src/injector/BorrowedExtensionInjector.d.ts", - "import": "./dist/js/src/injector/BorrowedExtensionInjector.js" + "./injector/BorrowExtensionInjector.js": { + "types": "./dist/js/src/injector/BorrowExtensionInjector.d.ts", + "import": "./dist/js/src/injector/BorrowExtensionInjector.js" }, "./router/AutoSessionRouter.js": { "types": "./dist/js/src/router/AutoSessionRouter.d.ts", @@ -136,7 +160,7 @@ "build:clean": "pnpm run clean && pnpm run build", "build:package": "tsc -p tsconfig.json && rm -rf dist/js/test && rm -f dist/.gitignore", "build:extension-assets": "node js/scripts/build-extension-assets.mjs", - "build:python-readme": "mkdir -p python && if [ README.md -ef python/README.md ]; then :; else cp README.md python/README.md; fi", + "build:python-readme": "test -f python/README.md", "typecheck": "tsc -p tsconfig.json --noEmit", "lint": "pnpm exec prek run --all-files", "format": "pnpm exec prek run oxfmt --all-files", @@ -148,7 +172,7 @@ "demo:proxy:puppeteer": "pnpm run build && node dist/js/examples/puppeteer.js", "proxy": "pnpm run build && node dist/js/src/proxy/cli.js", "test": "pnpm run build && vitest run --fileParallelism=false --maxWorkers=1", - "test:launchers": "vitest run js/test/test.LocalBrowserLauncher.ts js/test/test.RemoteBrowserLauncher.ts js/test/test.BrowserbaseBrowserLauncher.ts", + "test:launchers": "vitest run js/test/test.LocalBrowserLauncher.ts js/test/test.RemoteBrowserLauncher.ts js/test/test.BBBrowserLauncher.ts", "pack:npm": "pnpm run build && npm pack --ignore-scripts", "prepare": "pnpm run build" }, diff --git a/python/README.md b/python/README.md index 82bb61c8..1c397076 100644 --- a/python/README.md +++ b/python/README.md @@ -34,13 +34,16 @@ You can send `Mod.*`, `Custom.*`, etc. through standard Playwright/Puppeteer/oth import { ModCDPClient } from "modcdp"; import { z } from "zod"; -const upstream_cdp_url = "http://127.0.0.1:9222"; // host:port, http(s), and ws(s) URLs work +const upstream_ws_cdp_url = "http://127.0.0.1:9222"; // host:port, http(s), and ws(s) URLs work const cdp = new ModCDPClient({ launcher: { launcher_mode: "remote" }, - upstream: { upstream_mode: "ws", upstream_cdp_url }, - injector: { injector_mode: "auto" }, - client: { client_routes: { "Target.getTargets": "service_worker" } }, - server: { server_loopback_cdp_url: upstream_cdp_url, server_routes: { "*.*": "loopback_cdp" } }, + upstream: { upstream_mode: "ws", upstream_ws_cdp_url }, + injector: { injector_mode: "discover" }, + router: { router_routes: { "Target.getTargets": "service_worker" } }, + server_config: { + upstream: { upstream_ws_cdp_url }, + router: { router_routes: { "*.*": "loopback_cdp" } }, + }, }); await cdp.connect(); @@ -118,22 +121,19 @@ Each demo launches Chrome with the fixed ModCDP extension artifact loaded, headf ```sh pnpm run demo:js # defaults to --loopback --upstream=ws pnpm run demo:js -- --debugger --upstream=pipe -pnpm run demo:js -- --loopback --upstream=reversews -pnpm run demo:js -- --loopback --upstream=nativemessaging pnpm run demo:python pnpm run demo:go ``` -The Python package is managed with `uv`; `pnpm run demo:python` runs the built demo through `uv` and does not require a separate `pip install`. Set `CHROME_PATH=/path/to/chromium` to force a specific browser binary. +The Python package is managed with `uv`; `pnpm run demo:python` runs the built demo through `uv` and does not require a separate `pip install`. Set `CHROME_PATH=/path/to/chromium` to force a specific browser binary. Native messaging mode requires Chrome to launch the configured native host; it is not a standalone shell demo mode. ## Transparent Proxy Upgrade any vanilla CDP client like Stagehand, Playwright, or Puppeteer transparently with support for `Mod.*` / `Custom.*` commands and events. ```sh -pnpm run proxy -- --upstream-mode=ws --upstream-cdp-url=http://127.0.0.1:9222 --port 9223 +pnpm run proxy -- --upstream-mode=ws --upstream-ws-cdp-url=http://127.0.0.1:9222 --port 9223 pnpm run proxy -- --launcher-mode=local --upstream-mode=pipe --port 9223 -pnpm run proxy -- --launcher-mode=local --upstream-mode=nativemessaging --port 9223 pnpm run proxy -- --launcher-mode=local --upstream-mode=nats --upstream-nats-url=ws://127.0.0.1:4223 --port 9223 # const browser = await playwright.chromium.connectOverCDP("http://127.0.0.1:9223") # const session = await browser.contexts()[0].newCDPSession(page) @@ -141,24 +141,9 @@ pnpm run proxy -- --launcher-mode=local --upstream-mode=nats --upstream-nats-url # ✨ All ModCDP commands now work through playwright! you can modify/extend playwright behavior to your heart's content ``` -The proxy uses the same `--launcher-*`, `--injector-*`, `--upstream-*`, `--client='{"client_routes": {...}}'`, and `--server='{"server_routes": {...}}'` option groups as `ModCDPClient`. `--launcher-options='{...}'` passes launcher-owned options such as `headless` and `sandbox`; `--client-routes='{...}'` and `--server-routes='{...}'` are route-only shorthands. `ws` keeps a transparent websocket-to-websocket fast path; `pipe`, `nativemessaging`, `nats`, and launched `reversews` proxy downstream CDP-shaped messages through the selected `ModCDPClient` upstream transport. +The proxy uses the same `--launcher-*`, `--injector-*`, `--upstream-*`, `--client-config='{"client_cdp_send_timeout_ms": 10000}'`, `--router='{"router_routes": {...}}'`, and `--server-config='{"router": {"router_routes": {...}}}'` config groups as `ModCDPClient`. CLI flags use kebab case and map to the owner-prefixed config fields, for example `--launcher-local-executable-path` maps to `launcher.launcher_local_executable_path`. `ws` keeps a transparent websocket-to-websocket fast path; `pipe`, `nativemessaging`, and `nats` proxy downstream CDP-shaped messages through the selected client-side `ModCDPClient` transport. -Native messaging mode creates the local native host wrapper and browser manifest on each run. The fixed extension auto-dials the default `com.modcdp.bridge` host, so the 1:1 automatic path does not require preinstalling a host binary at a fixed path. `--upstream-nativemessaging-manifest`, `--upstream-nativemessaging-manifests`, and `--upstream-nativemessaging-host-name` are for custom browser profiles, externally configured extensions, or isolated transport-level tests; the default auto-injected extension expects `com.modcdp.bridge`. - -### Reverse proxy mode - -Use reverse mode when the browser does not expose a public CDP websocket to the final client, but the ModCDP extension can open a websocket back to a local proxy. The proxy still serves a normal-looking CDP endpoint to Playwright, Puppeteer, Stagehand, or any other CDP client: - -```sh -pnpm run proxy -- --upstream-mode=reversews --port 9223 -pnpm run proxy -- --launcher-mode=local --upstream-mode=reversews --port 9223 -pnpm run proxy -- --launcher-mode=local --upstream-mode=reversews --upstream-reversews-bind=127.0.0.1:29293 --port 9223 -# const browser = await playwright.chromium.connectOverCDP("http://127.0.0.1:9223") -``` - -Reverse mode is opt-in. The shipped extension auto-connects to the fixed local reverse connector at `ws://127.0.0.1:29292`; the proxy/client listens there and waits for that extension connection. Keep `--upstream-reversews-bind` when using a custom extension build whose compiled autoconnect URL points at a different host or port. `--upstream-reversews-wait-timeout-ms` controls how long the proxy/client waits. Once connected, the extension identifies itself as a ModCDP service worker and the proxy uses that reverse websocket as its upstream. `Mod.*`, expression-backed `Custom.*` commands, custom event fanout, middleware, and normal CDP commands all stay routed through `globalThis.ModCDP.handleCommand(...)` in the service worker. - -Reverse mode is intentionally scoped to one local browser and one reverse extension connection per proxy process. The browser may still have other extensions installed; ModCDP does not require `--disable-extensions-except`. +Native messaging mode uses the configured browser native host name directly. The baked extension expects the default `com.modcdp.bridge` host, so changing `--upstream-nativemessaging-host-name` requires using an extension build that was baked for that host. Because Chrome owns native host process launch and stdio, native messaging is not a standalone `pnpm run proxy` mode. ## Routing modes @@ -170,7 +155,7 @@ Reverse mode is intentionally scoped to one local browser and one reverse extens | `--debugger` | client → SW → `chrome.debugger.sendCommand` against the active tab | The browser exposes no remote CDP port and you only have extension permissions. | | `--direct` | client → sends non-ModCDP commands to browser CDP directly | You already have a CDP endpoint and don't need extension interception. | -Pass via `client: { client_routes: { "*.*": "direct_cdp" | "service_worker" } }` and `server: { server_routes: { "*.*": "loopback_cdp" | "chrome_debugger" } }`. The demos default to `--loopback` (the most powerful mode). +Pass via `router: { router_routes: { "*.*": "direct_cdp" | "service_worker" } }` and `server_config: { router: { router_routes: { "*.*": "loopback_cdp" | "chromedebugger" } } }`. The demos default to `--loopback` (the most powerful mode). ## Repository layout @@ -215,8 +200,8 @@ dist/ Built JS output used by the extension and Node CLI scr ## Requirements -- Stock Google Chrome can be used without relaunch flags: visit `chrome://inspect/#remote-debugging` to expose the current browser at `http://127.0.0.1:9222`, and load/install the ModCDP extension in that profile. Pass that endpoint as `upstream: { upstream_mode: "ws", upstream_cdp_url: "http://127.0.0.1:9222" }`. -- Automated/test browsers can still preload the extension with `--load-extension=`. `Extensions.loadUnpacked` is used as a fallback when the connected browser exposes it over CDP. +- Stock Google Chrome can be used without relaunch flags: visit `chrome://inspect/#remote-debugging` to expose the current browser at `http://127.0.0.1:9222`, and load/install the ModCDP extension in that profile. Pass that endpoint as `upstream: { upstream_mode: "ws", upstream_ws_cdp_url: "http://127.0.0.1:9222" }`. +- Automated/test browsers need the extension-loading path that their Chrome build actually supports. Chrome for Testing currently supports `--load-extension=` and may not expose `Extensions.loadUnpacked`; Canary 150 exposes `Extensions.loadUnpacked` and does not load this MV3 extension through `--load-extension` in the local headless test path. - Node ≥ 22, Python ≥ 3.11 with `websocket-client`, Go ≥ 1.25 with `gobwas/ws`. --- @@ -226,13 +211,11 @@ dist/ Built JS output used by the extension and Node CLI scr ### Connect -1. Select a `launcher` class and an `upstream` transport. `launcher.launcher_mode="local"` starts a local browser, `launcher.launcher_mode="remote"` uses the supplied `upstream.upstream_cdp_url`, and `launcher.launcher_mode="none"` leaves browser lifecycle outside ModCDP. +1. Select a `launcher` class and an `upstream` transport. `launcher.launcher_mode="local"` starts a local browser, `launcher.launcher_mode="remote"` uses the supplied `upstream.upstream_ws_cdp_url`, and `launcher.launcher_mode="none"` leaves browser lifecycle outside ModCDP. 2. The configured extension injector classes try discovery, launch-arg injection, Browserbase upload, `Extensions.loadUnpacked`, or borrowing in the configured order. 3. Attach a session to that SW target and `Runtime.enable` on it. 4. Call `globalThis.ModCDP.configure(...)` to push the resolved loopback websocket and any explicit server route overrides into the SW. The clients do this automatically by default. -Reverse proxy mode flips the bootstrap direction: `js/src/proxy/proxy.ts --upstream-mode=reversews --port 9223` listens for the extension on the configured reverse connector while still serving downstream clients from the proxy port. The service worker sends a `modcdp.reverse.hello` message and then accepts CDP-shaped command messages from the proxy. The proxy maps downstream request IDs to reverse request IDs and forwards reverse events back to the downstream CDP client. - ### Send - `Mod.evaluate({ expression, params, cdpSessionId })` → `Runtime.evaluate` on the ext session, wrapping the expression with an IIFE that exposes `params` and `cdp = ModCDP.attachToSession(...)`. @@ -241,21 +224,17 @@ Reverse proxy mode flips the bootstrap direction: `js/src/proxy/proxy.ts --upstr - `Mod.addMiddleware({ name, phase, expression })` → `Runtime.evaluate` registering a service-worker middleware for `phase: "request" | "response" | "event"`. Use `name: "*"` to match every method/event in that phase, or pass generated names like `cdp.Target.targetInfoChanged`. - `Custom.X(params)` → `Runtime.evaluate` calling `globalThis.ModCDP.handleCommand("Custom.X", params, cdpSessionId)`. -In reverse mode, expression-backed commands cannot use `new Function` directly because MV3 extension CSP blocks unsafe eval. The service worker evaluates user expressions by attaching `chrome.debugger` to its own service-worker target and issuing `Runtime.evaluate`, preserving the normal ModCDP expression surface without weakening extension CSP. - ### Receive When SW handlers `cdp.emit('Custom.X', payload)`, the SW invokes `globalThis.__ModCDP_custom_event__(JSON.stringify({ event, data, cdpSessionId }))`. CDP delivers `Runtime.bindingCalled` on the ext session; the client (or proxy) decodes the payload and re-dispatches it as a normal `cdp.on('Custom.X', ...)` event. -In reverse mode, the same `publishEvent(...)` path also sends CDP-shaped event messages over the reverse websocket, so custom events and mirrored upstream events fan out through the standalone proxy to the downstream client. - ### Why this works `Runtime.addBinding` is the only out-of-page → in-page → out-of-page channel CDP exposes. Combined with one extension service worker (which gets `chrome.*` access as a side effect of being in an extension), you get: - A guaranteed JS execution context that's not a page, with the right permissions - A way to push named events back through the same CDP socket your client already speaks -- The same command/event surface whether the bytes arrive by websocket, pipe, native messaging, reverse websocket, or NATS. +- The same command/event surface whether the bytes arrive by websocket, pipe, native messaging, or NATS. @@ -263,24 +242,24 @@ In reverse mode, the same `publishEvent(...)` path also sends CDP-shaped event m Routing details ```ts -type CDPUpstream = "service_worker" | "direct_cdp" | "auto" | "loopback_cdp" | "chrome_debugger"; +type CDPUpstream = "service_worker" | "direct_cdp" | "auto" | "loopback_cdp" | "chromedebugger"; // client-side defaults const client_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "service_worker" } as const; // server-side defaults (inside the SW) -const server_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "auto" } as const; +const server_router_routes = { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "auto" } as const; ``` - **`service_worker`** — handle in the extension SW. - **`direct_cdp`** (client only) — send straight to the browser CDP websocket. -- **`auto`** (server only) — try `loopback_cdp` first, fall back to `chrome_debugger`. +- **`auto`** (server only) — try `loopback_cdp` first, fall back to `chromedebugger`. - **`loopback_cdp`** (server only) — SW dials a CDP websocket reachable from the browser. You may pass `http://host:port` as shorthand, but it is resolved to the concrete `ws://.../devtools/...` URL at configuration time. Useful for `Browser.*` commands that `chrome.debugger` doesn't support. -- **`chrome_debugger`** (server only) — `chrome.debugger.sendCommand` against `params.debuggee || { tabId, targetId, extensionId }`, defaulting to the active last-focused tab. +- **`chromedebugger`** (server only) — `chrome.debugger.sendCommand` against `params.debuggee || { tabId, targetId, extensionId }`, defaulting to the active last-focused tab. Route resolution is **deterministic across all three language clients**: exact-method match → longest-prefix wildcard → `*.*` fallback. This avoids map-iteration nondeterminism (Go) and key-insertion-order shadowing (JS/Python). -When server-side `auto` routing tries loopback CDP discovery, the SW only trusts `127.0.0.1:9222` after verifying a per-connection `server.server_browser_token` against its own service-worker target. It will not accidentally route loopback commands through a different browser that happens to have the same extension installed. +When server-side `auto` routing tries loopback CDP discovery, the SW only trusts `127.0.0.1:9222` after verifying a per-connection `server_config.server_browser_token` against its own service-worker target. It will not accidentally route loopback commands through a different browser that happens to have the same extension installed. @@ -469,13 +448,23 @@ Custom roundtrip overhead is dominated by `Runtime.evaluate` + the SW's loopback
-macOS Chrome compatibility matrix (tested 2026-05-01) +macOS Chrome compatibility notes (tested 2026-05-27) + +Latest local extension-loading probe: -Tested browsers: +| Browser | Version | `--load-extension=` | `Extensions.loadUnpacked` | +| --- | --- | --- | --- | +| Chrome Canary | `150.0.7859.0` | no ModCDP service worker target appeared | returned `mdedooklbnfejodmnhmkdpkaedafkehf`; service worker target appeared | +| Playwright Chrome for Testing | `148.0.7778.96` | service worker target appeared | `Method not available.` | -- `/Applications/Google Chrome.app` — Google Chrome `148.0.7778.96 beta` -- `/Applications/Google Chrome Canary.app` — Google Chrome `149.0.7819.0 canary` -- Playwright Chrome for Testing — `147.0.7727.15` +Practical guidance: + +- Use `injector_mode: "cdp"` / `Extensions.loadUnpacked` for Canary 150 when loading the extension into an already-running local browser over CDP. +- Use `injector_mode: "cli"` / `--load-extension=` with Chrome for Testing, Linux `/usr/bin/chromium`, or an explicit `CHROME_PATH` known to support launch-arg extension loading. +- Local tests that exercise `injector_mode: "cli"` pin the executable to Chrome for Testing on macOS because the default local browser candidate may be Canary, and Canary 150 does not load this extension through `--load-extension` in the headless launch path. +- `Extensions.loadUnpacked` is not a universal fallback. It is only available in browser builds that expose the `Extensions` CDP domain. + +Previous latency probe definitions: Latency columns: @@ -486,40 +475,6 @@ Latency columns: The launched-browser rows used an isolated temporary user data dir. The live/default-profile row is separate because it depends on the user enabling Chrome's `chrome://inspect/#remote-debugging` flow and accepting Chrome's connection prompt. -| Browser | UI | Mode | Works | `chrome.tabs.query` | `chrome.debugger` | `Browser.getVersion` | `Target.getTargets` | Default profile | direct ms | pong ms | loopback ms | debugger ms | -| --------------------------------- | ---------------- | ------------ | ----- | ------------------- | ----------------- | -------------------- | ------------------- | --------------- | --------: | ------: | ----------: | ----------: | -| Chrome Beta 148 | `--headless=new` | `--direct` | yes | yes | no | yes | yes | no | 4.8 | 3 | - | - | -| Chrome Beta 148 | `--headless=new` | `--loopback` | yes | yes | no | yes | yes | no | 2.3 | 2 | 13.5 | - | -| Chrome Beta 148 | `--headless=new` | `--debugger` | no | yes | no | no | no | no | 5.3 | 5 | - | - | -| Chrome Beta 148 | headful | `--direct` | yes | yes | no | yes | yes | no | 5.1 | 1 | - | - | -| Chrome Beta 148 | headful | `--loopback` | yes | yes | no | yes | yes | no | 2.4 | 1 | 13.5 | - | -| Chrome Beta 148 | headful | `--debugger` | no | yes | no | no | no | no | 2.2 | 2 | - | - | -| Chrome Canary 149 | `--headless=new` | `--direct` | yes | yes | yes | yes | yes | no | 2.2 | 1 | - | - | -| Chrome Canary 149 | `--headless=new` | `--loopback` | yes | yes | yes | yes | yes | no | 2.6 | 1 | 14.4 | - | -| Chrome Canary 149 | `--headless=new` | `--debugger` | yes | yes | yes | no | no | no | 2.1 | 1 | - | 1.4 | -| Chrome Canary 149 | headful | `--direct` | yes | yes | yes | yes | yes | no | 2.4 | 1 | - | - | -| Chrome Canary 149 | headful | `--loopback` | yes | yes | yes | yes | yes | no | 2.2 | 1 | 13.5 | - | -| Chrome Canary 149 | headful | `--debugger` | yes | yes | yes | no | no | no | 2.3 | 0 | - | 1.2 | -| Playwright Chrome for Testing 147 | `--headless=new` | `--direct` | yes | yes | yes\* | yes | yes | no | 2.3 | 3 | - | - | -| Playwright Chrome for Testing 147 | `--headless=new` | `--loopback` | yes | yes | yes\* | yes | yes | no | 1.9 | 1 | 13.0 | - | -| Playwright Chrome for Testing 147 | `--headless=new` | `--debugger` | yes | yes | yes\* | no | no | no | 2.6 | 1 | - | 0.7 | -| Playwright Chrome for Testing 147 | headful | `--direct` | yes | yes | yes\* | yes | yes | no | 2.0 | 1 | - | - | -| Playwright Chrome for Testing 147 | headful | `--loopback` | yes | yes | yes\* | yes | yes | no | 2.4 | 1 | 12.5 | - | -| Playwright Chrome for Testing 147 | headful | `--debugger` | yes | yes | yes\* | no | no | no | 2.1 | 1 | - | 1.2 | - -`*` Playwright Chrome for Testing exposes `chrome.debugger` when the ModCDP extension is launched with `--load-extension`. With auto-injection only, `--direct` and `--loopback` still work, but `chrome.debugger` is not available in the borrowed/injected service worker. - -Live/default-profile status: - -| Browser | UI | Mode | Result | -| --------------------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------ | -| Chrome Beta 148 | `--headless=new` | `--live` | not applicable | -| Chrome Beta 148 | headful | `--live` | current advertised `DevToolsActivePort` was stale; websocket failed with `ECONNREFUSED 127.0.0.1:9222` | -| Chrome Canary 149 | `--headless=new` | `--live` | not applicable | -| Chrome Canary 149 | headful | `--live` | no active live endpoint found | -| Playwright Chrome for Testing 147 | `--headless=new` | `--live` | not applicable | -| Playwright Chrome for Testing 147 | headful | `--live` | no active live endpoint found | - Minimum viable macOS CLI args: | Mode | Browsers | Args | @@ -528,11 +483,10 @@ Minimum viable macOS CLI args: | `--direct` headless | all three | `--headless=new --remote-debugging-port= --user-data-dir= chrome://newtab/` | | `--loopback` headful | all three | `--remote-debugging-port= --user-data-dir= --remote-allow-origins=* chrome://newtab/` | | `--loopback` headless | all three | `--headless=new --remote-debugging-port= --user-data-dir= --remote-allow-origins=* chrome://newtab/` | -| `--debugger` | Chrome Beta 148 | no working set found; `chrome.debugger` is unavailable in the extension service worker | -| `--debugger` headful | Chrome Canary 149 | `--remote-debugging-port= --user-data-dir= chrome://newtab/` | -| `--debugger` headless | Chrome Canary 149 | `--headless=new --remote-debugging-port= --user-data-dir= chrome://newtab/` | -| `--debugger` headful | Playwright Chrome for Testing 147 | `--remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | -| `--debugger` headless | Playwright Chrome for Testing 147 | `--headless=new --remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | +| `--debugger` headful | Canary 150 | `--remote-debugging-port= --user-data-dir=` plus `Extensions.loadUnpacked {path:"/dist/extension"}` | +| `--debugger` headless | Canary 150 | `--headless=new --remote-debugging-port= --user-data-dir=` plus `Extensions.loadUnpacked {path:"/dist/extension"}` | +| `--debugger` headful | Chrome for Testing 148 | `--remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | +| `--debugger` headless | Chrome for Testing 148 | `--headless=new --remote-debugging-port= --user-data-dir= --load-extension=/dist/extension chrome://newtab/` | Recommended full macOS launch args: @@ -541,7 +495,6 @@ Recommended full macOS launch args: --user-data-dir= --remote-allow-origins=* --enable-unsafe-extension-debugging ---load-extension=/dist/extension --no-first-run --no-default-browser-check --disable-default-apps @@ -555,6 +508,6 @@ Recommended full macOS launch args: chrome://newtab/ ``` -Add `--headless=new` for headless launches. Do not pass `--no-sandbox`, `--disable-gpu`, or `--remote-debugging-address` on macOS. On Linux only, pass `--no-sandbox` when there is no usable sandbox/display environment. +Add `--load-extension=/dist/extension` only for browser builds that support launch-arg extension loading. For Canary 150, load the extension after startup with `Extensions.loadUnpacked`. Add `--headless=new` for headless launches. Do not pass `--no-sandbox`, `--disable-gpu`, or `--remote-debugging-address` on macOS. On Linux only, pass `--no-sandbox` when there is no usable sandbox/display environment.
diff --git a/python/examples/demo.py b/python/examples/demo.py index 6718ce99..0546c213 100644 --- a/python/examples/demo.py +++ b/python/examples/demo.py @@ -5,11 +5,9 @@ --direct *.* -> direct_cdp on the client. --loopback *.* -> service_worker on the client; *.* -> loopback_cdp on the server. Default. - --debugger *.* -> service_worker on the client; *.* -> chrome_debugger + --debugger *.* -> service_worker on the client; *.* -> chromedebugger on the server. - --upstream ws|pipe|reversews|nativemessaging|nats. Defaults to ws. - reversews and nativemessaging use the fixed extension - defaults: ws://127.0.0.1:29292 and com.modcdp.bridge. + --upstream ws. Defaults to ws. """ import json @@ -18,17 +16,17 @@ import sys import threading import time -import urllib.request from pathlib import Path -from typing import cast sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from modcdp import ModCDPClient -from modcdp.types import JsonValue, ProtocolPayload +from modcdp.types import ProtocolPayload +from modcdp.types.modcdp import _isObjectMap ROOT = Path(__file__).resolve().parent.parent.parent EXTENSION_PATH = ROOT / "dist" / "extension" -REVERSE_TRANSPORT_WAIT_TIMEOUT_MS = 60_000 +DEMO_CDP_SEND_TIMEOUT_MS = 60_000 +DEMO_EXECUTION_CONTEXT_TIMEOUT_MS = 60_000 LIVE_DEVTOOLS_ACTIVE_PORTS = [ Path.home() / "Library" / "Application Support" / "Google" / "Chrome" / "DevToolsActivePort", Path.home() / "Library" / "Application Support" / "Google" / "Chrome Beta" / "DevToolsActivePort", @@ -39,23 +37,19 @@ def expect_object(value: object, label: str) -> ProtocolPayload: - if not isinstance(value, dict): + if not _isObjectMap(value): raise RuntimeError(f"{label} returned non-object value: {value!r}") - return cast(ProtocolPayload, value) + return value -def server_routes_for(mode: str, upstream_mode: str) -> ProtocolPayload: - route = "loopback_cdp" if mode == "loopback" else "chrome_debugger" if mode == "debugger" else "auto" - routes: ProtocolPayload = { +def server_router_routes_for(mode: str, upstream_mode: str) -> ProtocolPayload: + del upstream_mode + route = "loopback_cdp" if mode == "loopback" else "chromedebugger" if mode == "debugger" else "auto" + return { "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": route, } - if mode == "loopback" or upstream_mode in {"reversews", "nativemessaging", "nats"}: - routes["Target.setDiscoverTargets"] = "loopback_cdp" - routes["Target.createTarget"] = "loopback_cdp" - routes["Target.activateTarget"] = "loopback_cdp" - return routes def client_routes_for(mode: str) -> ProtocolPayload: @@ -63,14 +57,12 @@ def client_routes_for(mode: str) -> ProtocolPayload: "Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp" if mode == "direct" else "service_worker", - "Target.setDiscoverTargets": "direct_cdp", - "Target.createTarget": "direct_cdp", - "Target.activateTarget": "direct_cdp", } + routes["Runtime.*"] = "service_worker" return routes -UPSTREAM_MODES = {"ws", "pipe", "reversews", "nativemessaging", "nats"} +UPSTREAM_MODES = {"ws"} def parse_args(argv): @@ -92,37 +84,37 @@ def parse_args(argv): raise RuntimeError(f"unknown --upstream={upstream_mode}; expected {'|'.join(sorted(UPSTREAM_MODES))}") live = "live" in flags mode = "debugger" if "debugger" in flags else "direct" if "direct" in flags else "loopback" if "loopback" in flags else "direct" if live else "loopback" - if live and upstream_mode == "pipe": - raise RuntimeError("--live cannot be combined with --upstream=pipe because pipe handles only exist for launched browsers.") - if mode == "direct" and upstream_mode in {"reversews", "nativemessaging", "nats"}: - raise RuntimeError(f"--direct cannot be combined with --upstream={upstream_mode}; reverse transports terminate at ModCDPServer.") return mode, live, upstream_mode -def client_options_for(mode, upstream_mode, cdp_url, launch_options=None): - upstream: ProtocolPayload = {"upstream_mode": upstream_mode, "upstream_cdp_url": cdp_url} - if upstream_mode == "reversews": - upstream["upstream_reversews_wait_timeout_ms"] = REVERSE_TRANSPORT_WAIT_TIMEOUT_MS - if upstream_mode == "nativemessaging": - upstream["upstream_nativemessaging_wait_timeout_ms"] = REVERSE_TRANSPORT_WAIT_TIMEOUT_MS - if upstream_mode == "nats": - upstream["upstream_nats_wait_timeout_ms"] = REVERSE_TRANSPORT_WAIT_TIMEOUT_MS +def client_config_for(mode, upstream_mode, cdp_url, launcher_config=None): + upstream: ProtocolPayload = {"upstream_mode": upstream_mode, "upstream_ws_cdp_url": cdp_url} + injector: ProtocolPayload = { + "injector_mode": "discover" if cdp_url else "cli", + "injector_execution_context_timeout_ms": DEMO_EXECUTION_CONTEXT_TIMEOUT_MS, + } + if cdp_url: + injector["injector_discover_extension_path"] = str(EXTENSION_PATH) + else: + injector["injector_cli_extension_path"] = str(EXTENSION_PATH) if mode == "direct": return { - "launcher": {"launcher_mode": "remote" if cdp_url else "local", "launcher_options": launch_options or {}}, + "launcher": {"launcher_mode": "remote" if cdp_url else "local", **(launcher_config or {}), **({"launcher_remote_cdp_url": cdp_url} if cdp_url else {})}, "upstream": upstream, - "injector": {"injector_mode": "auto", "injector_extension_path": str(EXTENSION_PATH)}, - "client": {"client_routes": client_routes_for(mode)}, + "injector": injector, + "router": {"router_routes": client_routes_for(mode)}, + "client_config": {"client_cdp_send_timeout_ms": DEMO_CDP_SEND_TIMEOUT_MS}, } - server = { - "server_routes": server_routes_for(mode, upstream_mode), + server_config = { + "router": {"router_routes": server_router_routes_for(mode, upstream_mode), "loopback_execution_context_timeout_ms": DEMO_EXECUTION_CONTEXT_TIMEOUT_MS}, } return { - "launcher": {"launcher_mode": "remote" if cdp_url else "local", "launcher_options": launch_options or {}}, + "launcher": {"launcher_mode": "remote" if cdp_url else "local", **(launcher_config or {}), **({"launcher_remote_cdp_url": cdp_url} if cdp_url else {})}, "upstream": upstream, - "injector": {"injector_mode": "auto", "injector_extension_path": str(EXTENSION_PATH)}, - "client": {"client_routes": client_routes_for(mode)}, - "server": server, + "injector": injector, + "router": {"router_routes": client_routes_for(mode)}, + "client_config": {"client_cdp_send_timeout_ms": DEMO_CDP_SEND_TIMEOUT_MS}, + "server_config": server_config, } @@ -153,49 +145,37 @@ def main(): try: if live: cdp_url = wait_for_live_cdp_url() - launch_options: dict[str, object] = {} + launcher_config: dict[str, object] = {} else: cdp_url = None - launch_options: dict[str, object] = { - "chrome_ready_timeout_ms": 60_000, - "headless": sys.platform.startswith("linux") and not os.environ.get("DISPLAY"), - "sandbox": not sys.platform.startswith("linux"), + launcher_config: dict[str, object] = { + "launcher_local_chrome_ready_timeout_ms": 60_000, + "launcher_local_headless": sys.platform.startswith("linux") and not os.environ.get("DISPLAY"), + "launcher_local_sandbox": not sys.platform.startswith("linux"), } if os.environ.get("CHROME_PATH"): - launch_options["executable_path"] = os.environ["CHROME_PATH"] - - cdp = ModCDPClient(**client_options_for(mode, upstream_mode, cdp_url, launch_options)) - page_target_events = [] - target_created_events = [] - events_lock = threading.Lock() + launcher_config["launcher_local_executable_path"] = os.environ["CHROME_PATH"] - def on_target_created(payload, *_): - print(f"Target.targetCreated -> {payload.get('targetInfo', {}).get('targetId')}") - with events_lock: - target_created_events.append(payload) - - def on_page_target_updated(payload, *_): - print(f"Custom.pageTargetUpdated -> {payload}") - with events_lock: - page_target_events.append(payload) - - cdp.on("Target.targetCreated", on_target_created) + cdp = ModCDPClient(**client_config_for(mode, upstream_mode, cdp_url, launcher_config)) cdp.connect() print(f"upstream cdp: {cdp.cdp_url}") - print(f"connected; ext {cdp.extension_id} session {cdp.ext_session_id}") + print( + f"connected; ext {cdp.injector.extension_id if cdp.injector else None} " + f"session {cdp.injector.session_id if cdp.injector else None}" + ) print(f"connect timing -> {cdp.connect_timing}") - server_config: ProtocolPayload = {"server_routes": server_routes_for(mode, upstream_mode)} configure_params: ProtocolPayload = { - "upstream": {"upstream_mode": upstream_mode}, - "client": {"client_routes": client_routes_for(mode)}, - "server": server_config, + "router": { + "router_routes": server_router_routes_for(mode, upstream_mode), + "loopback_execution_context_timeout_ms": DEMO_EXECUTION_CONTEXT_TIMEOUT_MS, + }, } configure_result = expect_object(cdp.send("Mod.configure", configure_params), "Mod.configure") - if expect_object(configure_result.get("routes"), "Mod.configure.routes").get("*.*") != server_routes_for(mode, upstream_mode)["*.*"]: + if expect_object(expect_object(configure_result.get("router"), "Mod.configure.router").get("router_routes"), "Mod.configure.router.router_routes").get("*.*") != server_router_routes_for(mode, upstream_mode)["*.*"]: raise RuntimeError(f"unexpected Mod.configure result {configure_result}") - print(f"Mod.configure -> {configure_result.get('routes')}") + print(f"Mod.configure -> {expect_object(configure_result.get('router'), 'Mod.configure.router').get('router_routes')}") pong_events = [] pong_lock = threading.Lock() @@ -226,30 +206,45 @@ def on_pong(payload, *_): } print(f"ping latency -> {ping_latency}") - if mode == "debugger": - try: - version = expect_object(cdp.send("Browser.getVersion"), "Browser.getVersion") - if not isinstance(version.get("protocolVersion"), str) or not isinstance(version.get("product"), str): - raise RuntimeError(f"unexpected Browser.getVersion result {version}") - print(f"Browser.getVersion -> {version}") - except Exception as e: - print(f"Browser.getVersion -> (debugger route rejected: {str(e).splitlines()[0]} )") - runtime_eval = expect_object(cdp.send("Runtime.evaluate", {"expression": "(() => 42)()", "returnByValue": True}), "Runtime.evaluate") - result = expect_object(runtime_eval.get("result"), "Runtime.evaluate.result") - if result.get("value") != 42: - raise RuntimeError(f"unexpected Runtime.evaluate result {runtime_eval}") - print(f"Runtime.evaluate -> {runtime_eval}") - else: - version = expect_object(cdp.send("Browser.getVersion"), "Browser.getVersion") - if not isinstance(version.get("protocolVersion"), str) or not isinstance(version.get("product"), str): - raise RuntimeError(f"unexpected Browser.getVersion result {version}") - print(f"Browser.getVersion -> {version}") - modcdp_eval = expect_object(cdp.send("Mod.evaluate", {"expression": "({ extension_id: chrome.runtime.id })"}), "Mod.evaluate") - if not isinstance(modcdp_eval.get("extension_id"), str) or (cdp.extension_id and modcdp_eval.get("extension_id") != cdp.extension_id): + injector_extension_id = cdp.injector.extension_id if cdp.injector else None + if not isinstance(modcdp_eval.get("extension_id"), str) or (injector_extension_id and modcdp_eval.get("extension_id") != injector_extension_id): raise RuntimeError(f"unexpected Mod.evaluate result {modcdp_eval}") print(f"Mod.evaluate -> {modcdp_eval}") + topology_checked = False + if mode != "direct": + topology = expect_object(cdp.Mod.getTopology(), "Mod.getTopology") + root_frame_id = topology.get("rootFrameId") + frames = expect_object(topology.get("frames"), "Mod.getTopology.frames") + roots = expect_object(topology.get("roots"), "Mod.getTopology.roots") + contexts = expect_object(topology.get("contexts"), "Mod.getTopology.contexts") + if ( + not isinstance(root_frame_id, str) + or root_frame_id not in frames + or not any(_isObjectMap(root) and root.get("kind") == "document" for root in roots.values()) + or not any(_isObjectMap(context) and context.get("world") == "piercer" for context in contexts.values()) + ): + raise RuntimeError(f"unexpected Mod.getTopology result {topology}") + topology_checked = True + print(f"Mod.getTopology -> {{'rootFrameId': {root_frame_id!r}, 'frames': {len(frames)}, 'roots': {len(roots)}, 'contexts': {len(contexts)}}}") + + response_middleware_registration = expect_object(cdp.send("Mod.addMiddleware", { + "name": "Custom.echo", + "phase": "response", + "expression": '''async (payload, next) => next({ ...payload, responseMiddleware: "ok" })''', + }), "Mod.addMiddleware response") + if response_middleware_registration.get("registered") is not True or response_middleware_registration.get("phase") != "response": + raise RuntimeError(f"unexpected response middleware registration {response_middleware_registration}") + + event_middleware_registration = expect_object(cdp.send("Mod.addMiddleware", { + "name": "Custom.demoEvent", + "phase": "event", + "expression": '''async (payload, next) => next({ ...payload, eventMiddleware: "ok" })''', + }), "Mod.addMiddleware event") + if event_middleware_registration.get("registered") is not True or event_middleware_registration.get("phase") != "event": + raise RuntimeError(f"unexpected event middleware registration {event_middleware_registration}") + echo_registration = expect_object(cdp.send("Mod.addCustomCommand", { "name": "Custom.echo", "expression": "async (params, method) => ({ echoed: params.value, method })", @@ -257,56 +252,14 @@ def on_pong(payload, *_): if echo_registration.get("registered") is not True or echo_registration.get("name") != "Custom.echo": raise RuntimeError(f"unexpected Custom.echo registration {echo_registration}") echo_result = expect_object(cdp.send("Custom.echo", {"value": "custom-command-ok"}), "Custom.echo") - if echo_result.get("echoed") != "custom-command-ok" or echo_result.get("method") != "Custom.echo": + if ( + echo_result.get("echoed") != "custom-command-ok" + or echo_result.get("method") != "Custom.echo" + or echo_result.get("responseMiddleware") != "ok" + ): raise RuntimeError(f"unexpected Custom.echo result {echo_result}") print(f"Custom.echo -> {echo_result}") - tab_command_registration = expect_object(cdp.send("Mod.addCustomCommand", { - "name": "Custom.TabIdFromTargetId", - "expression": '''async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - return { tabId: target?.tabId ?? null }; - }''', - }), "Mod.addCustomCommand Custom.TabIdFromTargetId") - if tab_command_registration.get("registered") is not True: - raise RuntimeError(f"unexpected TabIdFromTargetId registration {tab_command_registration}") - target_command_registration = expect_object(cdp.send("Mod.addCustomCommand", { - "name": "Custom.targetIdFromTabId", - "expression": '''async ({ tabId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.type === "page" && target.tabId === tabId); - return { targetId: target?.id ?? null }; - }''', - }), "Mod.addCustomCommand Custom.targetIdFromTabId") - if target_command_registration.get("registered") is not True: - raise RuntimeError(f"unexpected targetIdFromTabId registration {target_command_registration}") - middleware_registered = False - for phase in ("response", "event"): - middleware_registration = expect_object(cdp.send("Mod.addMiddleware", { - "name": "*", - "phase": phase, - "expression": '''async (payload, next) => { - const seen = new WeakSet(); - const visit = async value => { - if (!value || typeof value !== "object" || seen.has(value)) return; - seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { - const { tabId } = await cdp.send("Custom.TabIdFromTargetId", { targetId: value.targetId }); - if (tabId != null) value.tabId = tabId; - } - for (const child of Array.isArray(value) ? value : Object.values(value)) await visit(child); - }; - await visit(payload); - return next(payload); - }''', - }), f"Mod.addMiddleware {phase}") - if middleware_registration.get("registered") is not True or middleware_registration.get("phase") != phase: - raise RuntimeError(f"unexpected {phase} middleware registration {middleware_registration}") - middleware_registered = True - if not middleware_registered: - raise RuntimeError("middleware registration loop did not run") - demo_events = [] demo_lock = threading.Lock() @@ -318,13 +271,45 @@ def on_demo_event(payload, *_): if demo_event_registration.get("registered") is not True or demo_event_registration.get("name") != "Custom.demoEvent": raise RuntimeError(f"unexpected Custom.demoEvent registration {demo_event_registration}") cdp.on("Custom.demoEvent", on_demo_event) - emit_result = expect_object(cdp.send("Mod.evaluate", {"expression": '''async () => await ModCDP.emit("Custom.demoEvent", { value: "custom-event-ok" })'''}), "Custom.demoEvent emit") + emit_expression = ( + """async () => { + await globalThis.__ModCDP_custom_event__(JSON.stringify({ + event: "Custom.demoEvent", + data: { value: "custom-event-ok" }, + cdpSessionId: null, + })); + return { emitted: true }; + }""" + if mode == "direct" + else """async () => { + const params = await ModCDP.runMiddleware("event", "Custom.demoEvent", { value: "custom-event-ok" }, { + cdpSessionId, + event: { + method: "Custom.demoEvent", + params: { value: "custom-event-ok" }, + }, + }); + const sent = downstream.sendEvent({ + method: "Custom.demoEvent", + params, + }); + return { emitted: sent > 0 }; + }""" + ) + emit_result = expect_object(cdp.send("Mod.evaluate", {"expression": emit_expression}), "Custom.demoEvent emit") if emit_result.get("emitted") is not True: raise RuntimeError(f"unexpected Custom.demoEvent emit result {emit_result}") deadline = time.monotonic() + 3.0 while True: with demo_lock: - demo_event = next((event for event in demo_events if event.get("value") == "custom-event-ok"), None) + demo_event = next( + ( + event + for event in demo_events + if event.get("value") == "custom-event-ok" and (mode == "direct" or event.get("eventMiddleware") == "ok") + ), + None, + ) if demo_event or time.monotonic() >= deadline: break time.sleep(0.02) @@ -332,63 +317,14 @@ def on_demo_event(payload, *_): raise RuntimeError("expected Custom.demoEvent") print(f"Custom.demoEvent -> {demo_event}") - page_target_event_registration = expect_object(cdp.send("Mod.addCustomEvent", {"name": "Custom.pageTargetUpdated"}), "Mod.addCustomEvent Custom.pageTargetUpdated") - if page_target_event_registration.get("registered") is not True: - raise RuntimeError(f"unexpected page target event registration {page_target_event_registration}") - cdp.on("Custom.pageTargetUpdated", on_page_target_updated) - - cdp.send("Target.setDiscoverTargets", {"discover": True}) - created_target = expect_object(cdp.send("Target.createTarget", {"url": "https://example.com", "background": True}), "Target.createTarget") - created_target_id = created_target.get("targetId") - if not created_target_id: - raise RuntimeError(f"Target.createTarget returned no targetId: {created_target}") - deadline = time.monotonic() + 3.0 - while True: - with events_lock: - matched_target_event = next((event for event in target_created_events if event.get("targetInfo", {}).get("targetId") == created_target_id), None) - if matched_target_event or time.monotonic() >= deadline: - break - time.sleep(0.02) - if not matched_target_event: - raise RuntimeError(f"expected Target.targetCreated for {created_target_id}") - print(f"normal event matched -> {created_target_id}") - - tab_from_target = expect_object(cdp.send("Custom.TabIdFromTargetId", {"targetId": created_target_id}), "Custom.TabIdFromTargetId") - if not isinstance(tab_from_target.get("tabId"), int | float): - raise RuntimeError(f"unexpected Custom.TabIdFromTargetId result {tab_from_target}") - print(f"Custom.TabIdFromTargetId -> {tab_from_target}") - - cdp.send("Target.activateTarget", {"targetId": created_target_id}) - page_target_emit_result = expect_object(cdp.send("Mod.evaluate", { - "params": {"targetId": created_target_id}, - "expression": '''async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - if (!target?.id) throw new Error(`target ${targetId} not found`); - await cdp.emit("Custom.pageTargetUpdated", { targetId: target.id, url: target.url ?? null }); - return { emitted: true, targetId: target.id }; - }''', - }), "Custom.pageTargetUpdated emit") - if page_target_emit_result.get("emitted") is not True or page_target_emit_result.get("targetId") != created_target_id: - raise RuntimeError(f"unexpected Custom.pageTargetUpdated emit result {page_target_emit_result}") - deadline = time.monotonic() + 3.0 - while True: - with events_lock: - page_target = next((event for event in page_target_events if event.get("targetId") == created_target_id), None) - if page_target or time.monotonic() >= deadline: - break - time.sleep(0.02) - if not page_target: - raise RuntimeError(f"expected Custom.pageTargetUpdated for {created_target_id}") - if tab_from_target.get("tabId") != page_target.get("tabId"): - raise RuntimeError(f"unexpected Custom.pageTargetUpdated result {page_target}") - - target_from_tab = expect_object(cdp.send("Custom.targetIdFromTabId", {"tabId": page_target["tabId"]}), "Custom.targetIdFromTabId") - if target_from_tab.get("targetId") != created_target_id or target_from_tab.get("tabId") != page_target.get("tabId"): - raise RuntimeError(f"unexpected Custom.targetIdFromTabId/middleware result {target_from_tab}") - print(f"Custom.targetIdFromTabId -> {target_from_tab}") + runtime_eval = expect_object(cdp.send("Runtime.evaluate", {"expression": "(() => 42)()", "returnByValue": True}), "Runtime.evaluate") + result = expect_object(runtime_eval.get("result"), "Runtime.evaluate.result") + if result.get("value") != 42: + raise RuntimeError(f"unexpected Runtime.evaluate result {runtime_eval}") + print(f"Runtime.evaluate -> {runtime_eval}") - print(f"\nSUCCESS ({mode}/{upstream_mode}): normal command, normal event, custom commands, custom event, and middleware all passed") + topology_label = "topology, " if topology_checked else "" + print(f"\nSUCCESS ({mode}/{upstream_mode}): native command, {topology_label}custom commands, custom event, and middleware all passed") # TTY-only: drop into a REPL where you can send live commands and # watch events as they print. Skip when run non-interactively so the @@ -410,7 +346,7 @@ def run_repl(cdp, mode): print("Enter commands as Domain.method({...JSON params...}). Examples:") print(' Browser.getVersion({})') print(' Mod.evaluate({"expression": "chrome.tabs.query({active: true})"})') - print(' Custom.TabIdFromTargetId({"targetId": "..."})') + print(' Runtime.evaluate({"expression": "document.title", "returnByValue": true})') print("Type exit or quit to disconnect (browser keeps running).") cmd_re = re.compile(r"^([A-Za-z_]\w*\.[A-Za-z_]\w*)(?:\((.*)\))?$") while True: diff --git a/python/modcdp/__init__.py b/python/modcdp/__init__.py index fb85c6f5..309cefc7 100644 --- a/python/modcdp/__init__.py +++ b/python/modcdp/__init__.py @@ -1,45 +1,53 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/index.ts +# - ./go/modcdp/modcdp.go from .router.AutoSessionRouter import AutoSessionRouter -from .injector.BBBrowserExtensionInjector import BBBrowserExtensionInjector -from .injector.BorrowedExtensionInjector import BorrowedExtensionInjector -from .launcher.BrowserbaseBrowserLauncher import BrowserbaseBrowserLauncher -from .launcher.BrowserLauncher import BrowserLauncher -from .injector.DiscoveredExtensionInjector import DiscoveredExtensionInjector -from .injector.ExtensionInjector import ExtensionInjector, defaultModCDPExtensionPath -from .injector.ExtensionsLoadUnpackedInjector import ExtensionsLoadUnpackedInjector -from .injector.LocalBrowserLaunchExtensionInjector import LocalBrowserLaunchExtensionInjector +from .injector.BBExtensionInjector import BBExtensionInjector +from .launcher.BBBrowserLauncher import BBBrowserLauncher +from .launcher.BrowserLauncher import BrowserLauncher, resolveCdpWebSocketUrl +from .injector.DiscoverExtensionInjector import DiscoverExtensionInjector +from .injector.ExtensionInjector import ExtensionInjector, DEFAULT_MODCDP_EXTENSION_ID, DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES +from .injector.NodeExtensionFiles import PreparedExtension, defaultModCDPExtensionPath, extensionIdFromManifestKey, prepareUnpackedExtension +from .injector.CDPExtensionInjector import CDPExtensionInjector +from .injector.CLIExtensionInjector import CLIExtensionInjector from .launcher.LocalBrowserLauncher import LocalBrowserLauncher from .client.ModCDPClient import ModCDPClient -from .transport.NativeMessagingUpstreamTransport import NativeMessagingUpstreamTransport -from .transport.NatsUpstreamTransport import NatsUpstreamTransport -from .launcher.NoopBrowserLauncher import NoopBrowserLauncher -from .transport.PipeUpstreamTransport import PipeUpstreamTransport +from .types.CDPTypes import CDPTypes +from .launcher.NoneBrowserLauncher import NoneBrowserLauncher from .launcher.RemoteBrowserLauncher import RemoteBrowserLauncher -from .transport.ReverseWebSocketUpstreamTransport import ReverseWebSocketUpstreamTransport from .transport.UpstreamTransport import UpstreamTransport -from .transport.WebSocketUpstreamTransport import WebSocketUpstreamTransport +from .transport.WSUpstreamTransport import WSUpstreamTransport +from .translate.translate import wrap_command_if_needed, unwrap_response_if_needed, unwrap_event_if_needed, encode_binding_payload from .types.generated.cdp import CDPEvent, CDPModel, CDPParams __all__ = [ "AutoSessionRouter", - "BBBrowserExtensionInjector", - "BorrowedExtensionInjector", - "BrowserbaseBrowserLauncher", + "BBExtensionInjector", + "BBBrowserLauncher", "BrowserLauncher", - "DiscoveredExtensionInjector", + "resolveCdpWebSocketUrl", + "DiscoverExtensionInjector", "ExtensionInjector", + "DEFAULT_MODCDP_EXTENSION_ID", + "DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES", + "PreparedExtension", "defaultModCDPExtensionPath", - "ExtensionsLoadUnpackedInjector", - "LocalBrowserLaunchExtensionInjector", + "extensionIdFromManifestKey", + "prepareUnpackedExtension", + "CDPExtensionInjector", + "CLIExtensionInjector", "LocalBrowserLauncher", "ModCDPClient", - "NativeMessagingUpstreamTransport", - "NatsUpstreamTransport", - "NoopBrowserLauncher", - "PipeUpstreamTransport", + "CDPTypes", + "NoneBrowserLauncher", "RemoteBrowserLauncher", - "ReverseWebSocketUpstreamTransport", "UpstreamTransport", - "WebSocketUpstreamTransport", + "WSUpstreamTransport", + "wrap_command_if_needed", + "unwrap_response_if_needed", + "unwrap_event_if_needed", + "encode_binding_payload", "CDPEvent", "CDPModel", "CDPParams", diff --git a/python/modcdp/client/ModCDPClient.py b/python/modcdp/client/ModCDPClient.py index 6a0be9d4..238a12c1 100644 --- a/python/modcdp/client/ModCDPClient.py +++ b/python/modcdp/client/ModCDPClient.py @@ -1,13 +1,17 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/client/ModCDPClient.ts +# - ./go/modcdp/client/ModCDPClient.go """ModCDPClient (Python): importable, no CLI, no demo code. -Constructor option groups mirror the JS / Go ports: +Constructor config groups mirror the JS / Go ports: launcher browser/session creation and cleanup upstream message transport to raw CDP or a ModCDP server - injector raw-CDP extension discovery/injection/borrowing - server ModCDPServer.configure params + injector raw-CDP extension discovery/injection + server_config ModCDPServer.configure params client client routing and client-owned send/event timings -Public methods: connect(), send(method, params), on(event, handler), close(), _cdp.send(), _cdp.on(). +Public methods: connect(), send(method, params), on(event, handler), close(). Synchronous (blocking) API; upstream transports own their read loops. """ @@ -15,75 +19,47 @@ import inspect import threading import time -from collections.abc import Mapping, Sequence +from collections.abc import Mapping from queue import Queue, Empty -from typing import Any, Literal, cast +from typing import Any -from pydantic import BaseModel, TypeAdapter, ValidationError -from pydantic_core import to_jsonable_python -from ..router.AutoSessionRouter import AutoSessionRouter -from ..types.jsonschema import type_adapter_from_json_schema -from ..types.generated import cdp as generated_cdp +from ..router.AutoSessionRouter import AutoSessionRouter, RouterConfig from ..types.generated.cdp import AwaitableDict, CDPEvent, CDPModel, CDPParams, CDPSurfaceMixin, cdp_event_name, install_cdp_surface -from ..launcher.BrowserbaseBrowserLauncher import BrowserbaseBrowserLauncher -from ..injector.BBBrowserExtensionInjector import BBBrowserExtensionInjector -from ..injector.BorrowedExtensionInjector import BorrowedExtensionInjector -from ..injector.DiscoveredExtensionInjector import DiscoveredExtensionInjector -from ..injector.ExtensionInjector import ( - DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES, - ExtensionInjector, - ExtensionInjectorConfig, -) -from ..injector.ExtensionsLoadUnpackedInjector import ExtensionsLoadUnpackedInjector -from ..injector.LocalBrowserLaunchExtensionInjector import LocalBrowserLaunchExtensionInjector +from ..types.CDPTypes import CDPTypes +from ..launcher.BBBrowserLauncher import BBBrowserLauncher +from ..injector.BBExtensionInjector import BBExtensionInjector +from ..injector.DiscoverExtensionInjector import DiscoverExtensionInjector +from ..injector.ExtensionInjector import ExtensionInjector, InjectorConfig +from ..injector.CDPExtensionInjector import CDPExtensionInjector +from ..injector.CLIExtensionInjector import CLIExtensionInjector from ..launcher.LocalBrowserLauncher import LocalBrowserLauncher -from ..launcher.NoopBrowserLauncher import NoopBrowserLauncher +from ..launcher.NoneBrowserLauncher import NoneBrowserLauncher from ..launcher.RemoteBrowserLauncher import RemoteBrowserLauncher -from ..transport.NativeMessagingUpstreamTransport import DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS, NativeMessagingUpstreamTransport -from ..transport.NatsUpstreamTransport import DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS, NatsUpstreamTransport -from ..transport.PipeUpstreamTransport import PipeUpstreamTransport -from ..transport.ReverseWebSocketUpstreamTransport import ( - DEFAULT_UPSTREAM_REVERSEWS_BIND, - DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS, - ReverseWebSocketUpstreamTransport, -) -from ..transport.UpstreamTransport import UpstreamTransport -from ..transport.WebSocketUpstreamTransport import WebSocketUpstreamTransport +from ..transport.UpstreamTransport import UpstreamMode, UpstreamTransport, UpstreamTransportConfig +from ..transport.WSUpstreamTransport import WSUpstreamTransport from ..translate.translate import ( CUSTOM_EVENT_BINDING_NAME, - DEFAULT_CLIENT_ROUTES, UPSTREAM_EVENT_BINDING_NAME, wrap_command_if_needed, unwrap_event_if_needed, unwrap_response_if_needed, ) -from ..launcher.BrowserLauncher import BrowserLaunchOptions +from ..launcher.BrowserLauncher import LauncherConfig from ..types.modcdp import ( - ModCDPAddCustomCommandParams, - ModCDPAddCustomEventObjectParams, - ModCDPAddCustomEventParams, - ModCDPAddMiddlewareParams, ModCDPCommandTiming, + ModCDPClientConfig, ModCDPConnectTiming, ModCDPPingLatency, - ModCDPRawTiming, - ModCDPRoutes, ModCDPServerConfig, CdpMessage, ExtensionInfo, - MessageParams, Handler, JsonValue, - PendingEntry, ProtocolParams, ProtocolPayload, ProtocolResult, - TranslatedCommand, ) - - -def _defaulted(value: Any, fallback: Any) -> Any: - return fallback if value is None else value +from ..types.toJSON import modCDPToJSON class AwaitableValue: @@ -121,27 +97,30 @@ def __init__(self, client: "ModCDPClient") -> None: def evaluate( self, - *, - expression: str, params: Mapping[str, Any] | None = None, + *, + expression: str | None = None, + evaluate_params: Mapping[str, Any] | None = None, cdpSessionId: str | None = None, ) -> AwaitableDict | AwaitableValue: - payload: dict[str, Any] = {"expression": expression} - if params is not None: - payload["params"] = dict(params) + payload: dict[str, Any] = dict(params or {}) + if expression is not None: + payload["expression"] = expression + if evaluate_params is not None: + payload["params"] = dict(evaluate_params) if cdpSessionId is not None: payload["cdpSessionId"] = cdpSessionId return self._client._send_command("Mod.evaluate", payload) def addCustomCommand( self, - name: str, + name: str | Mapping[str, Any], *, params_schema: Any | None = None, result_schema: Any | None = None, expression: str | None = None, ) -> AwaitableDict | AwaitableValue: - payload: dict[str, Any] = {"name": name} + payload: dict[str, Any] = {str(key): value for key, value in name.items()} if isinstance(name, Mapping) else {"name": name} if params_schema is not None: payload["params_schema"] = params_schema if result_schema is not None: @@ -150,20 +129,30 @@ def addCustomCommand( payload["expression"] = expression return self._client._send_command("Mod.addCustomCommand", payload) - def addCustomEvent(self, name: str, *, event_schema: Any | None = None) -> AwaitableDict | AwaitableValue: - payload: dict[str, Any] = {"name": name} + def addCustomEvent( + self, + name: str | Mapping[str, Any], + *, + event_schema: Any | None = None, + ) -> AwaitableDict | AwaitableValue: + payload: dict[str, Any] = {str(key): value for key, value in name.items()} if isinstance(name, Mapping) else {"name": name} if event_schema is not None: payload["event_schema"] = event_schema return self._client._send_command("Mod.addCustomEvent", payload) def addMiddleware( self, + params: Mapping[str, Any] | None = None, *, - phase: Literal["request", "response", "event"], - expression: str, - name: str | None = None, + phase: str | None = None, + expression: str | None = None, + name: object | None = None, ) -> AwaitableDict | AwaitableValue: - payload: dict[str, Any] = {"phase": phase, "expression": expression} + payload: dict[str, Any] = dict(params or {}) + if phase is not None: + payload["phase"] = phase + if expression is not None: + payload["expression"] = expression if name is not None: payload["name"] = name return self._client._send_command("Mod.addMiddleware", payload) @@ -174,40 +163,22 @@ def configure(self, **params: Any) -> AwaitableDict | AwaitableValue: def ping(self, **params: Any) -> AwaitableDict | AwaitableValue: return self._client._send_command("Mod.ping", params) + def getTopology(self, **params: Any) -> AwaitableDict | AwaitableValue: + return self._client._send_command("Mod.getTopology", params) + MODCDP_READY_EXPRESSION = ( - "Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && " - "globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)" + "Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)" ) DEFAULT_SERVER = object() DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000 DEFAULT_EVENT_WAIT_TIMEOUT_MS = 10_000 -DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000 DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS = 10_000 DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS = 60_000 DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS = 100 DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS = 20 DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS = 250 DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS = 250 - - -class _RawCDP: - def __init__(self, client: "ModCDPClient") -> None: - self._client = client - - def send( - self, - method: str, - params: ProtocolParams | None = None, - session_id: str | None = None, - ) -> ProtocolResult: - return self._client._send_message(method, params or {}, session_id=session_id, record_raw_timing=True) - - def on(self, event: str, handler: Handler) -> "ModCDPClient": - return self._client.on(event, handler) - - -def _json_object(value: JsonValue | None) -> ProtocolResult: - return value if isinstance(value, dict) else {} +ClientConfig = ModCDPClientConfig class ModCDPClient(CDPSurfaceMixin): @@ -216,234 +187,160 @@ def __init__( launcher: Mapping[str, Any] | None = None, upstream: Mapping[str, Any] | None = None, injector: Mapping[str, Any] | None = None, - client: Mapping[str, Any] | None = None, - server: Mapping[str, JsonValue] | None | object = DEFAULT_SERVER, - custom_commands: Sequence[ModCDPAddCustomCommandParams] | None = None, - custom_events: Sequence[ModCDPAddCustomEventParams] | None = None, - custom_middlewares: Sequence[ModCDPAddMiddlewareParams] | None = None, + router: Mapping[str, Any] | None = None, + client_config: Mapping[str, Any] | None = None, + server_config: Mapping[str, object] | ModCDPServerConfig | None | object = DEFAULT_SERVER, + types: CDPTypes | Mapping[str, Any] | None = None, ) -> None: launcher_input = dict(launcher or {}) upstream_input = dict(upstream or {}) injector_input = dict(injector or {}) - client_input = dict(client or {}) - upstream_mode = str(upstream_input.get("upstream_mode") or "ws") - self.upstream: dict[str, Any] = { - "upstream_mode": upstream_mode, - "upstream_cdp_url": upstream_input.get("upstream_cdp_url"), - "upstream_nats_url": upstream_input.get("upstream_nats_url"), - "upstream_nats_subject_prefix": upstream_input.get("upstream_nats_subject_prefix"), - "upstream_nats_wait_timeout_ms": int( - _defaulted(upstream_input.get("upstream_nats_wait_timeout_ms"), DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS) - ), - "upstream_reversews_bind": _defaulted(upstream_input.get("upstream_reversews_bind"), DEFAULT_UPSTREAM_REVERSEWS_BIND), - "upstream_reversews_wait_timeout_ms": int( - _defaulted(upstream_input.get("upstream_reversews_wait_timeout_ms"), DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS) - ), - "upstream_nativemessaging_manifest": upstream_input.get("upstream_nativemessaging_manifest"), - "upstream_nativemessaging_manifests": list( - cast(Sequence[str], upstream_input.get("upstream_nativemessaging_manifests") or []) - ) - if upstream_input.get("upstream_nativemessaging_manifests") is not None - else None, - "upstream_nativemessaging_host_name": upstream_input.get("upstream_nativemessaging_host_name"), - "upstream_nativemessaging_wait_timeout_ms": int( - _defaulted( - upstream_input.get("upstream_nativemessaging_wait_timeout_ms"), - DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS, - ) - ), - "upstream_ws_connect_error_settle_timeout_ms": int( - _defaulted( - upstream_input.get("upstream_ws_connect_error_settle_timeout_ms"), - DEFAULT_WS_CONNECT_ERROR_SETTLE_TIMEOUT_MS, - ) - ), - } - self.upstream_endpoint_kind = "raw_cdp" if self.upstream["upstream_mode"] in ("ws", "pipe") else "modcdp_server" - launcher_mode = launcher_input.get("launcher_mode") or ( - "none" if self.upstream_endpoint_kind == "modcdp_server" else "remote" if self.upstream.get("upstream_cdp_url") else "local" - ) - self.launcher: dict[str, Any] = { - "launcher_mode": launcher_mode, - "launcher_executable_path": launcher_input.get("launcher_executable_path"), - "launcher_user_data_dir": launcher_input.get("launcher_user_data_dir"), - "launcher_options": dict(cast(Mapping[str, Any], launcher_input.get("launcher_options") or {})), - } - injector_mode = injector_input.get("injector_mode") or ( - "auto" if self.upstream_endpoint_kind == "raw_cdp" or launcher_mode != "none" else "none" - ) - raw_service_worker_url_suffixes = injector_input.get("injector_service_worker_url_suffixes") - self.injector: dict[str, Any] = { - "injector_mode": injector_mode, - "injector_extension_path": injector_input.get("injector_extension_path"), - "injector_extension_id": injector_input.get("injector_extension_id"), - "injector_service_worker_url_includes": list(cast(Sequence[str], injector_input.get("injector_service_worker_url_includes") or [])), - "injector_service_worker_url_suffixes": list( - cast( - Sequence[str], - DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES - if raw_service_worker_url_suffixes is None - else raw_service_worker_url_suffixes, - ) - ), - "injector_trust_service_worker_target": bool(injector_input.get("injector_trust_service_worker_target", False)), - "injector_require_service_worker_target": bool(injector_input.get("injector_require_service_worker_target", False)), - "injector_service_worker_ready_expression": injector_input.get("injector_service_worker_ready_expression"), - "injector_execution_context_timeout_ms": int( - _defaulted(injector_input.get("injector_execution_context_timeout_ms"), DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS) - ), - "injector_service_worker_probe_timeout_ms": int( - _defaulted(injector_input.get("injector_service_worker_probe_timeout_ms"), DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS) - ), - "injector_service_worker_ready_timeout_ms": int( - _defaulted(injector_input.get("injector_service_worker_ready_timeout_ms"), DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS) - ), - "injector_service_worker_poll_interval_ms": int( - _defaulted(injector_input.get("injector_service_worker_poll_interval_ms"), DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS) - ), - "injector_target_session_poll_interval_ms": int( - _defaulted(injector_input.get("injector_target_session_poll_interval_ms"), DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS) - ), - } - self.client: dict[str, Any] = { - "client_routes": { - **DEFAULT_CLIENT_ROUTES, - **dict(cast(Mapping[str, str], client_input.get("client_routes") or {})), - }, - "client_hydrate_aliases": bool(client_input.get("client_hydrate_aliases", True)), - "client_mirror_upstream_events": bool(client_input.get("client_mirror_upstream_events", True)), - "client_cdp_send_timeout_ms": int(_defaulted(client_input.get("client_cdp_send_timeout_ms"), DEFAULT_CDP_SEND_TIMEOUT_MS)), - "client_event_wait_timeout_ms": int(_defaulted(client_input.get("client_event_wait_timeout_ms"), DEFAULT_EVENT_WAIT_TIMEOUT_MS)), - "client_heartbeat_interval_ms": int( - _defaulted(client_input.get("client_heartbeat_interval_ms"), DEFAULT_CLIENT_HEARTBEAT_INTERVAL_MS) - ), - } - self.cdp_url: str | None = cast(str | None, self.upstream.get("upstream_cdp_url")) - if server is DEFAULT_SERVER: - self.server: ModCDPServerConfig | None = {"server_routes": {"*.*": "chrome_debugger"}} if self.upstream_endpoint_kind == "modcdp_server" else {} - elif server is None: - self.server = None - elif isinstance(server, Mapping): - self.server = cast(ModCDPServerConfig, { - **({"server_routes": {"*.*": "chrome_debugger"}} if self.upstream_endpoint_kind == "modcdp_server" else {}), - **dict(server), - }) + router_input = dict(router or {}) + client_config_input = dict(client_config or {}) + upstream_mode_input = upstream_input.get("upstream_mode") or "ws" + if upstream_mode_input != "ws": + raise RuntimeError(f"unknown upstream_mode={upstream_mode_input}") + upstream_mode: UpstreamMode = upstream_mode_input + upstream_config = UpstreamTransportConfig.model_validate({**upstream_input, "upstream_mode": upstream_mode}) + launcher_mode = launcher_input.get("launcher_mode") or "none" + launcher_config = LauncherConfig.model_validate({**launcher_input, "launcher_mode": launcher_mode}) + injector_config = InjectorConfig.model_validate(injector_input) + parsed_router_config = RouterConfig.model_validate(router_input) + parsed_client_config = ClientConfig.model_validate(client_config_input) + if server_config is DEFAULT_SERVER: + parsed_server_config: ModCDPServerConfig | None = ModCDPServerConfig() + elif server_config is None: + parsed_server_config = None + elif isinstance(server_config, ModCDPServerConfig): + parsed_server_config = server_config + elif isinstance(server_config, Mapping): + parsed_server_config = ModCDPServerConfig.model_validate(dict(server_config)) + else: + raise TypeError("server_config must be a mapping, None, or omitted") + self.config = parsed_client_config + self.server_config = parsed_server_config + if launcher_config.launcher_mode == "local": + self.launcher = LocalBrowserLauncher(launcher_config) + elif launcher_config.launcher_mode == "remote": + self.launcher = RemoteBrowserLauncher(launcher_config) + elif launcher_config.launcher_mode == "bb": + self.launcher = BBBrowserLauncher(launcher_config) + elif launcher_config.launcher_mode == "none": + self.launcher = NoneBrowserLauncher(launcher_config) + else: + raise RuntimeError(f"unknown launcher_mode={launcher_config.launcher_mode}") + self.upstream = WSUpstreamTransport(upstream_config) + if injector_config.injector_mode == "none": + self.injector: ExtensionInjector | None = None + elif injector_config.injector_mode == "cli": + self.injector = CLIExtensionInjector(injector_config) + elif injector_config.injector_mode == "cdp": + self.injector = CDPExtensionInjector(injector_config) + elif injector_config.injector_mode == "bb": + self.injector = BBExtensionInjector(injector_config) + elif injector_config.injector_mode == "discover": + self.injector = DiscoverExtensionInjector(injector_config) else: - raise TypeError("server must be a mapping, None, or omitted") - self.custom_commands: list[ModCDPAddCustomCommandParams] = list(custom_commands or []) - self.custom_events: list[ModCDPAddCustomEventParams] = list(custom_events or []) - self.custom_middlewares: list[ModCDPAddMiddlewareParams] = list(custom_middlewares or []) - - self.extension_id: str | None = None - self.ext_target_id: str | None = None - self.ext_session_id: str | None = None - self.ext_execution_context_id: int | None = None + raise RuntimeError(f"unknown injector.injector_mode={injector_config.injector_mode}") + self.cdp_url: str | None = self.upstream.config.upstream_ws_cdp_url + if isinstance(types, CDPTypes): + self.types = types + elif isinstance(types, Mapping): + self.types = CDPTypes(types) + else: + self.types = CDPTypes() + self.latency: ModCDPPingLatency | None = None self.connect_timing: ModCDPConnectTiming | None = None self.last_command_timing: ModCDPCommandTiming | None = None - self.last_raw_timing: ModCDPRawTiming | None = None - self.transport: UpstreamTransport | None = None - self._next_id = 0 - self._pending: dict[int, PendingEntry] = {} self._handlers: dict[str, list[Handler]] = {} self._handler_wrappers: dict[tuple[str, Handler], Handler] = {} - self._lock = threading.Lock() - self.auto_sessions = AutoSessionRouter( - lambda method, params=None, session_id=None: self._send_message(method, params or {}, session_id), - lambda: self.injector["injector_execution_context_timeout_ms"], - ) - self._schema_lock = threading.RLock() - self._command_params_schemas: dict[str, TypeAdapter[Any]] = {} - self._command_result_schemas: dict[str, TypeAdapter[Any]] = {} - self._event_schemas: dict[str, TypeAdapter[Any]] = {} - self._command_result_model_schemas: set[str] = set() - self._event_model_schemas: set[str] = set() - self._event_classes: dict[str, type[CDPEvent]] = {} - if self.client["client_hydrate_aliases"]: + self.router = AutoSessionRouter(self.upstream, self.types, parsed_router_config.model_dump()) + if self.config.client_hydrate_aliases: install_cdp_surface(self) self.Mod = _ModDomain(self) self._closed = False - self._launched_browser: Any | None = None - self._extension_injectors: list[ExtensionInjector] = [] self._heartbeat_stop: threading.Event | None = None self._heartbeat_thread: threading.Thread | None = None - self._cdp = _RawCDP(self) - self._hydrate_native_protocol_schemas() - self._hydrate_custom_surface() + + def toJSON(self) -> dict[str, object]: + return modCDPToJSON( + self, + { + "config": { + "client_config": self.config, + "server_config": self.server_config, + }, + "state": { + "event_wait_cleanups": len(self._handlers), + "heartbeat_timer": self._heartbeat_thread is not None, + "latency": self.latency.get("round_trip_ms") if self.latency else None, + "connected": self.connect_timing is not None, + }, + "children": { + "launcher": self.launcher, + "upstream": self.upstream, + "injector": self.injector, + "router": self.router, + "types": self.types, + }, + }, + ) def connect(self) -> "ModCDPClient": connect_started_at = int(time.time() * 1000) transport_started_at = int(time.time() * 1000) self._connect_upstream_transport() transport_connected_at = int(time.time() * 1000) - if self.transport is None: - raise RuntimeError("upstream transport did not connect.") - self.transport.onRecv(lambda message: self._on_recv(cast(CdpMessage, message))) - self.transport.onClose(lambda error: self._handle_transport_close(error)) - - if self.upstream_endpoint_kind == "modcdp_server": - self.transport.waitForPeer() - if self.server is not None: - self._send_message("Mod.configure", cast(ProtocolParams, self._server_configure_params())) - threading.Thread(target=self._measure_ping_latency, daemon=True).start() - self._start_heartbeat() + self.upstream.onRecv(lambda message: self._on_recv(message)) + self.upstream.onClose(lambda error: self._handle_transport_close(error)) + + if self.injector is None and self.server_config is None: connected_at = int(time.time() * 1000) - self.connect_timing = cast(ModCDPConnectTiming, { - "started_at": connect_started_at, - "upstream_mode": self.upstream.get("upstream_mode"), - "upstream_endpoint_kind": self.upstream_endpoint_kind, - "transport_started_at": transport_started_at, - "transport_connected_at": transport_connected_at, - "transport_duration_ms": transport_connected_at - transport_started_at, - "connected_at": connected_at, - "duration_ms": connected_at - connect_started_at, - }) + self.connect_timing = ModCDPConnectTiming( + started_at=connect_started_at, + upstream_mode=self.upstream.config.upstream_mode, + transport_started_at=transport_started_at, + transport_connected_at=transport_connected_at, + transport_duration_ms=transport_connected_at - transport_started_at, + connected_at=connected_at, + duration_ms=connected_at - connect_started_at, + ) return self - self._initialize_raw_cdp_transport() + self.router.start() injector_started_at = int(time.time() * 1000) - if self.injector.get("injector_mode") == "none": - raise RuntimeError("injector.injector_mode='none' cannot be used with a raw_cdp upstream.") - ext = self._inject_extension(self._extension_injectors) + if self.injector is None: + raise RuntimeError("injector.injector_mode=none cannot be used with an extension-routed browser upstream.") + ext = self._inject_extension() injector_completed_at = int(time.time() * 1000) - self.extension_id = ext["extension_id"] - self.ext_target_id = ext["target_id"] - self.ext_session_id = ext["session_id"] - self._send_message("Runtime.enable", {}, self.ext_session_id) - self.ext_execution_context_id = self.auto_sessions.waitForExecutionContext( - self.ext_session_id, - self.injector["injector_execution_context_timeout_ms"], - ) - self._send_message("Runtime.addBinding", {"name": CUSTOM_EVENT_BINDING_NAME}, self.ext_session_id) - if self.client["client_mirror_upstream_events"]: - self._send_message("Runtime.addBinding", {"name": UPSTREAM_EVENT_BINDING_NAME}, self.ext_session_id) - - if self.server is not None: - self._send_raw(wrap_command_if_needed( - "Mod.configure", - cast(ProtocolParams, self._server_configure_params()), - routes=cast(ModCDPRoutes, self.client["client_routes"]), - cdp_session_id=self.ext_session_id, - )) + if self.injector.target_id is None or self.injector.session_id is None: + raise RuntimeError(f"{type(self.injector).__name__} did not record a ModCDP extension target.") + self.router.send("Runtime.enable", {}, self.injector.session_id) + self.router.send("Runtime.addBinding", {"name": CUSTOM_EVENT_BINDING_NAME}, self.injector.session_id) + if self.config.client_mirror_upstream_events: + self.router.send("Runtime.addBinding", {"name": UPSTREAM_EVENT_BINDING_NAME}, self.injector.session_id) + + if self.server_config is not None: + self.send("Mod.configure", self._server_configure_params()) self._start_heartbeat() threading.Thread(target=self._measure_ping_latency, daemon=True).start() connected_at = int(time.time() * 1000) - self.connect_timing = cast(ModCDPConnectTiming, { - "started_at": connect_started_at, - "upstream_mode": self.upstream.get("upstream_mode"), - "upstream_endpoint_kind": self.upstream_endpoint_kind, - "transport_started_at": transport_started_at, - "transport_connected_at": transport_connected_at, - "transport_duration_ms": transport_connected_at - transport_started_at, - "injector_source": ext.get("source"), - "injector_started_at": injector_started_at, - "injector_completed_at": injector_completed_at, - "injector_duration_ms": injector_completed_at - injector_started_at, - "connected_at": connected_at, - "duration_ms": connected_at - connect_started_at, - }) + self.connect_timing = ModCDPConnectTiming( + started_at=connect_started_at, + upstream_mode=self.upstream.config.upstream_mode, + transport_started_at=transport_started_at, + transport_connected_at=transport_connected_at, + transport_duration_ms=transport_connected_at - transport_started_at, + injector_source=ext.source, + injector_started_at=injector_started_at, + injector_completed_at=injector_completed_at, + injector_duration_ms=injector_completed_at - injector_started_at, + connected_at=connected_at, + duration_ms=connected_at - connect_started_at, + ) return self def _send_command( @@ -454,83 +351,70 @@ def _send_command( validate_custom_schema: bool = True, ) -> Any: started_at = int(time.time() * 1000) - command_params = cast(ProtocolParams, dict(params or {})) - if method == "Mod.addCustomCommand": - self._register_custom_command(command_params) - expression = command_params.get("expression") - if not isinstance(expression, str) or not expression: - completed_at = int(time.time() * 1000) - self.last_command_timing = { - "method": method, - "target": "client", - "started_at": started_at, - "completed_at": completed_at, - "duration_ms": completed_at - started_at, - } - return AwaitableDict({"name": cast(str, command_params.get("name")), "registered": True}) - command_params = self._custom_command_wire_params(command_params) - elif method == "Mod.addCustomEvent": - self._register_custom_event(command_params) - if self.ext_session_id is None and self.upstream_endpoint_kind != "modcdp_server": - completed_at = int(time.time() * 1000) - self.last_command_timing = { - "method": method, - "target": "client", - "started_at": started_at, - "completed_at": completed_at, - "duration_ms": completed_at - started_at, - } - return AwaitableDict({"name": cast(str, command_params.get("name")), "registered": True}) - command_params = self._custom_event_wire_params(command_params) - should_validate_params = validate_custom_schema or method in self._command_params_schemas - should_validate_result = validate_custom_schema or method in self._command_result_schemas - if method not in {"Mod.addCustomCommand", "Mod.addCustomEvent"} and should_validate_params: - command_params = self._validate_command_params(method, command_params) - - if self.upstream_endpoint_kind == "modcdp_server": - result = self._send_message(method, command_params) - if should_validate_result and method != "Mod.addCustomCommand": - result = self._validate_command_result(method, result) + preparation = self.types.prepareCommand( + method, + dict(params or {}), + can_register_locally=method == "Mod.addCustomCommand" + or ( + method in ("Mod.addCustomEvent", "Mod.addMiddleware") + and (self.injector is None or self.injector.session_id is None) + ), + ) + if preparation.local_result is not None: + completed_at = int(time.time() * 1000) + self.last_command_timing = ModCDPCommandTiming( + method=method, + target="client", + started_at=started_at, + completed_at=completed_at, + duration_ms=completed_at - started_at, + ) + result = self.types.parseCommandResult(method, preparation.local_result) if validate_custom_schema else preparation.local_result + return AwaitableDict(dict(result)) if isinstance(result, Mapping) else AwaitableValue(result) + command_params = preparation.params + if self.injector is None and self.server_config is None: + result = self.router.send(method, command_params, session_id) + result = self.types.parseCommandResult(method, result) if validate_custom_schema else result completed_at = int(time.time() * 1000) - self.last_command_timing = { - "method": method, - "target": "modcdp_server", - "started_at": started_at, - "completed_at": completed_at, - "duration_ms": completed_at - started_at, - } - return AwaitableDict(result) if isinstance(result, dict) else AwaitableValue(result) + self.last_command_timing = ModCDPCommandTiming( + method=method, + target="browser_targets", + started_at=started_at, + completed_at=completed_at, + duration_ms=completed_at - started_at, + ) + return AwaitableDict(dict(result)) if isinstance(result, Mapping) else AwaitableValue(result) command = wrap_command_if_needed( method, command_params, - routes=cast(ModCDPRoutes, self.client["client_routes"]), - cdp_session_id=self.ext_session_id, - target_cdp_session_id=session_id, + routes=self.router.config.router_routes, + cdp_session_id=session_id, ) - result = self._send_raw(command) - if should_validate_result and method != "Mod.addCustomCommand": - result = self._validate_command_result(method, result) + if command.target == "direct_cdp": + step = command.steps[0] + step_params = step.params + result = self.router.send(step.method, step_params if isinstance(step_params, Mapping) else {}, step.sessionId) + elif command.target == "service_worker": + if self.injector is None or self.injector.session_id is None: + raise RuntimeError("service_worker commands require an injected ModCDP extension target.") + step = self.types.serviceWorkerCommandStep(method, command_params, session_id) + result = self.router.send(step.method, step.params or {}, self.injector.session_id) + unwrap = step.unwrap + result = unwrap_response_if_needed(result, unwrap) + else: + raise RuntimeError(f"Unsupported command target {command.target!r}") + if validate_custom_schema: + result = self.types.parseCommandResult(method, result) completed_at = int(time.time() * 1000) - self.last_command_timing = { - "method": method, - "target": command["target"], - "started_at": started_at, - "completed_at": completed_at, - "duration_ms": completed_at - started_at, - } - return AwaitableDict(result) if isinstance(result, dict) else AwaitableValue(result) - - def sendRaw( - self, - method: str, - params: Mapping[str, Any] | None = None, - session_id: str | None = None, - ) -> ProtocolResult: - result = self._send_message(method, cast(ProtocolParams, dict(params or {})), session_id, record_raw_timing=True) - if not isinstance(result, dict): - raise RuntimeError(f"{method} returned non-object value: {result!r}") - return result + self.last_command_timing = ModCDPCommandTiming( + method=method, + target=command.target, + started_at=started_at, + completed_at=completed_at, + duration_ms=completed_at - started_at, + ) + return AwaitableDict(dict(result)) if isinstance(result, Mapping) else AwaitableValue(result) def send( self, @@ -538,26 +422,18 @@ def send( params: Mapping[str, Any] | None = None, session_id: str | None = None, ) -> AwaitableDict | AwaitableValue: - result = self._send_command(method, params, session_id=session_id, validate_custom_schema=False) + result = self._send_command(method, params, session_id=session_id) if isinstance(result, AwaitableDict): return result if isinstance(result, AwaitableValue): return result - return AwaitableDict(result) if isinstance(result, dict) else AwaitableValue(result) + return AwaitableDict(dict(result)) if isinstance(result, Mapping) else AwaitableValue(result) def on(self, event: str | type[CDPEvent], handler: Handler) -> "ModCDPClient": event_name = cdp_event_name(event) if not isinstance(event, str) else event if event_name is None: raise TypeError("event must be a CDP event class or event name string") - wrapped_handler = handler - if not isinstance(event, str): - event_class = event - - def typed_handler(payload): - typed_payload = event_class.model_validate(payload) if isinstance(payload, Mapping) else payload - return handler(typed_payload) - - wrapped_handler = typed_handler + wrapped_handler: Handler = handler if isinstance(event, str) else _typed_event_handler(event, handler) self._handler_wrappers[(event_name, handler)] = wrapped_handler handlers = self._handlers.setdefault(event_name, []) if wrapped_handler not in handlers: @@ -565,9 +441,9 @@ def typed_handler(payload): return self def once(self, event: str | type[CDPEvent], handler: Handler) -> "ModCDPClient": - def wrapped_handler(payload: Any) -> Any: + def wrapped_handler(payload: object, session_id: str | None = None) -> object: self.off(event, wrapped_handler) - return handler(payload) + return _call_handler(handler, payload, session_id) return self.on(event, wrapped_handler) @@ -589,9 +465,41 @@ async def _value(): return _value().__await__() - def _run_handler(self, handler: Handler, payload: Any, event_name: str) -> None: + def configure( + self, + *, + upstream: Mapping[str, Any] | None = None, + router: Mapping[str, Any] | None = None, + client_config: Mapping[str, Any] | None = None, + server_config: Mapping[str, object] | ModCDPServerConfig | None | object = DEFAULT_SERVER, + ) -> "ModCDPClient": + if client_config is not None: + self.config = ClientConfig.model_validate({**self.config.model_dump(), **dict(client_config)}) + self.upstream.update({"upstream_cdp_send_timeout_ms": self.config.client_cdp_send_timeout_ms}) + if upstream is not None: + self.upstream.update(dict(upstream)) + if router is not None: + current_routes = dict(self.router.config.router_routes) + raw_incoming_routes = router.get("router_routes") or {} + incoming_routes = {str(key): str(value) for key, value in raw_incoming_routes.items()} if isinstance(raw_incoming_routes, Mapping) else {} + self.router.config = RouterConfig.model_validate( + { + **self.router.config.model_dump(), + **dict(router), + "router_routes": { + **current_routes, + **incoming_routes, + }, + } + ) + if server_config is not DEFAULT_SERVER: + self.server_config = None if server_config is None else ModCDPServerConfig.model_validate(server_config) + return self + + def _run_handler(self, handler: Handler, *args: object) -> None: + event_name = str(args[0]) if args else "" try: - result = handler(payload) + result = _call_handler(handler, *args) if inspect.iscoroutine(result): asyncio.run(result) except Exception as e: @@ -600,7 +508,7 @@ def _run_handler(self, handler: Handler, payload: Any, event_name: str) -> None: def __getattr__(self, domain: str): if domain.startswith("_"): raise AttributeError(domain) - if not self.client["client_hydrate_aliases"]: + if not self.config.client_hydrate_aliases: raise AttributeError(domain) from ..types.generated.cdp import DynamicDomain @@ -608,107 +516,102 @@ def __getattr__(self, domain: str): setattr(self, domain, dynamic) return dynamic - def _server_configure_params(self) -> ModCDPServerConfig: - server = dict(self.server or {}) - server_routes = server.pop("server_routes", None) - server_loopback_cdp_url = server.pop("server_loopback_cdp_url", None) - server_browser_token = server.pop("server_browser_token", None) - server_cdp_send_timeout_ms = server.pop( - "server_cdp_send_timeout_ms", - self.client["client_cdp_send_timeout_ms"], - ) - server_loopback_execution_context_timeout_ms = server.pop( - "server_loopback_execution_context_timeout_ms", - self.injector["injector_execution_context_timeout_ms"], - ) - server_ws_connect_error_settle_timeout_ms = server.pop( - "server_ws_connect_error_settle_timeout_ms", - self.upstream["upstream_ws_connect_error_settle_timeout_ms"], - ) - server_downstream_client_timeout_ms = server.pop( - "server_downstream_client_timeout_ms", - max(int(self.client["client_heartbeat_interval_ms"]) * 4, 1_000), - ) - custom_events: list[ModCDPAddCustomEventObjectParams] = [] - for event in self.custom_events: - custom_events.append( - {"name": event} - if isinstance(event, str) - else cast(ModCDPAddCustomEventObjectParams, self._custom_event_wire_params(cast(ProtocolParams, event))) - ) - custom_commands: list[ModCDPAddCustomCommandParams] = [ - cast(ModCDPAddCustomCommandParams, self._custom_command_wire_params(cast(ProtocolParams, command))) - for command in self.custom_commands - if isinstance(command.get("expression"), str) and command.get("expression") - ] - custom_middlewares: list[ModCDPAddMiddlewareParams] = list(self.custom_middlewares) - return cast(ModCDPServerConfig, { - "upstream": { - "upstream_mode": self.upstream.get("upstream_mode"), - **({"upstream_nats_url": self.upstream.get("upstream_nats_url")} if self.upstream.get("upstream_nats_url") else {}), - **( - {"upstream_nats_subject_prefix": self.upstream.get("upstream_nats_subject_prefix")} - if self.upstream.get("upstream_nats_subject_prefix") - else {} - ), + def _server_configure_params(self) -> dict[str, object]: + server_model = self.server_config or ModCDPServerConfig() + configured_server_config = server_model.model_dump(exclude_none=True, exclude_unset=True) + launcher_server_config = self.launcher.configForServer(self.upstream) + has_upstream_config = "upstream" in launcher_server_config or "upstream" in configured_server_config + upstream = { + **_mapping_dict(launcher_server_config.get("upstream")), + **_mapping_dict(configured_server_config.get("upstream")), + } + router = { + **_mapping_dict(launcher_server_config.get("router")), + **_mapping_dict(configured_server_config.get("router")), + } + server_client_config = { + **_mapping_dict(launcher_server_config.get("client_config")), + **_mapping_dict(configured_server_config.get("client_config")), + } + downstream = { + **_mapping_dict(launcher_server_config.get("downstream")), + **_mapping_dict(configured_server_config.get("downstream")), + } + custom_events = self.types.customEventWireRegistrations() + custom_commands = self.types.customCommandWireRegistrations(expression_required=True) + custom_middlewares = self.types.customMiddlewareWireRegistrations() + params: dict[str, object] = { + **( + { + "upstream": { + "upstream_ws_connect_error_settle_timeout_ms": self.upstream.config.upstream_ws_connect_error_settle_timeout_ms, + **upstream, + }, + } + if has_upstream_config + else {} + ), + "router": { + "loopback_execution_context_timeout_ms": self.injector.config.injector_execution_context_timeout_ms + if self.injector is not None + else self.router.config.loopback_execution_context_timeout_ms, + **router, }, - "client": {"client_routes": self.client["client_routes"]}, - "server": { - "server_cdp_send_timeout_ms": server_cdp_send_timeout_ms, - "server_loopback_execution_context_timeout_ms": server_loopback_execution_context_timeout_ms, - "server_ws_connect_error_settle_timeout_ms": server_ws_connect_error_settle_timeout_ms, - "server_downstream_client_timeout_ms": server_downstream_client_timeout_ms, - **({"server_routes": server_routes} if server_routes is not None else {}), - **({"server_loopback_cdp_url": server_loopback_cdp_url} if server_loopback_cdp_url is not None else {}), - **({"server_browser_token": server_browser_token} if server_browser_token is not None else {}), - **server, + "client_config": { + "client_cdp_send_timeout_ms": self.config.client_cdp_send_timeout_ms, + **server_client_config, + }, + "downstream": { + "downstream_client_timeout_ms": max(self.config.client_heartbeat_interval_ms * 4, 1_000), + **downstream, }, "custom_events": custom_events, "custom_commands": custom_commands, "custom_middlewares": custom_middlewares, - }) + } + server_browser_token = configured_server_config.get("server_browser_token") + if server_browser_token is not None: + params["server_browser_token"] = server_browser_token + return params def close(self) -> None: if self._closed: return self._closed = True self._stop_heartbeat() - if self._launched_browser is not None: - self._launched_browser["close"]() - self._launched_browser = None + self.router.stop() + self.launcher.close() try: - if self.transport: - self.transport.close() + self.upstream.close() except Exception: pass - self.transport = None - for injector in self._extension_injectors: + if self.injector is not None: try: - injector.close() + self.injector.close() except Exception: pass - self._extension_injectors = [] def _handle_transport_close(self, error: Exception) -> None: self._stop_heartbeat() - self._reject_all(error) + self._emit_event("error", error, None) def _start_heartbeat(self) -> None: self._stop_heartbeat() - if not self.server or self.server.get("server_close_browser_on_downstream_disconnect") is not True: + if self.server_config is None or self.server_config.downstream is None: + return + if self.server_config.downstream.downstream_close_browser_on_disconnect is not True: return - interval_ms = int(self.client["client_heartbeat_interval_ms"]) stop = threading.Event() - self._heartbeat_stop = stop - def run() -> None: - while not stop.wait(interval_ms / 1000): + def heartbeat() -> None: + while not stop.wait(self.config.client_heartbeat_interval_ms / 1000): try: self.send("Mod.ping", {"sent_at": int(time.time() * 1000)}) except Exception: - return + pass - self._heartbeat_thread = threading.Thread(target=run, daemon=True) + self._heartbeat_stop = stop + self._heartbeat_thread = threading.Thread(target=heartbeat, daemon=True) self._heartbeat_thread.start() def _stop_heartbeat(self) -> None: @@ -718,423 +621,55 @@ def _stop_heartbeat(self) -> None: stop.set() self._heartbeat_thread = None - def _session_id_for_target(self, target_id: str, timeout: float = 0) -> str | None: - if timeout <= 0: - return self.auto_sessions.sessionIdForTarget(target_id) - deadline = time.time() + timeout - while time.time() <= deadline: - session_id = self.auto_sessions.sessionIdForTarget(target_id) - if session_id: - return session_id - time.sleep(self.injector["injector_target_session_poll_interval_ms"] / 1000) - return None - - def _ensure_session_id_for_target(self, target_id: str, timeout: float = 0, allow_attach: bool = False) -> str | None: - session_id = self.auto_sessions.sessionIdForTarget(target_id) - if session_id: - return session_id - if allow_attach: - attached_session_id = self.auto_sessions.attachToTarget(target_id) - if attached_session_id: - return attached_session_id - return self._session_id_for_target(target_id, timeout=timeout) - - def _browser_launcher(self): - if self.launcher.get("launcher_mode") == "local": - return LocalBrowserLauncher(self._launch_options()) - if self.launcher.get("launcher_mode") == "remote": - return RemoteBrowserLauncher(self._launch_options(), self.cdp_url) - if self.launcher.get("launcher_mode") == "bb": - return BrowserbaseBrowserLauncher(self._launch_options()) - if self.launcher.get("launcher_mode") == "none": - return NoopBrowserLauncher(self._launch_options()) - raise RuntimeError(f"unknown launcher.launcher_mode={self.launcher.get('launcher_mode')}") - - def _launch_options(self) -> BrowserLaunchOptions: - launch_options = cast(BrowserLaunchOptions, dict(cast(Mapping[str, Any], self.launcher.get("launcher_options") or {}))) - if self.launcher.get("launcher_executable_path"): - launch_options["executable_path"] = cast(str, self.launcher["launcher_executable_path"]) - if self.launcher.get("launcher_user_data_dir"): - launch_options["user_data_dir"] = cast(str, self.launcher["launcher_user_data_dir"]) - return launch_options - def _connect_upstream_transport(self) -> None: - if self.transport is not None: - return - launcher = self._browser_launcher() - transport = self._upstream_transport() - injectors = self._extension_injectors_for_config() - self._extension_injectors = injectors - initial_transport_config = self._upstream_transport_config() - - transport.update(initial_transport_config) - launcher.update(self._launch_options()) - for injector in injectors: - injector.update(self._base_extension_injector_config(None)) - for injector in injectors: - injector.update(cast(ExtensionInjectorConfig, launcher.getInjectorConfig())) - for injector in injectors: - injector.update(cast(ExtensionInjectorConfig, transport.getInjectorConfig())) - for injector in injectors: - injector.prepare() - for injector in injectors: - launcher.update(injector.getLauncherConfig()) - for injector in injectors: - transport.update(injector.getTransportConfig()) - launcher.update(cast(BrowserLaunchOptions, transport.getLauncherConfig())) - launcher.update({"loopback_cdp": self._server_needs_loopback_cdp()}) - transport.update(launcher.getTransportConfig()) - - if transport.endpoint_kind == "modcdp_server": - transport.connect() - if self.launcher.get("launcher_mode") != "none": - launched = launcher.launch() - self._launched_browser = launched - transport.update(launcher.getTransportConfig()) - for injector in injectors: - injector.update(cast(ExtensionInjectorConfig, launcher.getInjectorConfig())) - for injector in injectors: - transport.update(injector.getTransportConfig()) - launched_cdp_url = cast(str | None, self._launched_browser.get("cdp_url")) if self._launched_browser else None - if transport.endpoint_kind == "raw_cdp": - transport.connect() - - self.transport = transport - self.cdp_url = cast( - str | None, - (transport.url or launched_cdp_url) if transport.endpoint_kind == "raw_cdp" else launched_cdp_url, + launcher = self.launcher + transport = self.upstream + + if self.injector is not None: + self.injector.update({"injector_cdp_send_timeout_ms": self.config.client_cdp_send_timeout_ms}) + self.injector.prepare() + launcher.update(self.injector.configForLauncher()) + transport.update(self.injector.configForUpstream()) + launcher.update(transport.configForLauncher()) + server_upstream_ws_cdp_url = ( + self.server_config.upstream.upstream_ws_cdp_url + if self.server_config is not None and self.server_config.upstream is not None + else None ) - if transport.mode == "ws" and transport.url: - # For ws mode, cdp_url has been resolved to the concrete WebSocket CDP endpoint after connect(). - self.upstream["upstream_cdp_url"] = transport.url - server_config = ( - {"server_loopback_cdp_url": launched_cdp_url} - if transport.endpoint_kind == "modcdp_server" and launched_cdp_url and not launched_cdp_url.startswith("pipe://") - else {} + needs_loopback_cdp = ( + self.server_config is not None + and not server_upstream_ws_cdp_url + and (self.server_config.router.router_routes if self.server_config.router is not None else {}).get("*.*") == "loopback_cdp" ) - transport_server_config = transport.getServerConfig() - server_config.update(launcher.getServerConfig()) - server_config.update(transport_server_config) - if self.server is not None and server_config.get("server_loopback_cdp_url"): - configured_loopback = self.server.get("server_loopback_cdp_url") - if "server_loopback_cdp_url" not in self.server or configured_loopback in ( - initial_transport_config.get("cdp_url"), - launched_cdp_url, - ): - self.server = cast(ModCDPServerConfig, {**self.server, **server_config}) - - def _server_needs_loopback_cdp(self) -> bool: - if self.server is None or self.server.get("server_loopback_cdp_url"): - return False - return "loopback_cdp" in set((self.server.get("server_routes") or {}).values()) - - def _upstream_transport_config(self) -> dict[str, Any]: - return { - "cdp_url": self.upstream.get("upstream_cdp_url"), - "upstream_nats_url": self.upstream.get("upstream_nats_url"), - "upstream_nats_subject_prefix": self.upstream.get("upstream_nats_subject_prefix"), - "upstream_nats_wait_timeout_ms": self.upstream.get("upstream_nats_wait_timeout_ms"), - "upstream_reversews_bind": self.upstream.get("upstream_reversews_bind"), - "upstream_reversews_wait_timeout_ms": self.upstream.get("upstream_reversews_wait_timeout_ms"), - "upstream_nativemessaging_manifest": self.upstream.get("upstream_nativemessaging_manifest"), - "upstream_nativemessaging_manifests": self.upstream.get("upstream_nativemessaging_manifests"), - "upstream_nativemessaging_host_name": self.upstream.get("upstream_nativemessaging_host_name"), - "upstream_nativemessaging_wait_timeout_ms": self.upstream.get("upstream_nativemessaging_wait_timeout_ms"), - "injector_extension_id": self.injector.get("injector_extension_id"), - } + launcher.update({"launcher_local_loopback_cdp": needs_loopback_cdp}) + transport.update(launcher.configForUpstream()) - def _initialize_raw_cdp_transport(self) -> None: - self._send_message("Target.setAutoAttach", { - "autoAttach": True, - "waitForDebuggerOnStart": False, - "flatten": True, + if self.launcher.config.launcher_mode != "none": + launched = launcher.launch() + transport.update(launcher.configForUpstream()) + if self.injector is not None: + transport.update(self.injector.configForUpstream()) + launched_cdp_url = launcher.launched.cdp_url if launcher.launched else None + transport.connect() + + self.cdp_url = transport.url or launched_cdp_url + if transport.upstream_mode == "ws" and transport.url: + # For ws mode, cdp_url has been resolved to the concrete WebSocket CDP endpoint after connect(). + self.upstream.config = UpstreamTransportConfig.model_validate({**self.upstream.config.model_dump(), "upstream_ws_cdp_url": transport.url}) + + def _inject_extension(self) -> ExtensionInfo: + if self.injector is None: + raise RuntimeError("injector.injector_mode=none cannot inject an extension.") + self.injector.update({ + "send": self.upstream.send, + "injector_cdp_send_timeout_ms": self.config.client_cdp_send_timeout_ms, }) - self._send_message("Target.setDiscoverTargets", {"discover": True}) - - def _upstream_transport(self): - mode = self.upstream.get("upstream_mode") - if mode == "ws": - return WebSocketUpstreamTransport() - if mode == "pipe": - return PipeUpstreamTransport() - if mode == "reversews": - return ReverseWebSocketUpstreamTransport() - if mode == "nativemessaging": - return NativeMessagingUpstreamTransport() - if mode == "nats": - return NatsUpstreamTransport() - raise RuntimeError(f"unknown upstream.upstream_mode={mode}") - - def _extension_injectors_for_config(self) -> list[ExtensionInjector]: - mode = self.injector.get("injector_mode") - if mode == "none": - return [] - injectors: list[ExtensionInjector] = [] - prefer_launch_injection = mode == "auto" and self.launcher.get("launcher_mode") == "local" - if mode in ("auto", "discover") and not prefer_launch_injection: - injectors.append(DiscoveredExtensionInjector()) - if mode in ("auto", "inject"): - if self.launcher.get("launcher_mode") == "bb": - injectors.append(BBBrowserExtensionInjector()) - if self.launcher.get("launcher_mode") == "local": - injectors.append(LocalBrowserLaunchExtensionInjector()) - injectors.append(ExtensionsLoadUnpackedInjector()) - if prefer_launch_injection: - injectors.append(DiscoveredExtensionInjector()) - if mode in ("auto", "borrow"): - injectors.append(BorrowedExtensionInjector()) - if not injectors: - raise RuntimeError(f"unknown injector.injector_mode={mode}") - return injectors - - def _base_extension_injector_config(self, send: Any | None) -> ExtensionInjectorConfig: - trust_service_worker_target = ( - self.injector["injector_trust_service_worker_target"] - or len(self.injector["injector_service_worker_url_includes"]) > 0 - or any( - len([part for part in suffix.split("/") if part]) > 1 - for suffix in self.injector["injector_service_worker_url_suffixes"] - ) - ) - - def send_cdp(method: str, params: ProtocolParams | None = None, session_id: str | None = None) -> ProtocolResult: - if send is None: - raise RuntimeError("Extension injector CDP send is not connected.") - return self._send_message( - method, - params or {}, - session_id, - timeout=self.client["client_cdp_send_timeout_ms"] / 1000, - ) - - def attach_to_target(target_id: str) -> str | None: - return self._ensure_session_id_for_target( - target_id, - timeout=self.injector["injector_service_worker_probe_timeout_ms"] / 1000, - allow_attach=True, - ) - - return { - "send": send_cdp if send is not None else None, - "sessionIdForTarget": self.auto_sessions.sessionIdForTarget, - "attachToTarget": attach_to_target if send is not None else None, - "waitForExecutionContext": self.auto_sessions.waitForExecutionContext, - "injector_extension_path": cast(str | None, self.injector.get("injector_extension_path")), - "injector_extension_id": cast(str | None, self.injector.get("injector_extension_id")), - "injector_service_worker_url_includes": cast(list[str], self.injector["injector_service_worker_url_includes"]), - "injector_service_worker_url_suffixes": cast(list[str], self.injector["injector_service_worker_url_suffixes"]), - "injector_trust_service_worker_target": trust_service_worker_target, - "injector_require_service_worker_target": self.injector["injector_require_service_worker_target"] or self.injector.get("injector_mode") == "discover", - "injector_service_worker_ready_expression": cast(str | None, self.injector.get("injector_service_worker_ready_expression")), - "injector_cdp_send_timeout_ms": self.client["client_cdp_send_timeout_ms"], - "injector_execution_context_timeout_ms": self.injector["injector_execution_context_timeout_ms"], - "injector_service_worker_probe_timeout_ms": self.injector["injector_service_worker_probe_timeout_ms"], - "injector_service_worker_ready_timeout_ms": self.injector["injector_service_worker_ready_timeout_ms"], - "injector_service_worker_poll_interval_ms": self.injector["injector_service_worker_poll_interval_ms"], - "injector_target_session_poll_interval_ms": self.injector["injector_target_session_poll_interval_ms"], - } - - def _inject_extension(self, injectors: list[ExtensionInjector] | None = None) -> ExtensionInfo: - injectors = injectors or self._extension_injectors_for_config() - errors: list[str] = [] - for injector in injectors: - injector.update(self._base_extension_injector_config(self._send_message)) - try: - injector.prepare() - result = injector.inject() - if result: - return cast(ExtensionInfo, result) - except Exception as error: - injector.last_error = error - errors.append(f"{type(injector).__name__}: {error}") - detail = f"\n\n{chr(10).join(errors)}" if errors else "" - raise RuntimeError(f"Cannot install, discover, or borrow the ModCDP extension in the running browser.{detail}") - - # --- internals --------------------------------------------------------- - - def _send_raw(self, wrapped: TranslatedCommand) -> Any: - if wrapped["target"] == "direct_cdp": - step = wrapped["steps"][0] - return self._send_message(step["method"], step.get("params") or {}, step.get("sessionId")) - if wrapped["target"] != "service_worker": - raise RuntimeError(f"Unsupported command target {wrapped['target']!r}") - - result: ProtocolResult = {} - unwrap: str | None = None - for step in wrapped["steps"]: - params = dict(step.get("params") or {}) - if step["method"] == "Runtime.callFunctionOn" and "executionContextId" not in params: - if self.ext_execution_context_id is None: - self.ext_execution_context_id = self.auto_sessions.waitForExecutionContext( - self.ext_session_id, - self.injector["injector_execution_context_timeout_ms"], - ) - params["executionContextId"] = self.ext_execution_context_id - result = self._send_message(step["method"], params, self.ext_session_id) - unwrap = step.get("unwrap") - return unwrap_response_if_needed(result, unwrap) - - def _hydrate_custom_surface(self) -> None: - for command in self.custom_commands: - self._register_custom_command(cast(ProtocolParams, command)) - for event in self.custom_events: - if isinstance(event, str): - continue - self._register_custom_event(cast(ProtocolParams, event)) - - def _hydrate_native_protocol_schemas(self) -> None: - with self._schema_lock: - for domain_name, domain_class in vars(generated_cdp).items(): - if not domain_name.endswith("Domain") or not isinstance(domain_class, type): - continue - domain = domain_name.removesuffix("Domain") - nested_classes = { - name: value - for name, value in vars(domain_class).items() - if isinstance(value, type) and issubclass(value, CDPModel) - } - for class_name, params_class in nested_classes.items(): - if issubclass(params_class, CDPEvent): - event_name = getattr(params_class, "cdp_event_name", None) - if isinstance(event_name, str): - self._event_schemas[event_name] = TypeAdapter(params_class) - continue - if not class_name.startswith("_") or not class_name.endswith("Params"): - continue - command_base = class_name[1:-6] - result_class = nested_classes.get(f"_{command_base}Result") - if result_class is None: - continue - method = f"{domain}.{command_base[:1].lower()}{command_base[1:]}" - if issubclass(params_class, CDPParams): - self._command_params_schemas[method] = TypeAdapter(params_class) - self._command_result_schemas[method] = TypeAdapter(result_class) - self._command_result_model_schemas.add(method) - - def _register_custom_command(self, params: ProtocolParams) -> None: - name = params.get("name") - if not isinstance(name, str) or not name: - raise TypeError("name must be a non-empty string") - params_schema, _, _ = self._adapter_from_optional_schema(params.get("params_schema"), "params_schema") - result_schema, _, result_is_model = self._adapter_from_optional_schema(params.get("result_schema"), "result_schema") - with self._schema_lock: - if params_schema is not None: - self._command_params_schemas[name] = params_schema - if result_schema is not None: - self._command_result_schemas[name] = result_schema - if result_is_model: - self._command_result_model_schemas.add(name) - else: - self._command_result_model_schemas.discard(name) - - def _custom_command_wire_params(self, params: ProtocolParams) -> ProtocolParams: - wire = dict(params) - _, params_schema, _ = self._adapter_from_optional_schema(wire.get("params_schema"), "params_schema") - _, result_schema, _ = self._adapter_from_optional_schema(wire.get("result_schema"), "result_schema") - if "params_schema" in wire: - wire["params_schema"] = cast(JsonValue, params_schema) - if "result_schema" in wire: - wire["result_schema"] = cast(JsonValue, result_schema) - return cast(ProtocolParams, wire) - - def _custom_event_wire_params(self, params: ProtocolParams) -> ProtocolParams: - wire = dict(params) - _, event_schema, _ = self._adapter_from_optional_schema(wire.get("event_schema"), "event_schema") - if "event_schema" in wire: - wire["event_schema"] = cast(JsonValue, event_schema) - return cast(ProtocolParams, wire) - - def _register_custom_event(self, params: ProtocolParams) -> None: - name = params.get("name") - if not isinstance(name, str) or not name: - raise TypeError("name must be a non-empty string") - event_schema, _, event_is_model = self._adapter_from_optional_schema(params.get("event_schema"), "event_schema") - if event_schema is not None: - with self._schema_lock: - self._event_schemas[name] = event_schema - if event_is_model: - self._event_model_schemas.add(name) - else: - self._event_model_schemas.discard(name) - - def _adapter_from_optional_schema(self, schema: Any | None, field_name: str) -> tuple[TypeAdapter[Any] | None, dict[str, Any] | None, bool]: - if schema is None: - return None, None, False - if isinstance(schema, type) and issubclass(schema, BaseModel): - return TypeAdapter(schema), schema.model_json_schema(), True - if not isinstance(schema, Mapping): - raise TypeError(f"{field_name} must be a JSON Schema object") - schema_dict = dict(cast(Mapping[str, Any], schema)) - return type_adapter_from_json_schema(schema_dict), schema_dict, False - - def _validate_command_params(self, method: str, params: ProtocolParams) -> ProtocolParams: - with self._schema_lock: - adapter = self._command_params_schemas.get(method) - if adapter is None: - return params - try: - validated = adapter.validate_python(dict(params), strict=True) - except ValidationError as e: - raise ValueError(f"{method} params did not match params_schema: {e}") from e - jsonable = ( - validated.model_dump(mode="json", exclude_none=True, by_alias=True) - if isinstance(validated, BaseModel) - else to_jsonable_python(validated) - ) - if not isinstance(jsonable, Mapping): - raise ValueError(f"{method} params_schema must validate to a JSON object") - return cast(ProtocolParams, dict(jsonable)) - - def _validate_command_result(self, method: str, result: Any) -> Any: - with self._schema_lock: - adapter = self._command_result_schemas.get(method) - if adapter is None: - return result - try: - validated = adapter.validate_python(result, strict=True) - except ValidationError as e: - raise ValueError(f"{method} result did not match result_schema: {e}") from e - if isinstance(validated, CDPModel): - return cast(JsonValue, validated.model_dump(mode="json", exclude_none=True, by_alias=True)) - if method in self._command_result_model_schemas and isinstance(validated, BaseModel): - fields = list(type(validated).model_fields) - if len(fields) == 1: - return cast(JsonValue, getattr(validated, fields[0])) - return cast(JsonValue, validated) - return cast(JsonValue, to_jsonable_python(validated)) - - def _validate_event_payload(self, event: str, payload: ProtocolPayload) -> Any | None: - with self._schema_lock: - adapter = self._event_schemas.get(event) - event_class = self._event_classes.get(event) - if adapter is None and event_class is None: - return dict(payload) - if adapter is None and event_class is not None: - try: - return cast(ProtocolPayload, event_class.model_validate(dict(payload)).model_dump(mode="json", exclude_none=True, by_alias=True)) - except ValidationError as e: - raise ValueError(f"{event} event did not match native event schema: {e}") from e - assert adapter is not None - try: - validated = adapter.validate_python(dict(payload), strict=True) - except ValidationError as direct_error: - if set(payload.keys()) != {"value"}: - raise ValueError(f"{event} event did not match event_schema: {direct_error}") from direct_error - try: - validated = adapter.validate_python(payload["value"], strict=True) - except ValidationError as value_error: - raise ValueError(f"{event} event did not match event_schema: {value_error}") from value_error - if event in self._event_model_schemas: - return cast(ProtocolPayload, validated) - return {"value": cast(JsonValue, to_jsonable_python(validated))} - if isinstance(validated, CDPModel): - return cast(ProtocolPayload, validated.model_dump(mode="json", exclude_none=True, by_alias=True)) - if event in self._event_model_schemas: - return cast(ProtocolPayload, validated) - jsonable = to_jsonable_python(validated) - if isinstance(jsonable, Mapping): - return cast(ProtocolPayload, dict(jsonable)) - return {"value": cast(JsonValue, jsonable)} + self.injector.prepare() + result = self.injector.inject() + if not result: + raise RuntimeError(f"{type(self.injector).__name__} did not return a ModCDP extension target.") + self.injector.recordInjectionResult(result) + return result def _measure_ping_latency(self) -> ModCDPPingLatency | None: sent_at = int(time.time() * 1000) @@ -1159,105 +694,80 @@ def on_pong(payload: ProtocolPayload) -> None: returned_at = int(time.time() * 1000) raw_received_at = payload.get("received_at") received_at = raw_received_at if isinstance(raw_received_at, (int, float)) else None - latency: ModCDPPingLatency = { - "sent_at": sent_at, - "received_at": received_at, - "returned_at": returned_at, - "round_trip_ms": returned_at - sent_at, - "service_worker_ms": received_at - sent_at if received_at is not None else None, - "return_path_ms": returned_at - received_at if received_at is not None else None, - } + latency = ModCDPPingLatency( + sent_at=sent_at, + received_at=received_at, + returned_at=returned_at, + round_trip_ms=returned_at - sent_at, + service_worker_ms=received_at - sent_at if received_at is not None else None, + return_path_ms=returned_at - received_at if received_at is not None else None, + ) self.latency = latency return latency - def _send_message( - self, - method: str, - params: MessageParams | None = None, - session_id: str | None = None, - timeout: float | None = None, - record_raw_timing: bool = False, - ) -> ProtocolResult: - effective_timeout = self.client["client_cdp_send_timeout_ms"] / 1000 if timeout is None else timeout - with self._lock: - self._next_id += 1 - msg_id = self._next_id - done: Queue[CdpMessage] = Queue() - self._pending[msg_id] = (method, done) - started_at = int(time.time() * 1000) - msg: CdpMessage = {"id": msg_id, "method": method, "params": params or {}} - if session_id: - msg["sessionId"] = session_id - try: - if self.transport is not None: - self.transport.send(cast(dict[str, Any], msg)) - else: - raise RuntimeError("ModCDP upstream is not connected") - except Exception: - with self._lock: - self._pending.pop(msg_id, None) - raise - try: - response = done.get(timeout=effective_timeout) - except Empty: - with self._lock: - self._pending.pop(msg_id, None) - raise RuntimeError(f"{method} timed out after {int(effective_timeout * 1000)}ms") - err = response.get("error") - if err: - raise RuntimeError(f"{method} failed: {err.get('message', err)}") - if record_raw_timing: - completed_at = int(time.time() * 1000) - self.last_raw_timing = { - "method": method, - "started_at": started_at, - "completed_at": completed_at, - "duration_ms": completed_at - started_at, - } - return _json_object(response.get("result")) - - def _reject_all(self, error: Exception) -> None: - with self._lock: - pending = list(self._pending.values()) - self._pending.clear() - for _, done in pending: - done.put({"error": {"message": str(error)}}) - def _on_recv(self, msg: CdpMessage) -> None: if "id" in msg and msg["id"] is not None: - with self._lock: - entry = self._pending.pop(msg["id"], None) - if entry: - entry[1].put(msg) return method = msg.get("method") raw_params = msg.get("params") - params = cast(ProtocolParams, raw_params) if isinstance(raw_params, Mapping) else {} - if isinstance(method, str): - session_id = msg.get("sessionId") - self.auto_sessions.recordProtocolEvent(method, params, session_id if isinstance(session_id, str) else None) - if method and self.ext_session_id is not None and msg.get("sessionId") == self.ext_session_id: + params = _protocol_params(raw_params) + extension_session_id = self.injector.session_id if self.injector is not None else None + if isinstance(method, str) and extension_session_id is not None and msg.get("sessionId") == extension_session_id: session_id = msg.get("sessionId") u = unwrap_event_if_needed( method, params, session_id if isinstance(session_id, str) else None, - self.ext_session_id, + extension_session_id, ) if u: - validated_payload = self._validate_event_payload(u["event"], u["data"]) - if validated_payload is None: - return - for handler in list(self._handlers.get(u["event"], [])): - def run_wrapped_event(handler=handler, payload=validated_payload, event_name=u["event"]): - self._run_handler(handler, payload, event_name) - threading.Thread(target=run_wrapped_event, daemon=True).start() + validated_payload = self.types.parseEventPayload(u.event, u.data) + self._emit_event(u.event, validated_payload, u.sessionId) return - if method: - validated_payload = self._validate_event_payload(method, dict(params)) - if validated_payload is None: - return - for handler in list(self._handlers.get(method, [])): - def run_method_event(handler=handler, payload=validated_payload, event_name=method): - self._run_handler(handler, payload, event_name) - threading.Thread(target=run_method_event, daemon=True).start() + if isinstance(method, str): + validated_payload = self.types.parseEventPayload(method, dict(params)) + session_id = msg.get("sessionId") + self._emit_event(method, validated_payload, session_id if isinstance(session_id, str) else None) + + def _emit_event(self, event_name: str, payload: object, session_id: str | None) -> None: + for handler in list(self._handlers.get(event_name, [])): + def run_method_event(handler=handler, payload=payload, session_id=session_id): + self._run_handler(handler, payload, session_id) + threading.Thread(target=run_method_event, daemon=True).start() + for handler in list(self._handlers.get("*", [])): + def run_wildcard_event(handler=handler, event_name=event_name, payload=payload, session_id=session_id): + self._run_handler(handler, event_name, payload, session_id) + threading.Thread(target=run_wildcard_event, daemon=True).start() + + +def _call_handler(handler: Handler, *args: object) -> object: + signature = inspect.signature(handler) + parameters = list(signature.parameters.values()) + if any(parameter.kind == inspect.Parameter.VAR_POSITIONAL for parameter in parameters): + return handler(*args) + positional_parameters = [ + parameter + for parameter in parameters + if parameter.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD) + ] + return handler(*args[: len(positional_parameters)]) + + +def _typed_event_handler(event_class: type[CDPEvent], handler: Handler) -> Handler: + def typed_handler(payload: object, session_id: str | None = None) -> object: + typed_payload = event_class.model_validate(payload) if isinstance(payload, Mapping) else payload + return _call_handler(handler, typed_payload, session_id) + + return typed_handler + + +def _protocol_params(value: object) -> ProtocolParams: + if not isinstance(value, Mapping): + return {} + return {str(key): raw_value for key, raw_value in value.items()} + + +def _mapping_dict(value: object) -> dict[str, object]: + if not isinstance(value, Mapping): + return {} + return {str(key): raw_value for key, raw_value in value.items()} diff --git a/python/modcdp/client/__init__.py b/python/modcdp/client/__init__.py index 64df04b3..e2092900 100644 --- a/python/modcdp/client/__init__.py +++ b/python/modcdp/client/__init__.py @@ -1 +1,5 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/client/ModCDPClient.ts +# - ./go/modcdp/client/ModCDPClient.go from .ModCDPClient import ModCDPClient diff --git a/python/modcdp/extension.zip b/python/modcdp/extension.zip index 8032e35e..e68ec9f4 100644 Binary files a/python/modcdp/extension.zip and b/python/modcdp/extension.zip differ diff --git a/python/modcdp/injector/BBBrowserExtensionInjector.py b/python/modcdp/injector/BBExtensionInjector.py similarity index 62% rename from python/modcdp/injector/BBBrowserExtensionInjector.py rename to python/modcdp/injector/BBExtensionInjector.py index 622a360b..db21ccd1 100644 --- a/python/modcdp/injector/BBBrowserExtensionInjector.py +++ b/python/modcdp/injector/BBExtensionInjector.py @@ -1,3 +1,7 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/BBExtensionInjector.ts +# - ./go/modcdp/injector/BBExtensionInjector.go from __future__ import annotations import json @@ -9,53 +13,51 @@ import zipfile from pathlib import Path -from ..launcher.BrowserLauncher import BrowserLaunchOptions -from ..injector.ExtensionInjector import DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, ExtensionInjector, ExtensionInjectionResult, defaultModCDPExtensionPath +from ..launcher.BrowserLauncher import LauncherConfig +from ..injector.ExtensionInjector import ExtensionInjector, ExtensionInjectionResult, InjectorConfig -DEFAULT_BROWSERBASE_BASE_URL = "https://api.browserbase.com" - - -class BBBrowserExtensionInjector(ExtensionInjector): - def __init__(self, options=None) -> None: - super().__init__(options) +class BBExtensionInjector(ExtensionInjector): + def __init__(self, config: InjectorConfig | dict | None = None) -> None: + config = config.model_dump() if isinstance(config, InjectorConfig) else dict(config or {}) + super().__init__({**config, "injector_mode": "bb"}) self.extension_id: str | None = None self.zip_path: str | None = None self.cleanup_dir: tempfile.TemporaryDirectory[str] | None = None def prepare(self) -> None: - configured_extension_id = _first_string(self.options.get("injector_extension_id")) + configured_extension_id = _first_string(self.config.injector_bb_extension_id) if configured_extension_id: self.extension_id = configured_extension_id return if self.extension_id: return - extension_path = self.options.get("injector_extension_path") or defaultModCDPExtensionPath() + extension_path = self.config.injector_bb_extension_path if not extension_path: return - self.options["injector_extension_path"] = extension_path + self.update({"injector_bb_extension_path": extension_path}) self.zip_path = extension_path if extension_path.endswith(".zip") else self._zipExtensionDir(extension_path) try: self.extension_id = self._uploadExtension(self.zip_path) + self.update({"injector_bb_extension_id": self.extension_id}) except Exception: self.close() raise - def getLauncherConfig(self) -> BrowserLaunchOptions: - if not self.extension_id: - return {} - return {"injector_extension_id": self.extension_id} + def configForLauncher(self) -> LauncherConfig | dict: + return { + **dict(super().configForLauncher()), + "launcher_bb_extension_id": self.extension_id or self.config.injector_bb_extension_id, + } def inject(self) -> ExtensionInjectionResult | None: - extension_id = self.options.get("injector_extension_id") - self.options["injector_extension_id"] = None - try: - discovered = self._waitForReadyServiceWorker( - self.options.get("injector_service_worker_ready_timeout_ms") or DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, - matched_only=bool(self.options.get("injector_trust_service_worker_target")), - ) - return {**discovered, "source": "bb"} if discovered else None - finally: - self.options["injector_extension_id"] = extension_id + discovered = self._waitForReadyServiceWorker( + self.config.injector_service_worker_ready_timeout_ms, + matched_only=self.config.injector_trust_service_worker_target, + ) + if discovered is None: + return None + discovered.source = "bb" + return discovered def close(self) -> None: if self.cleanup_dir: @@ -72,13 +74,10 @@ def _zipExtensionDir(self, extension_path: str) -> str: return zip_path def _uploadExtension(self, zip_path: str) -> str: - browserbase_api_key = _first_string(self.options.get("injector_browserbase_api_key"), os.environ.get("BROWSERBASE_API_KEY")) + browserbase_api_key = _first_string(self.config.injector_bb_api_key, os.environ.get("BROWSERBASE_API_KEY")) if not browserbase_api_key: - raise RuntimeError("BBBrowserExtensionInjector requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key.") - base_url = _first_string( - self.options.get("injector_browserbase_base_url"), - os.environ.get("BROWSERBASE_BASE_URL"), - ) or DEFAULT_BROWSERBASE_BASE_URL + raise RuntimeError("BBExtensionInjector requires BROWSERBASE_API_KEY or injector.injector_bb_api_key.") + base_url = self.config.injector_bb_base_url boundary = f"----modcdp-{uuid.uuid4().hex}" zip_bytes = Path(zip_path).read_bytes() body = ( diff --git a/python/modcdp/injector/BorrowedExtensionInjector.py b/python/modcdp/injector/BorrowedExtensionInjector.py deleted file mode 100644 index 326b6243..00000000 --- a/python/modcdp/injector/BorrowedExtensionInjector.py +++ /dev/null @@ -1,128 +0,0 @@ -from __future__ import annotations - -import time -from pathlib import Path -from typing import Any, Mapping, cast - -from ..injector.ExtensionInjector import DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, EXT_ID_FROM_URL_RE, ExtensionInjector, ExtensionInjectionResult, MODCDP_READY_EXPRESSION - - -class BorrowedExtensionInjector(ExtensionInjector): - def inject(self) -> ExtensionInjectionResult | None: - deadline = time.monotonic() + (self.options.get("injector_service_worker_ready_timeout_ms") or DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS) / 1000 - while True: - borrowed = self._borrowVisibleServiceWorkers() - if borrowed: - return borrowed - if time.monotonic() >= deadline: - return None - time.sleep((self.options.get("injector_service_worker_poll_interval_ms") or DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS) / 1000) - - def _borrowVisibleServiceWorkers(self) -> ExtensionInjectionResult | None: - borrowed: list[ExtensionInjectionResult] = [] - visible_service_workers = [ - target - for target in self._targetInfos() - if target.get("type") == "service_worker" and isinstance(target.get("url"), str) and target["url"].startswith("chrome-extension://") - ] - has_configured_matcher = bool( - self.options.get("injector_extension_id") - or self.options.get("injector_service_worker_url_includes") - or self.options.get("injector_service_worker_url_suffixes") - ) - candidates = [target for target in visible_service_workers if self._serviceWorkerTargetMatches(target)] if has_configured_matcher else visible_service_workers - for target in candidates: - try: - bootstrapped = self._bootstrapTarget(target) - except Exception: - bootstrapped = None - if bootstrapped: - borrowed.append({**bootstrapped, "source": "borrowed"}) - borrowed.sort(key=lambda item: (bool(item.get("has_debugger")), bool(item.get("has_tabs"))), reverse=True) - return borrowed[0] if borrowed else None - - def _bootstrapTarget(self, target) -> ExtensionInjectionResult | None: - session_id = self._ensureSessionIdForTarget( - target["targetId"], - self.options.get("injector_service_worker_probe_timeout_ms") or DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, - True, - ) - if session_id is None: - return None - try: - self._sendWithTimeout("Runtime.enable", {}, session_id) - except Exception: - pass - bootstrap = self._sendWithTimeout( - "Runtime.evaluate", - { - "expression": f"({bootstrap_modcdp_server_expression()})()", - "awaitPromise": True, - "returnByValue": True, - }, - session_id, - ) - result = cast(Mapping[str, Any], bootstrap.get("result")) if isinstance(bootstrap.get("result"), Mapping) else {} - raw_value = result.get("value") - value = cast(Mapping[str, Any], raw_value) if isinstance(raw_value, Mapping) else {} - if not bool(value.get("has_tabs")) or not bool(value.get("has_debugger")): - return None - ready = bool(value.get("ok")) - if ready and self._readyExpression() != MODCDP_READY_EXPRESSION: - probe = self._sendWithTimeout( - "Runtime.evaluate", - { - "expression": self._readyExpression(), - "returnByValue": True, - }, - session_id, - ) - probe_result = cast(Mapping[str, Any], probe.get("result")) if isinstance(probe.get("result"), Mapping) else {} - ready = bool(probe_result.get("value")) - if not ready: - return None - match = EXT_ID_FROM_URL_RE.match(target["url"]) - extension_id = value.get("extension_id") if isinstance(value.get("extension_id"), str) else None - return { - "source": "borrowed", - "extension_id": extension_id or (match.group(1) if match else None), - "target_id": target["targetId"], - "url": target["url"], - "session_id": session_id, - "has_tabs": bool(value.get("has_tabs")), - "has_debugger": bool(value.get("has_debugger")), - } - - -def bootstrap_modcdp_server_expression() -> str: - source = modcdp_server_source() - start = source.index("export function installModCDPServer") - end = source.index("export const ModCDPServer") - installer = source[start:end].replace("export function", "function", 1) - return ( - "function() {\n" - "const __name = (fn) => fn;\n" - f"{installer}\n" - "const ModCDP = installModCDPServer(globalThis);\n" - "return {\n" - " ok: Boolean(ModCDP?.__ModCDPServerVersion >= 1 && ModCDP?.handleCommand && ModCDP?.addCustomEvent),\n" - " extension_id: globalThis.chrome?.runtime?.id ?? null,\n" - " has_tabs: Boolean(globalThis.chrome?.tabs?.query),\n" - " has_debugger: Boolean(globalThis.chrome?.debugger?.sendCommand && globalThis.chrome?.debugger?.getTargets),\n" - "};\n" - "}" - ) - - -def modcdp_server_source() -> str: - candidates: list[Path] = [] - for parent in Path(__file__).resolve().parents: - candidates.append(parent / "dist" / "js" / "src" / "server" / "ModCDPServer.js") - candidates.append(parent / "dist" / "extension" / "js" / "src" / "server" / "ModCDPServer.js") - candidates.append(parent / "dist" / "extension" / "ModCDPServer.js") - candidates.append(parent / "ModCDPServer.js") - for candidate in candidates: - if candidate.exists(): - return candidate.read_text() - checked = ", ".join(str(candidate) for candidate in candidates) - raise FileNotFoundError(f"Unable to locate ModCDPServer.js; checked: {checked}") diff --git a/python/modcdp/injector/CDPExtensionInjector.py b/python/modcdp/injector/CDPExtensionInjector.py new file mode 100644 index 00000000..feef0b0e --- /dev/null +++ b/python/modcdp/injector/CDPExtensionInjector.py @@ -0,0 +1,72 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/CDPExtensionInjector.ts +# - ./go/modcdp/injector/CDPExtensionInjector.go +from __future__ import annotations + +import time +from collections.abc import Callable + +from ..injector.ExtensionInjector import ExtensionInjector, ExtensionInjectionResult, InjectorConfig +from ..injector.NodeExtensionFiles import defaultModCDPExtensionPath, prepareUnpackedExtension + + +class CDPExtensionInjector(ExtensionInjector): + def __init__(self, config: InjectorConfig | dict | None = None) -> None: + config = config.model_dump() if isinstance(config, InjectorConfig) else dict(config or {}) + super().__init__({**config, "injector_mode": "cdp"}) + self.unpacked_extension_path: str | None = None + self.cleanup: Callable[[], None] | None = None + + def prepare(self) -> None: + extension_path = self.config.injector_cdp_extension_path or defaultModCDPExtensionPath() + if not extension_path or self.unpacked_extension_path: + super().prepare() + return + prepared = prepareUnpackedExtension(extension_path) + self.unpacked_extension_path = prepared.unpacked_extension_path + self.cleanup = prepared.cleanup + super().prepare() + + def inject(self) -> ExtensionInjectionResult | None: + extension_path = self.unpacked_extension_path + if not extension_path: + return None + try: + load_result = self._sendWithTimeout("Extensions.loadUnpacked", {"path": extension_path}) + except RuntimeError as error: + if "Method not available" in str(error) or "wasn't found" in str(error) or "Method not found" in str(error): + return None + raise RuntimeError( + f"Extensions.loadUnpacked failed for {extension_path}: {error}\n" + "If the path is correct and the manifest is valid, load the ModCDP extension manually in chrome://extensions and reconnect." + ) from error + extension_id = load_result.get("id") + if not isinstance(extension_id, str) or not extension_id: + raise RuntimeError(f"Extensions.loadUnpacked returned no extension id (got {load_result})") + self.extension_id = extension_id + self.service_worker_extension_id = extension_id + + sw_url_prefix = f"chrome-extension://{extension_id}/" + deadline = time.monotonic() + self.config.injector_service_worker_ready_timeout_ms / 1000 + while time.monotonic() < deadline: + for target in self._targetInfos(): + if target.type != "service_worker" or not target.url.startswith(sw_url_prefix): + continue + probed = self._probeTarget( + target, + self.config.injector_service_worker_probe_timeout_ms, + allow_attach=True, + ) + if probed: + probed.source = "cdp" + probed.extension_id = extension_id + return probed + time.sleep(self.config.injector_service_worker_poll_interval_ms / 1000) + raise RuntimeError(f"Timed out waiting for service worker target for extension {extension_id}.") + + def close(self) -> None: + super().close() + if self.cleanup: + self.cleanup() + self.cleanup = None diff --git a/python/modcdp/injector/CLIExtensionInjector.py b/python/modcdp/injector/CLIExtensionInjector.py new file mode 100644 index 00000000..3113be43 --- /dev/null +++ b/python/modcdp/injector/CLIExtensionInjector.py @@ -0,0 +1,69 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/CLIExtensionInjector.ts +# - ./go/modcdp/injector/CLIExtensionInjector.go +from __future__ import annotations + +from collections.abc import Callable + +from ..injector.ExtensionInjector import ( + ExtensionInjector, + ExtensionInjectionResult, + InjectorConfig, +) +from ..injector.NodeExtensionFiles import ( + defaultModCDPExtensionPath, + extensionIdFromManifestKey, + prepareUnpackedExtension, +) + + +class CLIExtensionInjector(ExtensionInjector): + def __init__(self, config: InjectorConfig | dict | None = None) -> None: + config = config.model_dump() if isinstance(config, InjectorConfig) else dict(config or {}) + super().__init__({**config, "injector_mode": "cli"}) + self.unpacked_extension_path: str | None = None + self.extension_id: str | None = None + self.cleanup: Callable[[], None] | None = None + + def prepare(self) -> None: + extension_path = self.config.injector_cli_extension_path or defaultModCDPExtensionPath() + if not extension_path or self.unpacked_extension_path: + super().prepare() + return + prepared = prepareUnpackedExtension(extension_path) + self.unpacked_extension_path = prepared.unpacked_extension_path + self.cleanup = prepared.cleanup + self._resolveExtensionId() + super().prepare() + + def inject(self) -> ExtensionInjectionResult | None: + discovered = self._waitForReadyServiceWorker( + self.config.injector_service_worker_ready_timeout_ms, + matched_only=self.config.injector_trust_service_worker_target, + ) + if discovered is None: + return None + discovered.source = "cli" + return discovered + + def close(self) -> None: + super().close() + if self.cleanup: + self.cleanup() + self.cleanup = None + + def _resolveExtensionId(self) -> str | None: + if self.extension_id: + return self.extension_id + configured_extension_id = self.config.injector_cli_extension_id + if configured_extension_id: + self.extension_id = configured_extension_id + elif self.unpacked_extension_path: + self.extension_id = extensionIdFromManifestKey(self.unpacked_extension_path) + if self.extension_id: + self.service_worker_extension_id = self.extension_id + self.update({"injector_cli_extension_id": self.extension_id, "injector_service_worker_extension_id": self.extension_id}) + if self.unpacked_extension_path: + self.extra_args = [f"--load-extension={self.unpacked_extension_path}"] + return self.extension_id diff --git a/python/modcdp/injector/DiscoverExtensionInjector.py b/python/modcdp/injector/DiscoverExtensionInjector.py new file mode 100644 index 00000000..3bab6440 --- /dev/null +++ b/python/modcdp/injector/DiscoverExtensionInjector.py @@ -0,0 +1,69 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/DiscoverExtensionInjector.ts +# - ./go/modcdp/injector/DiscoverExtensionInjector.go +from __future__ import annotations + +from ..injector.ExtensionInjector import ( + ExtensionInjector, + ExtensionInjectionResult, + InjectorConfig, +) +from ..injector.NodeExtensionFiles import ( + PreparedExtension, + extensionIdFromManifestKey, + prepareUnpackedExtension, +) + + +class DiscoverExtensionInjector(ExtensionInjector): + def __init__(self, config: InjectorConfig | dict | None = None) -> None: + config = config.model_dump() if isinstance(config, InjectorConfig) else dict(config or {}) + super().__init__({**config, "injector_mode": "discover"}) + self.prepared_extension: PreparedExtension | None = None + + def prepare(self) -> None: + extension_path = self.config.injector_discover_extension_path + if not self.config.injector_service_worker_extension_id and extension_path: + manifest_path = extension_path + if extension_path.endswith(".zip"): + self.prepared_extension = prepareUnpackedExtension(extension_path) + manifest_path = self.prepared_extension.unpacked_extension_path + self.service_worker_extension_id = extensionIdFromManifestKey(manifest_path) + super().prepare() + + def inject(self) -> ExtensionInjectionResult | None: + discovered = self._discoverReadyServiceWorker() + if discovered: + discovered.source = "discover" + return discovered + if self.config.injector_trust_service_worker_target: + waited = self._waitForReadyServiceWorker( + self.config.injector_service_worker_probe_timeout_ms, + matched_only=True, + ) + if waited: + waited.source = "discover" + return waited + if not self.config.injector_require_service_worker_target: + return None + waited = self._waitForReadyServiceWorker( + self.config.injector_service_worker_ready_timeout_ms, + matched_only=self.config.injector_trust_service_worker_target, + ) + if waited: + waited.source = "discover" + return waited + matchers = ", ".join( + [ + *self.config.injector_service_worker_url_includes, + *self.config.injector_service_worker_url_suffixes, + ] + ) + raise RuntimeError(f"Required ModCDP service worker target was not visible ({matchers or 'no matcher'}).") + + def close(self) -> None: + super().close() + if self.prepared_extension: + self.prepared_extension.cleanup() + self.prepared_extension = None diff --git a/python/modcdp/injector/DiscoveredExtensionInjector.py b/python/modcdp/injector/DiscoveredExtensionInjector.py deleted file mode 100644 index 7a550f43..00000000 --- a/python/modcdp/injector/DiscoveredExtensionInjector.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from ..injector.ExtensionInjector import DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, ExtensionInjector, ExtensionInjectionResult - - -def _timeout(value: Any, fallback: int) -> int: - return fallback if value is None else int(value) - - -class DiscoveredExtensionInjector(ExtensionInjector): - def inject(self) -> ExtensionInjectionResult | None: - discovered = self._discoverReadyServiceWorker() - if discovered: - return {**discovered, "source": "discovered"} - if self.options.get("injector_trust_service_worker_target"): - waited = self._waitForReadyServiceWorker( - _timeout(self.options.get("injector_service_worker_probe_timeout_ms"), DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS), - matched_only=True, - ) - if waited: - return {**waited, "source": "discovered"} - if not self.options.get("injector_require_service_worker_target"): - return None - waited = self._waitForReadyServiceWorker( - _timeout(self.options.get("injector_service_worker_ready_timeout_ms"), DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS), - matched_only=bool(self.options.get("injector_trust_service_worker_target")), - ) - if waited: - return {**waited, "source": "discovered"} - matchers = ", ".join( - [ - *(self.options.get("injector_service_worker_url_includes") or []), - *(self.options.get("injector_service_worker_url_suffixes") or []), - ] - ) - raise RuntimeError(f"Required ModCDP service worker target was not visible ({matchers or 'no matcher'}).") diff --git a/python/modcdp/injector/ExtensionInjector.py b/python/modcdp/injector/ExtensionInjector.py index 510abb3f..d4a0fa29 100644 --- a/python/modcdp/injector/ExtensionInjector.py +++ b/python/modcdp/injector/ExtensionInjector.py @@ -1,29 +1,26 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/ExtensionInjector.ts +# - ./go/modcdp/injector/ExtensionInjector.go from __future__ import annotations -import base64 -import hashlib -import json import re -import shutil -import tempfile import threading import time -import zipfile from collections.abc import Callable, Mapping -from pathlib import Path from queue import Empty, Queue -from typing import Any, TypedDict, cast +from typing import Any, Literal, TypeAlias -from typing_extensions import NotRequired - -from ..launcher.BrowserLauncher import BrowserLaunchOptions -from ..types.modcdp import ProtocolParams, ProtocolResult, TargetInfo +from pydantic import BaseModel, ConfigDict, Field +from ..launcher.BrowserLauncher import LauncherConfig +from ..types.modcdp import ExtensionInfo, ProtocolParams, ProtocolResult, TargetInfo, _isObjectMap +from ..types.toJSON import modCDPToJSON EXT_ID_FROM_URL_RE = re.compile(r"^chrome-extension://([a-z]+)/") DEFAULT_MODCDP_EXTENSION_ID = "mdedooklbnfejodmnhmkdpkaedafkehf" DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES = ["/modcdp/service_worker.js"] MODCDP_READY_EXPRESSION = ( - "Boolean(globalThis.ModCDP?.__ModCDPServerVersion >= 1 && globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)" + "Boolean(globalThis.ModCDP?.handleCommand && globalThis.ModCDP?.addCustomEvent)" ) DEFAULT_CDP_SEND_TIMEOUT_MS = 10_000 DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000 @@ -33,158 +30,69 @@ DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS = 20 SendCDP = Callable[[str, ProtocolParams | None, str | None], ProtocolResult] -SessionIdForTarget = Callable[[str], str | None] -AttachToTarget = Callable[[str], str | None] -WaitForExecutionContext = Callable[[str, int], int] - - -class ExtensionInjectorConfig(TypedDict, total=False): - send: SendCDP | None - sessionIdForTarget: SessionIdForTarget | None - attachToTarget: AttachToTarget | None - waitForExecutionContext: WaitForExecutionContext | None - injector_extension_path: str | None - injector_extension_id: str | None - injector_service_worker_url_includes: list[str] - injector_service_worker_url_suffixes: list[str] - injector_trust_service_worker_target: bool - injector_require_service_worker_target: bool - injector_service_worker_ready_expression: str | None - injector_cdp_send_timeout_ms: int - injector_execution_context_timeout_ms: int - injector_service_worker_probe_timeout_ms: int - injector_service_worker_ready_timeout_ms: int - injector_service_worker_poll_interval_ms: int - injector_target_session_poll_interval_ms: int - injector_browserbase_api_key: str | None - injector_browserbase_base_url: str | None - upstream_nativemessaging_host_name: str | None - upstream_nats_url: str | None - upstream_nats_subject_prefix: str | None - - -def defaultModCDPExtensionPath() -> str | None: - bundled_extension = Path(__file__).resolve().parent.parent / "extension.zip" - return str(bundled_extension) if bundled_extension.exists() else None - - -def prepareUnpackedExtension(extension_path: str) -> tuple[str, tempfile.TemporaryDirectory[str]]: - cleanup_dir = tempfile.TemporaryDirectory(prefix="modcdp-extension-") - try: - if extension_path.endswith(".zip"): - with zipfile.ZipFile(extension_path) as archive: - _extract_zip(archive, cleanup_dir.name) - else: - shutil.copytree(extension_path, cleanup_dir.name, dirs_exist_ok=True) - return _extension_root(cleanup_dir.name), cleanup_dir - except BaseException: - cleanup_dir.cleanup() - raise - - -def extensionIdFromManifestKey(extension_path: str) -> str | None: - manifest_path = Path(extension_path) / "manifest.json" - if not manifest_path.exists(): - return None - manifest = json.loads(manifest_path.read_text()) - key = manifest.get("key") if isinstance(manifest, dict) else None - if not isinstance(key, str) or not key.strip(): - return None - digest = hashlib.sha256(base64.b64decode(key)).digest()[:16] - alphabet = "abcdefghijklmnop" - return "".join(alphabet[byte >> 4] + alphabet[byte & 0x0F] for byte in digest) - - -def _extension_root(unpacked_path: str) -> str: - if (Path(unpacked_path) / "manifest.json").exists(): - return unpacked_path - nested = Path(unpacked_path) / "extension" - if (nested / "manifest.json").exists(): - return str(nested) - return unpacked_path - -def _extract_zip(archive: zipfile.ZipFile, destination: str) -> None: - root = Path(destination).resolve() - for member in archive.infolist(): - target = (root / member.filename).resolve() - if target != root and root not in target.parents: - raise RuntimeError(f'zip entry "{member.filename}" escapes extension extraction directory') - archive.extractall(destination) - -def _defaulted(value: Any, fallback: int) -> int: - return fallback if value is None else int(value) - - -class ExtensionInjectionResult(TypedDict): - source: str - extension_id: str | None - target_id: str - url: str - session_id: str - has_tabs: NotRequired[bool] - has_debugger: NotRequired[bool] +class InjectorConfig(BaseModel): + model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) + + injector_mode: Literal["cli", "cdp", "bb", "discover", "none"] = "none" + send: SendCDP | None = None + injector_cli_extension_path: str | None = None + injector_cli_extension_id: str | None = None + injector_cdp_extension_path: str | None = None + injector_cdp_extension_id: str | None = None + injector_bb_extension_path: str | None = None + injector_bb_extension_id: str | None = None + injector_discover_extension_path: str | None = None + injector_service_worker_extension_id: str | None = None + injector_service_worker_url_includes: list[str] = Field(default_factory=list) + injector_service_worker_url_suffixes: list[str] = Field(default_factory=lambda: [*DEFAULT_MODCDP_SERVICE_WORKER_URL_SUFFIXES]) + injector_trust_service_worker_target: bool = False + injector_require_service_worker_target: bool = False + injector_service_worker_ready_expression: str = MODCDP_READY_EXPRESSION + injector_cdp_send_timeout_ms: int = Field(default=DEFAULT_CDP_SEND_TIMEOUT_MS, gt=0) + injector_execution_context_timeout_ms: int = Field(default=DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS, gt=0) + injector_service_worker_probe_timeout_ms: int = Field(default=DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, gt=0) + injector_service_worker_ready_timeout_ms: int = Field(default=DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, gt=0) + injector_service_worker_poll_interval_ms: int = Field(default=DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, gt=0) + injector_target_session_poll_interval_ms: int = Field(default=DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS, gt=0) + injector_bb_api_key: str | None = None + injector_bb_base_url: str = "https://api.browserbase.com" + + +ExtensionInjectionResult: TypeAlias = ExtensionInfo class ExtensionInjector: - def __init__(self, options: ExtensionInjectorConfig | None = None) -> None: - self.options = cast(ExtensionInjectorConfig, { - "send": None, - "sessionIdForTarget": None, - "attachToTarget": None, - "waitForExecutionContext": None, - "injector_extension_path": None, - "injector_extension_id": None, - "injector_service_worker_url_includes": [], - "injector_service_worker_url_suffixes": [], - "injector_trust_service_worker_target": False, - "injector_require_service_worker_target": False, - "injector_service_worker_ready_expression": None, - "injector_cdp_send_timeout_ms": DEFAULT_CDP_SEND_TIMEOUT_MS, - "injector_execution_context_timeout_ms": DEFAULT_EXECUTION_CONTEXT_TIMEOUT_MS, - "injector_service_worker_probe_timeout_ms": DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, - "injector_service_worker_ready_timeout_ms": DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, - "injector_service_worker_poll_interval_ms": DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, - "injector_target_session_poll_interval_ms": DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS, - "injector_browserbase_api_key": None, - "injector_browserbase_base_url": None, - "upstream_nativemessaging_host_name": None, - "upstream_nats_url": None, - "upstream_nats_subject_prefix": None, - **dict(options or {}), - }) + def __init__(self, config: InjectorConfig | dict[str, Any] | None = None) -> None: + self.config = _injector_config(config) self.unusable_target_ids: set[str] = set() - self.last_error: Exception | None = None - - def update(self, config: ExtensionInjectorConfig | None = None) -> "ExtensionInjector": - config = cast(ExtensionInjectorConfig, dict(config or {})) - self.options = cast( - ExtensionInjectorConfig, - { - **self.options, - **config, - "injector_service_worker_url_includes": config.get( - "injector_service_worker_url_includes", - self.options.get("injector_service_worker_url_includes") or [], - ), - "injector_service_worker_url_suffixes": config.get( - "injector_service_worker_url_suffixes", - self.options.get("injector_service_worker_url_suffixes") or [], - ), - }, - ) + self.source: str | None = None + self.extension_id: str | None = None + self.service_worker_extension_id: str | None = None + self.target_id: str | None = None + self.url: str | None = None + self.session_id: str | None = None + self.extra_args: list[str] = [] + + def update(self, config: InjectorConfig | dict[str, Any] | None = None) -> "ExtensionInjector": + incoming = _injector_config(config) + self.config = InjectorConfig.model_validate({**self.config.model_dump(), **incoming.model_dump(exclude_unset=True)}) return self - def getInjectorConfig(self) -> ExtensionInjectorConfig: - return cast(ExtensionInjectorConfig, dict(self.options)) + def configForLauncher(self) -> LauncherConfig | dict[str, Any]: + return { + "launcher_local_extra_args": self.extra_args, + "launcher_bb_extension_id": self.config.injector_bb_extension_id, + } - def getLauncherConfig(self) -> BrowserLaunchOptions: + def configForUpstream(self) -> dict[str, Any]: return {} - def getTransportConfig(self) -> dict[str, Any]: - extension_id = self.options.get("injector_extension_id") - return {"injector_extension_id": extension_id} if extension_id else {} + def toJSON(self) -> dict[str, object]: + config = self.config.model_dump() + config.pop("send", None) + return modCDPToJSON(self, {"config": config}) def prepare(self) -> None: return None @@ -195,9 +103,19 @@ def close(self) -> None: def inject(self) -> ExtensionInjectionResult | None: raise NotImplementedError(f"{type(self).__name__}.inject is not implemented.") + def recordInjectionResult(self, result: ExtensionInjectionResult) -> "ExtensionInjector": + self.source = result.source + self.extension_id = result.extension_id + if result.extension_id is not None: + self.service_worker_extension_id = result.extension_id + self.target_id = result.target_id + self.url = result.url + self.session_id = result.session_id + return self + def _readyExpression(self) -> str: - expression = self.options.get("injector_service_worker_ready_expression") - if not expression: + expression = self.config.injector_service_worker_ready_expression + if expression == MODCDP_READY_EXPRESSION: return MODCDP_READY_EXPRESSION return f"({MODCDP_READY_EXPRESSION}) && Boolean({expression})" @@ -208,13 +126,10 @@ def _sendWithTimeout( session_id: str | None = None, timeout_ms: int | None = None, ) -> ProtocolResult: - send = self.options.get("send") + send = self.config.send if send is None: raise RuntimeError(f"{type(self).__name__} requires a CDP send function.") - effective_timeout_ms = _defaulted( - timeout_ms if timeout_ms is not None else self.options.get("injector_cdp_send_timeout_ms"), - DEFAULT_CDP_SEND_TIMEOUT_MS, - ) + effective_timeout_ms = timeout_ms if timeout_ms is not None else self.config.injector_cdp_send_timeout_ms if effective_timeout_ms <= 0: return send(method, params or {}, session_id) @@ -235,32 +150,6 @@ def runSend() -> None: raise error return result or {} - def _sessionIdForTarget(self, target_id: str, timeout_ms: int = 0) -> str | None: - deadline = time.monotonic() + timeout_ms / 1000 - while True: - session_id = self.options.get("sessionIdForTarget") - if session_id is not None: - value = session_id(target_id) - if value: - return value - if time.monotonic() >= deadline: - return None - time.sleep(_defaulted(self.options.get("injector_target_session_poll_interval_ms"), DEFAULT_TARGET_SESSION_POLL_INTERVAL_MS) / 1000) - - def _ensureSessionIdForTarget(self, target_id: str, timeout_ms: int = 0, allow_attach: bool = False) -> str | None: - session_id = self.options.get("sessionIdForTarget") - if session_id is not None: - value = session_id(target_id) - if value: - return value - if allow_attach: - attach_to_target = self.options.get("attachToTarget") - if attach_to_target is not None: - attached_session_id = attach_to_target(target_id) - if attached_session_id: - return attached_session_id - return self._sessionIdForTarget(target_id, timeout_ms) - def _targetInfos(self) -> list[TargetInfo]: result = self._sendWithTimeout("Target.getTargets") raw_targets = result.get("targetInfos") @@ -268,13 +157,13 @@ def _targetInfos(self) -> list[TargetInfo]: return [] targets: list[TargetInfo] = [] for raw_target in raw_targets: - if not isinstance(raw_target, Mapping): + if not _isObjectMap(raw_target): continue target_id = raw_target.get("targetId") target_type = raw_target.get("type") target_url = raw_target.get("url") if isinstance(target_id, str) and isinstance(target_type, str) and isinstance(target_url, str): - targets.append({"targetId": target_id, "type": target_type, "url": target_url}) + targets.append(TargetInfo(targetId=target_id, type=target_type, url=target_url)) return targets def _probeTarget( @@ -284,58 +173,71 @@ def _probeTarget( *, allow_attach: bool = False, ) -> ExtensionInjectionResult | None: - target_id = target["targetId"] + target_id = target.targetId if target_id in self.unusable_target_ids: return None - session_id = self._ensureSessionIdForTarget(target_id, session_timeout_ms, allow_attach) - if session_id is None: - return None - self._sendWithTimeout("Runtime.enable", {}, session_id) - probe = self._sendWithTimeout( - "Runtime.evaluate", - { - "expression": self._readyExpression(), - "returnByValue": True, - }, - session_id, + attached = self._sendWithTimeout( + "Target.attachToTarget", + {"targetId": target_id, "flatten": True}, + None, + session_timeout_ms, ) - result = cast(Mapping[str, Any], probe.get("result")) if isinstance(probe.get("result"), Mapping) else {} - value = result.get("value") - if value is not True: - return None - match = EXT_ID_FROM_URL_RE.match(target.get("url") or "") - return { - "source": "discovered", - "extension_id": match.group(1) if match else None, - "target_id": target_id, - "url": target["url"], - "session_id": session_id, - } + session_id = attached.get("sessionId") + if not isinstance(session_id, str) or not session_id: + raise RuntimeError(f"Target.attachToTarget returned no sessionId for targetId={target_id}") + try: + self._sendWithTimeout("Runtime.enable", {}, session_id) + probe = self._sendWithTimeout( + "Runtime.evaluate", + { + "expression": self._readyExpression(), + "returnByValue": True, + }, + session_id, + ) + raw_result = probe.get("result") + result = raw_result if _isObjectMap(raw_result) else {} + value = result.get("value") + if value is not True: + self._sendWithTimeout("Target.detachFromTarget", {"sessionId": session_id}) + return None + match = EXT_ID_FROM_URL_RE.match(target.url) + return ExtensionInfo( + source="discover", + extension_id=match.group(1) if match else None, + target_id=target.targetId, + url=target.url, + session_id=session_id, + ) + except BaseException: + self._sendWithTimeout("Target.detachFromTarget", {"sessionId": session_id}) + raise def _discoverReadyServiceWorker(self, *, matched_only: bool = False) -> ExtensionInjectionResult | None: target_infos = self._targetInfos() - if self.options.get("injector_trust_service_worker_target"): + if self.config.injector_trust_service_worker_target: for candidate in target_infos: if not self._serviceWorkerTargetMatches(candidate): continue probed = self._probeTarget( candidate, - _defaulted(self.options.get("injector_service_worker_probe_timeout_ms"), DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS), + self.config.injector_service_worker_probe_timeout_ms, allow_attach=True, ) if probed: - return {**probed, "source": "trusted"} - if self.options.get("injector_trust_service_worker_target") or matched_only: + probed.source = "trusted" + return probed + if self.config.injector_trust_service_worker_target or matched_only: return None for candidate in target_infos: - if candidate["type"] != "service_worker": + if candidate.type != "service_worker": continue - if not candidate["url"].startswith("chrome-extension://"): + if not candidate.url.startswith("chrome-extension://"): continue try: probed = self._probeTarget( candidate, - _defaulted(self.options.get("injector_service_worker_probe_timeout_ms"), DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS), + self.config.injector_service_worker_probe_timeout_ms, ) except Exception: continue @@ -349,24 +251,33 @@ def _waitForReadyServiceWorker(self, timeout_ms: int, *, matched_only: bool = Fa discovered = self._discoverReadyServiceWorker(matched_only=matched_only) if discovered: return discovered - time.sleep(_defaulted(self.options.get("injector_service_worker_poll_interval_ms"), DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS) / 1000) + time.sleep(self.config.injector_service_worker_poll_interval_ms / 1000) return None - def _serviceWorkerTargetMatches(self, candidate: Mapping[str, object]) -> bool: - raw_target_url = candidate.get("url") + def _serviceWorkerTargetMatches(self, candidate: TargetInfo | Mapping[str, object]) -> bool: + raw_target_url = candidate.url if isinstance(candidate, TargetInfo) else candidate.get("url") target_url = raw_target_url if isinstance(raw_target_url, str) else "" - if candidate.get("type") != "service_worker": + candidate_type = candidate.type if isinstance(candidate, TargetInfo) else candidate.get("type") + if candidate_type != "service_worker": return False if not target_url.startswith("chrome-extension://"): return False - extension_id = self.options.get("injector_extension_id") + extension_id = self.config.injector_service_worker_extension_id + if extension_id is None: + extension_id = self.service_worker_extension_id has_extension_id = bool(extension_id) if extension_id and not target_url.startswith(f"chrome-extension://{extension_id}/"): return False - includes = self.options.get("injector_service_worker_url_includes") or [] - suffixes = self.options.get("injector_service_worker_url_suffixes") or [] + includes = self.config.injector_service_worker_url_includes + suffixes = self.config.injector_service_worker_url_suffixes if includes and not all(part in target_url for part in includes): return False if suffixes and not any(target_url.endswith(suffix) for suffix in suffixes): return False return bool(has_extension_id or includes or suffixes) + + +def _injector_config(config: InjectorConfig | dict[str, Any] | None = None) -> InjectorConfig: + if isinstance(config, InjectorConfig): + return config + return InjectorConfig.model_validate(config or {}) diff --git a/python/modcdp/injector/ExtensionsLoadUnpackedInjector.py b/python/modcdp/injector/ExtensionsLoadUnpackedInjector.py deleted file mode 100644 index 9e4d7d3f..00000000 --- a/python/modcdp/injector/ExtensionsLoadUnpackedInjector.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -import tempfile -import time - -from ..injector.ExtensionInjector import DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS, DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS, ExtensionInjector, ExtensionInjectionResult, defaultModCDPExtensionPath, prepareUnpackedExtension - - -class ExtensionsLoadUnpackedInjector(ExtensionInjector): - def __init__(self, options=None) -> None: - super().__init__(options) - self.unpacked_extension_path: str | None = None - self.cleanup_dir: tempfile.TemporaryDirectory[str] | None = None - - def prepare(self) -> None: - extension_path = self.options.get("injector_extension_path") or defaultModCDPExtensionPath() - if not extension_path or self.unpacked_extension_path: - super().prepare() - return - self.options["injector_extension_path"] = extension_path - self.unpacked_extension_path, self.cleanup_dir = prepareUnpackedExtension(extension_path) - super().prepare() - - def inject(self) -> ExtensionInjectionResult | None: - extension_path = self.unpacked_extension_path - if not extension_path: - return None - try: - load_result = self._sendWithTimeout("Extensions.loadUnpacked", {"path": extension_path}) - except RuntimeError as error: - if "Method not available" in str(error) or "wasn't found" in str(error) or "Method not found" in str(error): - self.last_error = error - return None - raise RuntimeError( - f"Extensions.loadUnpacked failed for {extension_path}: {error}\n" - "If the path is correct and the manifest is valid, load the ModCDP extension manually in chrome://extensions and reconnect." - ) from error - extension_id = load_result.get("id") or load_result.get("extensionId") - if not isinstance(extension_id, str) or not extension_id: - raise RuntimeError(f"Extensions.loadUnpacked returned no extension id (got {load_result})") - self.options["injector_extension_id"] = extension_id - - sw_url_prefix = f"chrome-extension://{extension_id}/" - deadline = time.monotonic() + (self.options.get("injector_service_worker_ready_timeout_ms") or DEFAULT_SERVICE_WORKER_READY_TIMEOUT_MS) / 1000 - while time.monotonic() < deadline: - for target in self._targetInfos(): - if target["type"] != "service_worker" or not target["url"].startswith(sw_url_prefix): - continue - probed = self._probeTarget( - target, - self.options.get("injector_service_worker_probe_timeout_ms") or DEFAULT_SERVICE_WORKER_PROBE_TIMEOUT_MS, - allow_attach=True, - ) - if probed: - return {**probed, "source": "extensions_load_unpacked", "extension_id": extension_id} - time.sleep((self.options.get("injector_service_worker_poll_interval_ms") or DEFAULT_SERVICE_WORKER_POLL_INTERVAL_MS) / 1000) - raise RuntimeError(f"Timed out waiting for service worker target for extension {extension_id}.") - - def close(self) -> None: - super().close() - if self.cleanup_dir: - self.cleanup_dir.cleanup() - self.cleanup_dir = None diff --git a/python/modcdp/injector/LocalBrowserLaunchExtensionInjector.py b/python/modcdp/injector/LocalBrowserLaunchExtensionInjector.py deleted file mode 100644 index 79e51dc3..00000000 --- a/python/modcdp/injector/LocalBrowserLaunchExtensionInjector.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import tempfile - -from ..launcher.BrowserLauncher import BrowserLaunchOptions -from ..injector.ExtensionInjector import ( - ExtensionInjector, - ExtensionInjectionResult, - defaultModCDPExtensionPath, - extensionIdFromManifestKey, - prepareUnpackedExtension, -) - - -class LocalBrowserLaunchExtensionInjector(ExtensionInjector): - def __init__(self, options=None) -> None: - super().__init__(options) - self.unpacked_extension_path: str | None = None - self.extension_id: str | None = None - self.cleanup_dir: tempfile.TemporaryDirectory[str] | None = None - - def prepare(self) -> None: - extension_path = self.options.get("injector_extension_path") or defaultModCDPExtensionPath() - if not extension_path or self.unpacked_extension_path: - super().prepare() - return - self.options["injector_extension_path"] = extension_path - self.unpacked_extension_path, self.cleanup_dir = prepareUnpackedExtension(extension_path) - self._resolveExtensionId() - super().prepare() - - def getLauncherConfig(self) -> BrowserLaunchOptions: - if not self.unpacked_extension_path: - return {} - return {"extra_args": [f"--load-extension={self.unpacked_extension_path}"]} - - def inject(self) -> ExtensionInjectionResult | None: - discovered = self._discoverReadyServiceWorker( - matched_only=bool(self.options.get("injector_trust_service_worker_target")), - ) - return {**discovered, "source": "local_launch"} if discovered else None - - def close(self) -> None: - super().close() - if self.cleanup_dir: - self.cleanup_dir.cleanup() - self.cleanup_dir = None - - def _resolveExtensionId(self) -> str | None: - if self.extension_id: - return self.extension_id - configured_extension_id = self.options.get("injector_extension_id") - if configured_extension_id: - self.extension_id = configured_extension_id - elif self.unpacked_extension_path: - self.extension_id = extensionIdFromManifestKey(self.unpacked_extension_path) - if self.extension_id: - self.options["injector_extension_id"] = self.extension_id - return self.extension_id diff --git a/python/modcdp/injector/NodeExtensionFiles.py b/python/modcdp/injector/NodeExtensionFiles.py new file mode 100644 index 00000000..569f9d86 --- /dev/null +++ b/python/modcdp/injector/NodeExtensionFiles.py @@ -0,0 +1,80 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/NodeExtensionFiles.ts +# - ./go/modcdp/injector/NodeExtensionFiles.go +from __future__ import annotations + +import base64 +import hashlib +import json +import shutil +import tempfile +import zipfile +from collections.abc import Callable +from dataclasses import dataclass +from pathlib import Path + + +@dataclass +class PreparedExtension: + unpacked_extension_path: str + cleanup: Callable[[], None] + + +def defaultModCDPExtensionPath() -> str: + return str(Path(__file__).resolve().parent.parent / "extension.zip") + + +def prepareUnpackedExtension(extension_path: str) -> PreparedExtension: + cleanup_dir = tempfile.TemporaryDirectory(prefix="modcdp-extension-") + try: + if extension_path.endswith(".zip"): + with zipfile.ZipFile(extension_path) as archive: + _extract_zip(archive, cleanup_dir.name) + else: + shutil.copytree(extension_path, cleanup_dir.name, dirs_exist_ok=True) + return PreparedExtension( + unpacked_extension_path=_extension_root(cleanup_dir.name), + cleanup=cleanup_dir.cleanup, + ) + except BaseException: + cleanup_dir.cleanup() + raise + + +def extensionIdFromManifestKey(extension_path: str) -> str | None: + manifest_path = Path(extension_path) / "manifest.json" + if not manifest_path.exists(): + return None + manifest = json.loads(manifest_path.read_text()) + key = _first_string(manifest.get("key") if isinstance(manifest, dict) else None) + if not key: + return None + digest = hashlib.sha256(base64.b64decode(key)).digest()[:16] + alphabet = "abcdefghijklmnop" + return "".join(alphabet[byte >> 4] + alphabet[byte & 0x0F] for byte in digest) + + +def _first_string(*values: object) -> str | None: + for value in values: + if isinstance(value, str) and value.strip(): + return value.strip() + return None + + +def _extension_root(unpacked_path: str) -> str: + if (Path(unpacked_path) / "manifest.json").exists(): + return unpacked_path + nested_path = Path(unpacked_path) / "extension" + if (nested_path / "manifest.json").exists(): + return str(nested_path) + return unpacked_path + + +def _extract_zip(archive: zipfile.ZipFile, destination: str) -> None: + root = Path(destination).resolve() + for member in archive.infolist(): + target = (root / member.filename).resolve() + if target != root and root not in target.parents: + raise RuntimeError(f'zip entry "{member.filename}" escapes extension extraction directory') + archive.extractall(destination) diff --git a/python/modcdp/injector/__init__.py b/python/modcdp/injector/__init__.py index 851b24b4..f1325f5f 100644 --- a/python/modcdp/injector/__init__.py +++ b/python/modcdp/injector/__init__.py @@ -1,6 +1,18 @@ -from .BBBrowserExtensionInjector import BBBrowserExtensionInjector -from .BorrowedExtensionInjector import BorrowedExtensionInjector -from .DiscoveredExtensionInjector import DiscoveredExtensionInjector -from .ExtensionInjector import ExtensionInjector, defaultModCDPExtensionPath -from .ExtensionsLoadUnpackedInjector import ExtensionsLoadUnpackedInjector -from .LocalBrowserLaunchExtensionInjector import LocalBrowserLaunchExtensionInjector +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/injector/ExtensionInjector.ts +# - ./js/src/injector/BBExtensionInjector.ts +# - ./js/src/injector/CDPExtensionInjector.ts +# - ./js/src/injector/CLIExtensionInjector.ts +# - ./js/src/injector/DiscoverExtensionInjector.ts +# - ./go/modcdp/injector/ExtensionInjector.go +# - ./go/modcdp/injector/BBExtensionInjector.go +# - ./go/modcdp/injector/CDPExtensionInjector.go +# - ./go/modcdp/injector/CLIExtensionInjector.go +# - ./go/modcdp/injector/DiscoverExtensionInjector.go +from .BBExtensionInjector import BBExtensionInjector +from .DiscoverExtensionInjector import DiscoverExtensionInjector +from .ExtensionInjector import ExtensionInjector +from .NodeExtensionFiles import PreparedExtension, defaultModCDPExtensionPath, extensionIdFromManifestKey, prepareUnpackedExtension +from .CDPExtensionInjector import CDPExtensionInjector +from .CLIExtensionInjector import CLIExtensionInjector diff --git a/python/modcdp/launcher/BrowserbaseBrowserLauncher.py b/python/modcdp/launcher/BBBrowserLauncher.py similarity index 68% rename from python/modcdp/launcher/BrowserbaseBrowserLauncher.py rename to python/modcdp/launcher/BBBrowserLauncher.py index d9549efc..0069a613 100644 --- a/python/modcdp/launcher/BrowserbaseBrowserLauncher.py +++ b/python/modcdp/launcher/BBBrowserLauncher.py @@ -1,3 +1,7 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/BBBrowserLauncher.ts +# - ./go/modcdp/launcher/BBBrowserLauncher.go from __future__ import annotations import json @@ -7,27 +11,27 @@ from typing import Any from websocket import create_connection -from ..launcher.BrowserLauncher import BrowserLaunchOptions, BrowserLauncher, LaunchedBrowser +from ..launcher.BrowserLauncher import LauncherConfig, BrowserLauncher, LaunchedBrowser, _launcher_config -DEFAULT_BROWSERBASE_BASE_URL = "https://api.browserbase.com" DEFAULT_BROWSERBASE_VIEWPORT = {"width": 1288, "height": 711} -class BrowserbaseBrowserLauncher(BrowserLauncher): - def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser: - merged = {**self.options, **dict(options or {})} - browserbase_api_key = _first_string(merged.get("browserbase_api_key"), os.environ.get("BROWSERBASE_API_KEY")) +class BBBrowserLauncher(BrowserLauncher): + def __init__(self, config: LauncherConfig | dict | None = None) -> None: + raw_config = config.model_dump() if isinstance(config, LauncherConfig) else dict(config or {}) + super().__init__({**raw_config, "launcher_mode": "bb"}) + + def launch(self, config: LauncherConfig | dict | None = None) -> LaunchedBrowser: + merged = self.config if config is None else _launcher_config({**self.config.model_dump(), **_launcher_config(config).model_dump(exclude_unset=True)}) + browserbase_api_key = _first_string(merged.launcher_bb_api_key, os.environ.get("BROWSERBASE_API_KEY")) if not browserbase_api_key: - raise RuntimeError("launcher.launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_options.browserbase_api_key.") - - base_url = _first_string( - merged.get("browserbase_base_url"), - os.environ.get("BROWSERBASE_BASE_URL"), - ) or DEFAULT_BROWSERBASE_BASE_URL - resume_session_id = _first_string(merged.get("browserbase_session_id")) - keep_alive = _first_bool(merged.get("browserbase_keep_alive")) or False - close_session_on_close = _first_bool(merged.get("browserbase_close_session_on_close")) + raise RuntimeError("launcher_mode=bb requires BROWSERBASE_API_KEY or launcher.launcher_bb_api_key.") + + base_url = merged.launcher_bb_base_url + resume_session_id = _first_string(merged.launcher_bb_session_id) + keep_alive = _first_bool(merged.launcher_bb_keep_alive) or False + close_session_on_close = _first_bool(merged.launcher_bb_close_session_on_close) if close_session_on_close is None: close_session_on_close = not keep_alive @@ -40,32 +44,31 @@ def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser pathname=f"/v1/sessions/{resume_session_id}", ) else: - session_create_params = _object_value(merged.get("browserbase_session_create_params")) + session_create_params = _object_value(merged.launcher_bb_session_create_params) browser_settings = { **_object_value(session_create_params.get("browserSettings")), - **_object_value(merged.get("browserbase_browser_settings")), + **_object_value(merged.launcher_bb_browser_settings), } user_metadata = { **_object_value(session_create_params.get("userMetadata")), - **_object_value(merged.get("browserbase_user_metadata")), + **_object_value(merged.launcher_bb_user_metadata), } extension_id = _first_string( - merged.get("injector_extension_id"), + merged.launcher_bb_extension_id, session_create_params.get("extensionId"), _object_value(session_create_params.get("browserSettings")).get("extensionId"), ) - region = _first_string(merged.get("region"), session_create_params.get("region")) - viewport = _object_value(browser_settings.get("viewport")) + region = _first_string(merged.launcher_bb_region, session_create_params.get("region")) body: dict[str, Any] = { **session_create_params, **({"keepAlive": True} if keep_alive else {}), **({"region": region} if region else {}), - **({"timeout": merged.get("timeout")} if isinstance(merged.get("timeout"), int) else {}), + **({"timeout": merged.launcher_bb_timeout} if isinstance(merged.launcher_bb_timeout, int) else {}), **({"extensionId": extension_id} if extension_id else {}), "browserSettings": { **browser_settings, **({"extensionId": extension_id} if extension_id else {}), - "viewport": viewport if viewport.get("width") else DEFAULT_BROWSERBASE_VIEWPORT, + "viewport": browser_settings.get("viewport"), }, "userMetadata": { **user_metadata, @@ -107,14 +110,14 @@ def close() -> None: except Exception: pass - self.launched = { + self.launched = LaunchedBrowser( # Browserbase connectUrl is already a WebSocket CDP endpoint. - "cdp_url": connect_url, - "browserbase_session_id": session_id, - "browserbase_session_url": f"https://www.browserbase.com/sessions/{session_id}", - "browserbase_debug_url": _first_string(session.get("debuggerUrl"), session.get("debuggerFullscreenUrl")), - "close": close, - } + cdp_url=connect_url, + browserbase_session_id=session_id, + browserbase_session_url=f"https://www.browserbase.com/sessions/{session_id}", + browserbase_debug_url=_first_string(session.get("debuggerUrl"), session.get("debuggerFullscreenUrl")), + close=close, + ) return self.launched diff --git a/python/modcdp/launcher/BrowserLauncher.py b/python/modcdp/launcher/BrowserLauncher.py index 8b75ed83..e6a25b89 100644 --- a/python/modcdp/launcher/BrowserLauncher.py +++ b/python/modcdp/launcher/BrowserLauncher.py @@ -1,53 +1,23 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/BrowserLauncher.ts +# - ./go/modcdp/launcher/BrowserLauncher.go from __future__ import annotations import json import re import urllib.request from collections.abc import Callable -from typing import Any, TypedDict, cast - -from typing_extensions import NotRequired - - -class BrowserLaunchOptions(TypedDict, total=False): - executable_path: str | None - port: int | None - user_data_dir: str | None - headless: bool - sandbox: bool - args: list[str] - extra_args: list[str] - remote_debugging: str - loopback_cdp: bool - cleanup_user_data_dir: bool - chrome_ready_timeout_ms: int - chrome_ready_poll_interval_ms: int - cdp_url: str | None - browserbase_api_key: str | None - browserbase_base_url: str | None - browserbase_session_id: str | None - browserbase_keep_alive: bool - browserbase_close_session_on_close: bool - region: str | None - timeout: int | None - injector_extension_id: str | None - browserbase_browser_settings: dict[str, Any] | None - browserbase_user_metadata: dict[str, Any] | None - browserbase_session_create_params: dict[str, Any] | None - - -class LaunchedBrowser(TypedDict): - # Effective CDP endpoint for the selected transport; launchers resolve HTTP discovery endpoints to ws:// before returning when they can. - cdp_url: str | None - # Extension-dialable loopback CDP endpoint when it differs from cdp_url, for example pipe:// primary transport. - loopback_cdp_url: NotRequired[str | None] - close: Callable[[], Any] - profile_dir: NotRequired[str | None] - pipe_read: NotRequired[Any] - pipe_write: NotRequired[Any] - browserbase_session_id: NotRequired[str | None] - browserbase_session_url: NotRequired[str | None] - browserbase_debug_url: NotRequired[str | None] +from typing import TYPE_CHECKING, Any, TypeAlias + +from ..types.modcdp import LaunchedBrowser, ModCDPLauncherConfig +from ..types.toJSON import modCDPToJSON + +if TYPE_CHECKING: + from ..transport.UpstreamTransport import UpstreamTransport + + +LauncherConfig: TypeAlias = ModCDPLauncherConfig DEFAULT_CHROME_READY_TIMEOUT_MS = 45_000 @@ -58,49 +28,59 @@ class LaunchedBrowser(TypedDict): class BrowserLauncher: launched: LaunchedBrowser | None - def __init__(self, options: BrowserLaunchOptions | None = None) -> None: - self.options = cast(BrowserLaunchOptions, dict(options or {})) + def __init__(self, config: LauncherConfig | dict[str, Any] | None = None) -> None: + self.config = _launcher_config(config) self.launched = None - def update(self, config: BrowserLaunchOptions | None = None) -> "BrowserLauncher": - config = cast(BrowserLaunchOptions, dict(config or {})) - self.options = cast( - BrowserLaunchOptions, - { - **self.options, - **config, - **({"args": merge_chrome_args(self.options.get("args"), config["args"])} if "args" in config else {}), - **( - {"extra_args": merge_chrome_args(self.options.get("extra_args"), config["extra_args"])} - if "extra_args" in config - else {} - ), - }, - ) + def update(self, config: LauncherConfig | dict[str, Any] | None = None) -> "BrowserLauncher": + incoming = _launcher_config(config) + updates = incoming.model_dump(exclude_unset=True) + if "launcher_local_args" in incoming.model_fields_set: + updates["launcher_local_args"] = merge_chrome_args(self.config.launcher_local_args, incoming.launcher_local_args) + if "launcher_local_extra_args" in incoming.model_fields_set: + updates["launcher_local_extra_args"] = merge_chrome_args(self.config.launcher_local_extra_args, incoming.launcher_local_extra_args) + self.config = LauncherConfig.model_validate({**self.config.model_dump(), **updates}) return self - def getTransportConfig(self) -> dict[str, Any]: - return { - "cdp_url": (self.launched or {}).get("cdp_url") or self.options.get("cdp_url"), - "user_data_dir": (self.launched or {}).get("profile_dir") or self.options.get("user_data_dir"), - "pipe_read": (self.launched or {}).get("pipe_read"), - "pipe_write": (self.launched or {}).get("pipe_write"), - } - - def getServerConfig(self) -> dict[str, Any]: - loopback_cdp_url = (self.launched or {}).get("loopback_cdp_url") - return {"server_loopback_cdp_url": loopback_cdp_url} if loopback_cdp_url else {} - - def getInjectorConfig(self) -> dict[str, Any]: - return { - "injector_browserbase_api_key": self.options.get("browserbase_api_key"), - "injector_browserbase_base_url": self.options.get("browserbase_base_url"), - "injector_extension_id": self.options.get("injector_extension_id"), - } - - def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser: + def configForUpstream(self) -> dict[str, Any]: + config: dict[str, Any] = {} + upstream_ws_cdp_url = (self.launched.cdp_url if self.launched is not None else None) or self.config.launcher_remote_cdp_url + if upstream_ws_cdp_url: + config["upstream_ws_cdp_url"] = upstream_ws_cdp_url + return config + + def configForServer(self, upstream: UpstreamTransport) -> dict[str, Any]: + launcher_local_loopback_cdp_url = self.launched.loopback_cdp_url if self.launched is not None else None + if not launcher_local_loopback_cdp_url and upstream.config.upstream_mode == "ws" and upstream.config.upstream_ws_cdp_url: + launcher_local_loopback_cdp_url = upstream.config.upstream_ws_cdp_url + return {"upstream": {"upstream_mode": "ws", "upstream_ws_cdp_url": launcher_local_loopback_cdp_url}} if launcher_local_loopback_cdp_url else {} + + def launch(self, config: LauncherConfig | dict[str, Any] | None = None) -> LaunchedBrowser: raise NotImplementedError(f"{type(self).__name__}.launch is not implemented.") + def close(self) -> None: + launched = self.launched + self.launched = None + if launched is not None: + launched.close() + + def toJSON(self) -> dict[str, object]: + return modCDPToJSON( + self, + { + "state": { + "launched": self.launched is not None, + "cdp_url": self.launched.cdp_url if self.launched is not None else None, + "loopback_cdp_url": self.launched.loopback_cdp_url if self.launched is not None else None, + "cdp_listen_port": self.launched.cdp_listen_port if self.launched is not None else None, + "profile_dir": self.launched.profile_dir if self.launched is not None else None, + "browserbase_session_id": self.launched.browserbase_session_id if self.launched is not None else None, + "browserbase_session_url": self.launched.browserbase_session_url if self.launched is not None else None, + "browserbase_debug_url": self.launched.browserbase_debug_url if self.launched is not None else None, + } + }, + ) + def merge_chrome_args(existing: list[str] | None = None, incoming: list[str] | None = None) -> list[str]: args = [*(existing or []), *(incoming or [])] @@ -123,6 +103,12 @@ def merge_chrome_args(existing: list[str] | None = None, incoming: list[str] | N return merged +def _launcher_config(config: LauncherConfig | dict[str, Any] | None = None) -> LauncherConfig: + if isinstance(config, LauncherConfig): + return config + return LauncherConfig.model_validate(config or {}) + + def resolveCdpWebSocketUrl(endpoint: str, name: str = "cdp_url") -> str: if endpoint.startswith(("ws://", "wss://")): return endpoint diff --git a/python/modcdp/launcher/LocalBrowserLauncher.py b/python/modcdp/launcher/LocalBrowserLauncher.py index 8f5569e6..5432df31 100644 --- a/python/modcdp/launcher/LocalBrowserLauncher.py +++ b/python/modcdp/launcher/LocalBrowserLauncher.py @@ -1,10 +1,13 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/LocalBrowserLauncher.ts +# - ./go/modcdp/launcher/LocalBrowserLauncher.go from __future__ import annotations import glob import json import os import re -import select import signal import shutil import subprocess @@ -13,18 +16,23 @@ import time import urllib.request from pathlib import Path -from typing import Protocol, cast +from typing import Protocol from ..launcher.BrowserLauncher import ( DEFAULT_CHROME_READY_POLL_INTERVAL_MS, DEFAULT_CHROME_READY_TIMEOUT_MS, - BrowserLaunchOptions, + LauncherConfig, BrowserLauncher, LaunchedBrowser, + _launcher_config, ) class LocalBrowserLauncher(BrowserLauncher): + def __init__(self, config: LauncherConfig | dict | None = None) -> None: + raw_config = config.model_dump() if isinstance(config, LauncherConfig) else dict(config or {}) + super().__init__({**raw_config, "launcher_mode": "local"}) + @staticmethod def findChromeBinary(explicit: str | None = None) -> str: candidates = [explicit, os.environ.get("CHROME_PATH"), *_candidate_paths()] @@ -32,25 +40,23 @@ def findChromeBinary(explicit: str | None = None) -> str: if candidate and Path(candidate).exists(): return str(candidate) tried = ", ".join(str(candidate) for candidate in candidates if candidate) - raise RuntimeError(f"No Chrome/Chromium binary found. Tried: {tried}. Set CHROME_PATH or pass executable_path.") + raise RuntimeError(f"No Chrome/Chromium binary found. Tried: {tried}. Set CHROME_PATH or pass launcher_local_executable_path.") @staticmethod def freePort() -> int: return _free_port() - def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser: - merged = cast(BrowserLaunchOptions, {**self.options, **dict(options or {})}) - executable_path = self.findChromeBinary(merged.get("executable_path")) - use_pipe = merged.get("remote_debugging") == "pipe" - use_loopback_cdp = (not use_pipe) or bool(merged.get("loopback_cdp")) or merged.get("port") is not None - requested_port = merged.get("port") - port = int(requested_port) if use_loopback_cdp and requested_port is not None else (0 if use_loopback_cdp else None) + def launch(self, config: LauncherConfig | dict | None = None) -> LaunchedBrowser: + merged = self.config if config is None else _launcher_config({**self.config.model_dump(), **_launcher_config(config).model_dump(exclude_unset=True)}) + executable_path = self.findChromeBinary(merged.launcher_local_executable_path) + requested_port = merged.launcher_local_cdp_listen_port + port = int(requested_port) if requested_port is not None else 0 temp_profile_dir: tempfile.TemporaryDirectory[str] | None = None - profile_dir = merged.get("user_data_dir") + profile_dir = merged.launcher_local_user_data_dir if not profile_dir: temp_profile_dir = tempfile.TemporaryDirectory(prefix="modcdp.") profile_dir = temp_profile_dir.name - cleanup_profile_dir = str(profile_dir) if merged.get("cleanup_user_data_dir") else None + cleanup_profile_dir = str(profile_dir) if merged.launcher_local_user_data_dir and merged.launcher_local_cleanup_user_data_dir else None args = [ "--enable-unsafe-extension-debugging", "--remote-allow-origins=*", @@ -68,84 +74,30 @@ def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser "--use-mock-keychain", "--disable-gpu", f"--user-data-dir={profile_dir}", - "--remote-debugging-address=127.0.0.1" if use_loopback_cdp else None, - f"--remote-debugging-port={port}" if use_loopback_cdp else None, - "--remote-debugging-pipe" if use_pipe else None, + "--remote-debugging-address=127.0.0.1", + f"--remote-debugging-port={port}", ] args = [arg for arg in args if arg is not None] default_headless = sys.platform.startswith("linux") and not os.environ.get("DISPLAY") - if merged.get("headless", default_headless): + headless = merged.launcher_local_headless if merged.launcher_local_headless is not None else default_headless + if headless: args.append("--headless=new") - default_sandbox = not sys.platform.startswith("linux") - if merged.get("sandbox", default_sandbox) is False: + default_sandbox = not default_headless + if (merged.launcher_local_sandbox if merged.launcher_local_sandbox is not None else default_sandbox) is False: args.append("--no-sandbox") - args.extend(list(merged.get("args") or [])) - args.extend(list(merged.get("extra_args") or [])) + args.extend(list(merged.launcher_local_args)) + args.extend(list(merged.launcher_local_extra_args)) args.append("about:blank") - if use_pipe: - parent_read, child_write = os.pipe() - child_read, parent_write = os.pipe() - parent_read = _move_fd_if_needed(parent_read, {3, 4}) - parent_write = _move_fd_if_needed(parent_write, {3, 4}) - child_read = _move_fd_if_needed(child_read, {3, 4}) - child_write = _move_fd_if_needed(child_write, {3, 4}) - process = _spawn_chrome_with_pipe_fds(executable_path, args, child_read, child_write) - os.close(child_read) - os.close(child_write) - pipe_read = os.fdopen(parent_read, "rb", buffering=0) - pipe_write = os.fdopen(parent_write, "wb", buffering=0) - try: - _wait_for_pipe_ready(pipe_read, pipe_write, int(merged.get("chrome_ready_timeout_ms") or DEFAULT_CHROME_READY_TIMEOUT_MS)) - loopback_cdp_url = ( - ( - _wait_for_browser_selected_cdp_websocket_url( - str(profile_dir), - int(merged.get("chrome_ready_timeout_ms") or DEFAULT_CHROME_READY_TIMEOUT_MS), - int(merged.get("chrome_ready_poll_interval_ms") or DEFAULT_CHROME_READY_POLL_INTERVAL_MS), - process, - ) - if port == 0 - else _wait_for_cdp_websocket_url( - f"http://127.0.0.1:{port}", - int(merged.get("chrome_ready_timeout_ms") or DEFAULT_CHROME_READY_TIMEOUT_MS), - int(merged.get("chrome_ready_poll_interval_ms") or DEFAULT_CHROME_READY_POLL_INTERVAL_MS), - ) - ) - if port is not None - else None - ) - except Exception: - pipe_read.close() - pipe_write.close() - _close(process, temp_profile_dir, cleanup_profile_dir=cleanup_profile_dir) - raise - launched: LaunchedBrowser = { - "cdp_url": f"pipe://{process.pid}", - "profile_dir": profile_dir, - "pipe_read": pipe_read, - "pipe_write": pipe_write, - "close": lambda: _close( - process, - temp_profile_dir, - pipe_read, - pipe_write, - cleanup_profile_dir=cleanup_profile_dir, - ), - } - if loopback_cdp_url: - launched["loopback_cdp_url"] = loopback_cdp_url - self.launched = launched - return self.launched - process = subprocess.Popen( [executable_path, *args], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=not sys.platform.startswith("win"), ) - timeout_s = int(merged.get("chrome_ready_timeout_ms") or DEFAULT_CHROME_READY_TIMEOUT_MS) / 1000 - poll_s = int(merged.get("chrome_ready_poll_interval_ms") or DEFAULT_CHROME_READY_POLL_INTERVAL_MS) / 1000 + timeout_s = merged.launcher_local_chrome_ready_timeout_ms / 1000 + poll_s = merged.launcher_local_chrome_ready_poll_interval_ms / 1000 deadline = time.time() + timeout_s + active_port: int | None = None while time.time() < deadline: exit_code = process.poll() if exit_code is not None: @@ -162,13 +114,14 @@ def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser try: with urllib.request.urlopen(f"{cdp_url}/json/version", timeout=0.5) as response: version = json.loads(response.read()) - self.launched = { + self.launched = LaunchedBrowser( # cdp_url is resolved from the HTTP discovery endpoint before returning. - "cdp_url": version.get("webSocketDebuggerUrl") or cdp_url, - "loopback_cdp_url": version.get("webSocketDebuggerUrl") or cdp_url, - "profile_dir": profile_dir, - "close": lambda: _close(process, temp_profile_dir, cleanup_profile_dir=cleanup_profile_dir), - } + cdp_url=version.get("webSocketDebuggerUrl") or cdp_url, + cdp_listen_port=active_port if port == 0 else port, + loopback_cdp_url=version.get("webSocketDebuggerUrl") or cdp_url, + profile_dir=profile_dir, + close=lambda: _close(process, temp_profile_dir, cleanup_profile_dir=cleanup_profile_dir), + ) return self.launched except Exception: time.sleep(poll_s) @@ -236,36 +189,6 @@ def _candidate_paths() -> list[str]: return [*canary, *_chrome_for_testing_candidates(), *stock] -def _move_fd_if_needed(fd: int, reserved: set[int]) -> int: - if fd not in reserved: - return fd - moved = os.dup(fd) - while moved in reserved: - next_fd = os.dup(fd) - os.close(moved) - moved = next_fd - os.close(fd) - return moved - - -def _spawn_chrome_with_pipe_fds(executable_path: str, args: list[str], child_read: int, child_write: int) -> _ChromeProcess: - def map_pipe_fds() -> None: - os.dup2(child_read, 3) - os.dup2(child_write, 4) - os.close(child_read) - os.close(child_write) - - return subprocess.Popen( - [executable_path, *args], - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - close_fds=sys.platform.startswith("win"), - preexec_fn=None if sys.platform.startswith("win") else map_pipe_fds, - start_new_session=not sys.platform.startswith("win"), - ) - - class _ChromeProcess(Protocol): pid: int @@ -278,35 +201,6 @@ def wait(self, timeout: float | None = None) -> int | None: ... def poll(self) -> int | None: ... -def _wait_for_pipe_ready(pipe_read, pipe_write, timeout_ms: int) -> None: - ready_id = 1 - pipe_write.write(json.dumps({"id": ready_id, "method": "Browser.getVersion", "params": {}}).encode() + b"\0") - pipe_write.flush() - deadline = time.time() + timeout_ms / 1000 - buffer = b"" - while time.time() < deadline: - ready, _, _ = select.select([pipe_read], [], [], max(0.0, min(0.1, deadline - time.time()))) - if not ready: - continue - chunk = pipe_read.read(1) - if not chunk: - time.sleep(0.01) - continue - buffer += chunk - if b"\0" not in buffer: - continue - raw, buffer = buffer.split(b"\0", 1) - if not raw: - continue - message = json.loads(raw.decode()) - if message.get("id") != ready_id: - continue - if message.get("error"): - raise RuntimeError(message["error"].get("message") or "Browser.getVersion failed over pipe") - return - raise RuntimeError(f"Chrome remote-debugging pipe did not respond within {timeout_ms}ms") - - def _wait_for_cdp_websocket_url(cdp_url: str, timeout_ms: int, poll_interval_ms: int) -> str: deadline = time.time() + timeout_ms / 1000 poll_s = poll_interval_ms / 1000 @@ -347,7 +241,7 @@ def _wait_for_browser_selected_cdp_websocket_url( timeout_ms: int, poll_interval_ms: int, process: _ChromeProcess, -) -> str: +) -> tuple[str, int]: deadline = time.time() + timeout_ms / 1000 poll_s = poll_interval_ms / 1000 last_error: Exception | None = None @@ -358,7 +252,7 @@ def _wait_for_browser_selected_cdp_websocket_url( active_port = _read_devtools_active_port(profile_dir) if active_port is not None: try: - return _wait_for_cdp_websocket_url(f"http://127.0.0.1:{active_port}", poll_interval_ms, poll_interval_ms) + return _wait_for_cdp_websocket_url(f"http://127.0.0.1:{active_port}", poll_interval_ms, poll_interval_ms), active_port except Exception as err: last_error = err time.sleep(poll_s) @@ -370,16 +264,8 @@ def _wait_for_browser_selected_cdp_websocket_url( def _close( process: _ChromeProcess, temp_profile_dir: tempfile.TemporaryDirectory[str] | None, - pipe_read=None, - pipe_write=None, cleanup_profile_dir: str | None = None, ) -> None: - for pipe in (pipe_read, pipe_write): - try: - if pipe is not None: - pipe.close() - except Exception: - pass _signal_process(process, signal.SIGTERM) try: process.wait(timeout=2) diff --git a/python/modcdp/launcher/NoneBrowserLauncher.py b/python/modcdp/launcher/NoneBrowserLauncher.py new file mode 100644 index 00000000..0c2c59c8 --- /dev/null +++ b/python/modcdp/launcher/NoneBrowserLauncher.py @@ -0,0 +1,17 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/NoneBrowserLauncher.ts +# - ./go/modcdp/launcher/NoneBrowserLauncher.go +from __future__ import annotations + +from ..launcher.BrowserLauncher import LauncherConfig, BrowserLauncher, LaunchedBrowser + + +class NoneBrowserLauncher(BrowserLauncher): + def __init__(self, config: LauncherConfig | dict | None = None) -> None: + raw_config = config.model_dump() if isinstance(config, LauncherConfig) else dict(config or {}) + super().__init__({**raw_config, "launcher_mode": "none"}) + + def launch(self, config: LauncherConfig | dict | None = None) -> LaunchedBrowser: + self.launched = LaunchedBrowser(cdp_url=None, close=lambda: None) + return self.launched diff --git a/python/modcdp/launcher/NoopBrowserLauncher.py b/python/modcdp/launcher/NoopBrowserLauncher.py deleted file mode 100644 index b1e1ba53..00000000 --- a/python/modcdp/launcher/NoopBrowserLauncher.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -from ..launcher.BrowserLauncher import BrowserLaunchOptions, BrowserLauncher, LaunchedBrowser - - -class NoopBrowserLauncher(BrowserLauncher): - def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser: - self.launched = {"cdp_url": None, "close": lambda: None} - return self.launched diff --git a/python/modcdp/launcher/RemoteBrowserLauncher.py b/python/modcdp/launcher/RemoteBrowserLauncher.py index a2b338e2..0aedd5f5 100644 --- a/python/modcdp/launcher/RemoteBrowserLauncher.py +++ b/python/modcdp/launcher/RemoteBrowserLauncher.py @@ -1,20 +1,22 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/RemoteBrowserLauncher.ts +# - ./go/modcdp/launcher/RemoteBrowserLauncher.go from __future__ import annotations -from typing import cast - -from ..launcher.BrowserLauncher import BrowserLaunchOptions, BrowserLauncher, LaunchedBrowser, resolveCdpWebSocketUrl +from ..launcher.BrowserLauncher import LauncherConfig, BrowserLauncher, LaunchedBrowser, resolveCdpWebSocketUrl, _launcher_config class RemoteBrowserLauncher(BrowserLauncher): - def __init__(self, options: BrowserLaunchOptions | None = None, cdp_url: str | None = None) -> None: - super().__init__(cast(BrowserLaunchOptions, {**dict(options or {}), **({"cdp_url": cdp_url} if cdp_url is not None else {})})) + def __init__(self, config: LauncherConfig | dict | None = None) -> None: + raw_config = config.model_dump() if isinstance(config, LauncherConfig) else dict(config or {}) + super().__init__({**raw_config, "launcher_mode": "remote"}) - def launch(self, options: BrowserLaunchOptions | None = None) -> LaunchedBrowser: - merged = {**self.options, **dict(options or {})} - cdp_url = cast(str | None, merged.get("cdp_url")) + def launch(self, config: LauncherConfig | dict | None = None) -> LaunchedBrowser: + merged = self.config if config is None else _launcher_config({**self.config.model_dump(), **_launcher_config(config).model_dump(exclude_unset=True)}) + cdp_url = merged.launcher_remote_cdp_url if not cdp_url: - raise RuntimeError("launcher.launcher_mode=remote requires upstream.upstream_cdp_url.") - # cdp_url is resolved here so downstream transports can dial it directly. - cdp_url = resolveCdpWebSocketUrl(cdp_url, "remote cdp_url") - self.launched = {"cdp_url": cdp_url, "close": lambda: None} + raise RuntimeError("launcher_mode=remote requires launcher_remote_cdp_url.") + cdp_url = resolveCdpWebSocketUrl(cdp_url, "launcher_remote_cdp_url") + self.launched = LaunchedBrowser(cdp_url=cdp_url, close=lambda: None) return self.launched diff --git a/python/modcdp/launcher/__init__.py b/python/modcdp/launcher/__init__.py index 9dbf93e9..293308de 100644 --- a/python/modcdp/launcher/__init__.py +++ b/python/modcdp/launcher/__init__.py @@ -1,5 +1,17 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/launcher/BrowserLauncher.ts +# - ./js/src/launcher/LocalBrowserLauncher.ts +# - ./js/src/launcher/RemoteBrowserLauncher.ts +# - ./js/src/launcher/BBBrowserLauncher.ts +# - ./js/src/launcher/NoneBrowserLauncher.ts +# - ./go/modcdp/launcher/BrowserLauncher.go +# - ./go/modcdp/launcher/LocalBrowserLauncher.go +# - ./go/modcdp/launcher/RemoteBrowserLauncher.go +# - ./go/modcdp/launcher/BBBrowserLauncher.go +# - ./go/modcdp/launcher/NoneBrowserLauncher.go from .BrowserLauncher import BrowserLauncher -from .BrowserbaseBrowserLauncher import BrowserbaseBrowserLauncher +from .BBBrowserLauncher import BBBrowserLauncher from .LocalBrowserLauncher import LocalBrowserLauncher -from .NoopBrowserLauncher import NoopBrowserLauncher +from .NoneBrowserLauncher import NoneBrowserLauncher from .RemoteBrowserLauncher import RemoteBrowserLauncher diff --git a/python/modcdp/router/AutoSessionRouter.py b/python/modcdp/router/AutoSessionRouter.py index adcc6162..6831a29b 100644 --- a/python/modcdp/router/AutoSessionRouter.py +++ b/python/modcdp/router/AutoSessionRouter.py @@ -1,112 +1,696 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/router/AutoSessionRouter.ts +# - ./go/modcdp/router/AutoSessionRouter.go from __future__ import annotations import threading +import time from collections.abc import Callable, Mapping from typing import Any +from ..translate.translate import DEFAULT_CLIENT_ROUTES +from ..transport.UpstreamTransport import UpstreamTransport +from ..types.CDPTypes import CDPTypes +from ..types.generated.cdp import PageDomain, RuntimeDomain, TargetDomain +from ..types.modcdp import ModCDPRouterConfig, ProtocolParams, ProtocolResult, _isObjectMap +from ..types.toJSON import modCDPToJSON -SendCDP = Callable[[str, dict[str, Any], str | None], dict[str, Any]] -max_detached_session_guards = 1024 +targetAutoAttachParams = {"autoAttach": True, "waitForDebuggerOnStart": False, "flatten": True} +browserLevelDomains = {"Browser", "Target", "SystemInfo"} +DEFAULT_ROUTER_EXECUTION_CONTEXT_TIMEOUT_MS = 10_000 +RouterConfig = ModCDPRouterConfig class AutoSessionRouter: - def __init__(self, send: SendCDP, defaultExecutionContextTimeoutMs: Callable[[], int]) -> None: - self.send = send - self.defaultExecutionContextTimeoutMs = defaultExecutionContextTimeoutMs - self.target_sessions: dict[str, str] = {} - self.session_targets: dict[str, dict[str, Any]] = {} - self.execution_contexts: dict[str, int] = {} - self._execution_context_waiters: dict[str, list[tuple[threading.Event, dict[str, Any]]]] = {} - self._detached_sessions: dict[str, None] = {} + def __init__( + self, + upstream: UpstreamTransport, + types: CDPTypes, + config: RouterConfig | Mapping[str, Any] | None = None, + ) -> None: + raw_config = dict(config.model_dump() if isinstance(config, RouterConfig) else config or {}) + self.config = RouterConfig.model_validate( + { + **raw_config, + "router_routes": { + **DEFAULT_CLIENT_ROUTES, + **dict(raw_config.get("router_routes") or {}), + }, + } + ) + self.upstream = upstream + self.types = types + self.sessionId_from_targetId: dict[str, str] = {} + self.targetId_from_sessionId: dict[str, str] = {} + self.targets: dict[str, dict[str, Any]] = {} + self.contexts: dict[str, dict[str, Any]] = {} + self._execution_context_waiters: dict[str, list[tuple[threading.Event, dict[str, Any], Callable[[dict[str, Any]], bool]]]] = {} self._lock = threading.RLock() + self._subscription_cleanups: list[Callable[[], None]] = [] + self._started = False - def sessionIdForTarget(self, target_id: str) -> str | None: - with self._lock: - return self.target_sessions.get(target_id) + def start(self) -> None: + if self._started: + return None + self._subscription_cleanups = self._listen() + try: + self.upstream.send("Target.setAutoAttach", targetAutoAttachParams, None) + self.upstream.send("Target.setDiscoverTargets", {"discover": True}, None) + except Exception: + for cleanup in self._subscription_cleanups: + cleanup() + self._subscription_cleanups = [] + raise + self._started = True + + def stop(self) -> None: + for cleanup in self._subscription_cleanups: + cleanup() + self._subscription_cleanups = [] + self._started = False + return None + + def _listen(self) -> list[Callable[[], None]]: + return [ + self.upstream.on( + TargetDomain.attachedToTarget, + lambda event, _target_id, session_id: self._recordProtocolEvent( + TargetDomain.attachedToTarget.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + TargetDomain.detachedFromTarget, + lambda event, _target_id, session_id: self._recordProtocolEvent( + TargetDomain.detachedFromTarget.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + TargetDomain.targetInfoChanged, + lambda event, _target_id, session_id: self._recordProtocolEvent( + TargetDomain.targetInfoChanged.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + TargetDomain.targetDestroyed, + lambda event, _target_id, session_id: self._recordProtocolEvent( + TargetDomain.targetDestroyed.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + RuntimeDomain.executionContextCreated, + lambda event, _target_id, session_id: self._recordProtocolEvent( + RuntimeDomain.executionContextCreated.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + RuntimeDomain.executionContextDestroyed, + lambda event, _target_id, session_id: self._recordProtocolEvent( + RuntimeDomain.executionContextDestroyed.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + RuntimeDomain.executionContextsCleared, + lambda event, _target_id, session_id: self._recordProtocolEvent( + RuntimeDomain.executionContextsCleared.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + PageDomain.frameNavigated, + lambda event, _target_id, session_id: self._recordProtocolEvent( + PageDomain.frameNavigated.cdp_event_name, event, _target_id, session_id + ), + ), + self.upstream.on( + PageDomain.frameDetached, + lambda event, _target_id, session_id: self._recordProtocolEvent( + PageDomain.frameDetached.cdp_event_name, event, _target_id, session_id + ), + ), + ] + + def toJSON(self) -> dict[str, object]: + return modCDPToJSON( + self, + { + "config": { + "router_routes": self.config.router_routes, + "loopback_execution_context_timeout_ms": self.config.loopback_execution_context_timeout_ms, + }, + "state": { + "started": self._started, + "sessions": len(self.sessionId_from_targetId), + "targets": len(self.targets), + "contexts": len(self.contexts), + "execution_context_waiters": len(self._execution_context_waiters), + }, + }, + ) + + def send(self, method: str, params: ProtocolParams | None = None, requested_session_id: str | None = None) -> ProtocolResult: + if self.types.nativeCommandSchema(method) is None: + raise RuntimeError(f"AutoSessionRouter cannot route unknown CDP command {method}.") + command_params = self.types.parseCommandParams(method, params or {}) + domain = method.split(".", 1)[0] + if requested_session_id is not None: + target_id = self.targetId_from_sessionId.get(requested_session_id) + if target_id is None: + raise RuntimeError(f"No target is recorded for sessionId={requested_session_id}.") + routed_params = ( + self._callFunctionOnParamsForRoute(command_params, target_id, requested_session_id) + if method == "Runtime.callFunctionOn" + else command_params + ) + return _protocol_result(self.types.parseCommandResult(method, self.upstream.send(method, routed_params, requested_session_id))) + if domain in browserLevelDomains: + return _protocol_result(self.types.parseCommandResult(method, self.upstream.send(method, command_params, None))) + target_id = self._resolveTargetId(command_params) + target_id, session_id = self.ensureRouteForTarget(target_id) + routed_params = ( + self._callFunctionOnParamsForRoute(command_params, target_id, session_id) + if method == "Runtime.callFunctionOn" + else command_params + ) + return _protocol_result(self.types.parseCommandResult(method, self.upstream.send(method, routed_params, session_id))) def attachToTarget(self, target_id: str) -> str | None: - existing_session_id = self.sessionIdForTarget(target_id) - if existing_session_id is not None: - return existing_session_id - result = self.send("Target.attachToTarget", {"targetId": target_id, "flatten": True}, None) - session_id = result.get("sessionId") - return session_id if isinstance(session_id, str) and session_id else None - - def recordProtocolEvent(self, method: str, data: object, session_id: str | None) -> None: - event_data = dict(data) if isinstance(data, Mapping) else {} + with self._lock: + session_id = self.sessionId_from_targetId.get(target_id) + if session_id is not None: + return session_id + session_id = self.upstream.attachToTarget(target_id) + if session_id: + with self._lock: + self._recordTargetSession(target_id, session_id, self.targets.get(target_id)) + return session_id + return None + + def ensureSessionForTarget(self, target_id: str) -> str: + _target_id, session_id = self.ensureRouteForTarget(target_id) + if session_id is None: + raise RuntimeError(f"Upstream attached targetId={target_id} without a CDP session id.") + return session_id + + def ensureRouteForTarget(self, target_id: str | None) -> tuple[str, str | None]: + resolved_target_id = target_id or self._resolveTargetId({}) + if resolved_target_id is not None: + session_id = self.sessionId_from_targetId.get(resolved_target_id) + if session_id is not None: + return resolved_target_id, session_id + target = self.targets.get(resolved_target_id) + if target and target.get("sessionId") is None: + return resolved_target_id, None + if resolved_target_id is None: + resolved_target_id = self.upstream.createTarget("about:blank#modcdp") + session_id = self.attachToTarget(resolved_target_id) + if session_id is None: + self._recordTargetSessionlessAttachment(resolved_target_id) + return resolved_target_id, None + return resolved_target_id, session_id + + def _recordProtocolEvent(self, method: str, data: object, event_target_id: str | None, session_id: str | None) -> None: + event_data = data if _isObjectMap(data) else {} if method == "Target.attachedToTarget": attached_session_id = event_data.get("sessionId") if isinstance(event_data.get("sessionId"), str) else session_id raw_target_info = event_data.get("targetInfo") - target_info = dict(raw_target_info) if isinstance(raw_target_info, Mapping) else None + target_info = raw_target_info if _isObjectMap(raw_target_info) else None target_id = target_info.get("targetId") if target_info else None if isinstance(attached_session_id, str) and isinstance(target_id, str) and target_info: with self._lock: - self._detached_sessions.pop(attached_session_id, None) - self.target_sessions[target_id] = attached_session_id - self.session_targets[attached_session_id] = target_info + self._recordTargetSession(target_id, attached_session_id, target_info) + elif method == "Target.targetInfoChanged": + raw_target_info = event_data.get("targetInfo") + if _isObjectMap(raw_target_info): + with self._lock: + self._recordTarget(raw_target_info) + elif method == "Target.targetDestroyed": + target_id = event_data.get("targetId") + if isinstance(target_id, str): + self._forgetTarget(target_id) elif method == "Runtime.executionContextCreated": raw_context = event_data.get("context") - context = raw_context if isinstance(raw_context, Mapping) else None + context = raw_context if _isObjectMap(raw_context) else None context_id = context.get("id") if context else None + if (session_id or event_target_id) and isinstance(context_id, int) and context is not None: + self._recordExecutionContext(event_target_id, session_id, context) + elif method == "Runtime.executionContextDestroyed": + context_id = event_data.get("executionContextId") if session_id and isinstance(context_id, int): - self._recordExecutionContext(session_id, context_id) + self._forgetExecutionContextById(session_id, context_id) + elif method == "Runtime.executionContextsCleared": + if session_id: + self._forgetExecutionContextsForRoute(session_id) + elif method == "Page.frameNavigated": + raw_frame = event_data.get("frame") + frame = raw_frame if _isObjectMap(raw_frame) else {} + frame_id = frame.get("id") + target_id = event_target_id or (self.targetId_from_sessionId.get(session_id) if session_id else None) + if isinstance(frame_id, str): + self._forgetExecutionContextsForFrame(session_id, target_id, frame_id) + elif method == "Page.frameDetached": + frame_id = event_data.get("frameId") + target_id = event_target_id or (self.targetId_from_sessionId.get(session_id) if session_id else None) + if isinstance(frame_id, str): + self._forgetExecutionContextsForFrame(session_id, target_id, frame_id) elif method == "Target.detachedFromTarget": detached_session_id = event_data.get("sessionId") if isinstance(event_data.get("sessionId"), str) else session_id if isinstance(detached_session_id, str): self._forgetSession(detached_session_id) def waitForExecutionContext(self, session_id: str | None, timeout_ms: int | None = None) -> int: - effective_timeout_ms = timeout_ms if timeout_ms is not None else self.defaultExecutionContextTimeoutMs() if not session_id: raise RuntimeError("Cannot wait for a Runtime execution context without a session.") - with self._lock: - existing = self.execution_contexts.get(session_id) - if existing is not None: - return existing - event = threading.Event() - result: dict[str, int] = {} - self._execution_context_waiters.setdefault(session_id, []).append((event, result)) - if not event.wait(effective_timeout_ms / 1000): - with self._lock: - waiters = self._execution_context_waiters.get(session_id, []) - self._execution_context_waiters[session_id] = [item for item in waiters if item[0] is not event] - if not self._execution_context_waiters[session_id]: - self._execution_context_waiters.pop(session_id, None) - raise RuntimeError(f"Timed out waiting for Runtime.executionContextCreated for session {session_id}.") - error = result.get("error") - if isinstance(error, BaseException): - raise error - return result["context_id"] + return int(self._waitForExecutionContextMatching(lambda context: context.get("sessionId") == session_id, session_id, timeout_ms)["id"]) + + def ensureExecutionContext(self, frame: Mapping[str, str], selector: Mapping[str, str] | None = None) -> dict[str, Any]: + selected = {"world": "main", **dict(selector or {})} + frame_id = frame["frameId"] + target_id = frame["targetId"] + route_target_id, session_id = self.ensureRouteForTarget(target_id) + existing = self._findExecutionContext(route_target_id, session_id, frame_id, selected) + if existing is not None: + return existing + self.upstream.send("Runtime.enable", {}, session_id) + if selected["world"] in ("isolated", "piercer"): + created = self.upstream.send( + "Page.createIsolatedWorld", + { + "frameId": frame_id, + **({"worldName": selected.get("worldName") or "__modcdp_piercer__"} if selected["world"] == "piercer" else {}), + "grantUniveralAccess": True, + }, + session_id, + ) + created_context = self._findExecutionContext(route_target_id, session_id, frame_id, selected) + execution_context_id = created.get("executionContextId") + if not isinstance(execution_context_id, int): + raise RuntimeError("Page.createIsolatedWorld returned no executionContextId.") + if created_context and created_context.get("id") == execution_context_id: + return created_context + context = { + "id": execution_context_id, + "sessionId": session_id, + "targetId": route_target_id, + "frameId": frame_id, + "world": "piercer" if selected["world"] == "piercer" else selected.get("worldName") or "isolated", + "name": selected.get("worldName"), + } + self.contexts[self._contextKey(route_target_id, session_id, execution_context_id, None)] = context + return context + return self._waitForExecutionContextMatching( + lambda context: context.get("targetId") == route_target_id + and context.get("sessionId") == session_id + and context.get("frameId") == frame_id + and context.get("world") == selected["world"], + session_id or route_target_id, + ) + + def getTopology(self, params: Mapping[str, Any] | None = None) -> dict[str, Any]: + object_group = f"modcdp-topology-{int(time.time() * 1000)}" + raw_target_infos = self.upstream.send("Target.getTargets", {}, None).get("targetInfos") + target_infos = [target for target in raw_target_infos if _isObjectMap(target)] if isinstance(raw_target_infos, list) else [] + for target_info in target_infos: + self._recordTarget(target_info) + root_target = self._resolveRootTarget(dict(params or {}), target_infos) + if root_target is None: + raise RuntimeError("Mod.getTopology could not resolve a page target.") + frames: dict[str, dict[str, Any]] = {} + root_target_id = str(root_target["targetId"]) + _root_route_target_id, root_session_id = self._enableTarget(root_target_id) + root_tree = self.upstream.send("Page.getFrameTree", {}, root_session_id).get("frameTree") + if not _isObjectMap(root_tree): + raise RuntimeError("Page.getFrameTree returned no frameTree.") + root_frame_id = self._recordFrameTree(root_tree, root_target_id, None, frames) + + oopif_targets = [ + target + for target in target_infos + if target.get("type") == "iframe" and isinstance(target.get("parentFrameId"), str) and target.get("targetId") not in frames + ] + for target in oopif_targets: + target_id = str(target["targetId"]) + _route_target_id, session_id = self._enableTarget(target_id) + frame_tree = self.upstream.send("Page.getFrameTree", {}, session_id).get("frameTree") + if _isObjectMap(frame_tree): + self._recordFrameTree(frame_tree, target_id, str(target.get("parentFrameId")), frames) + + for frame_id, frame in list(frames.items()): + parent_frame_id = frame.get("parentFrameId") + if not isinstance(parent_frame_id, str): + continue + parent = frames.get(parent_frame_id) + if parent is None: + continue + _parent_target_id, parent_session_id = self.ensureRouteForTarget(str(parent["targetId"])) + owner = self.upstream.send("DOM.getFrameOwner", {"frameId": frame_id}, parent_session_id) + backend_node_id = owner.get("backendNodeId") + if isinstance(backend_node_id, int): + frame["outerBackendNodeId"] = backend_node_id + + contexts: dict[str, dict[str, Any]] = {} + roots: dict[str, dict[str, Any]] = {} + for frame_id, frame in list(frames.items()): + context = self.ensureExecutionContext({"frameId": frame_id, "targetId": str(frame["targetId"])}, {"world": "piercer"}) + contexts[self._contextKey(str(context["targetId"]), context.get("sessionId") if isinstance(context.get("sessionId"), str) else None, int(context["id"]), context.get("uniqueId"))] = context + evaluate_params: dict[str, Any] = {"expression": "document.documentElement", "objectGroup": object_group} + if isinstance(context.get("uniqueId"), str): + evaluate_params["uniqueContextId"] = context["uniqueId"] + else: + evaluate_params["contextId"] = context["id"] + root_object = self.upstream.send("Runtime.evaluate", evaluate_params, context.get("sessionId") if isinstance(context.get("sessionId"), str) else None) + result = root_object.get("result") + if not _isObjectMap(result): + raise RuntimeError("Runtime.evaluate returned no remote object result.") + object_id = result.get("objectId") + if not isinstance(object_id, str) or not object_id: + raise RuntimeError(f"Mod.getTopology could not resolve document root for frameId={frame_id}.") + described = self.upstream.send("DOM.describeNode", {"objectId": object_id}, context.get("sessionId") if isinstance(context.get("sessionId"), str) else None) + node = described.get("node") + if not _isObjectMap(node): + raise RuntimeError("DOM.describeNode returned no node.") + roots[object_id] = { + "kind": "document", + "frameId": frame_id, + "outerBackendNodeId": frame.get("outerBackendNodeId"), + "innerBackendNodeId": node.get("backendNodeId"), + "executionContextId": context["id"], + **({"uniqueContextId": context["uniqueId"]} if isinstance(context.get("uniqueId"), str) else {}), + } + + for target_id in {str(frame["targetId"]) for frame in frames.values()}: + _route_target_id, session_id = self.ensureRouteForTarget(target_id) + document = self.upstream.send("DOM.getDocument", {"depth": -1, "pierce": True}, session_id) + root = document.get("root") + if _isObjectMap(root): + self._recordShadowRoots(root, frames, roots, object_group) + + frame_target_ids = {frame.get("targetId") for frame in frames.values()} + for context in self.contexts.values(): + if context.get("targetId") in frame_target_ids: + contexts[self._contextKey(str(context["targetId"]), context.get("sessionId") if isinstance(context.get("sessionId"), str) else None, int(context["id"]), context.get("uniqueId"))] = context + + return { + "objectGroup": object_group, + "rootFrameId": root_frame_id, + "frames": frames, + "roots": roots, + "targets": {target_id: target for target_id, target in self.targets.items() if any(info.get("targetId") == target_id for info in target_infos)}, + "contexts": contexts, + } + + def _recordTarget(self, target_info: dict[str, object]) -> None: + target_id = target_info.get("targetId") + if not isinstance(target_id, str): + return + session_id = self.sessionId_from_targetId.get(target_id) + existing = self.targets.get(target_id) + target = {**dict(target_info)} + if session_id is not None: + target["sessionId"] = session_id + elif existing and existing.get("sessionId") is None: + target["sessionId"] = None + self.targets[target_id] = target - def _recordExecutionContext(self, session_id: str, context_id: int) -> None: + def _recordTargetSession(self, target_id: str, session_id: str, target_info: Mapping[str, Any] | None) -> None: + self.sessionId_from_targetId[target_id] = session_id + self.targetId_from_sessionId[session_id] = target_id + target = {**dict(target_info or self.targets.get(target_id) or {"targetId": target_id, "type": "page"})} + target["targetId"] = target_id + target["sessionId"] = session_id + self.targets[target_id] = target + + def _recordTargetSessionlessAttachment(self, target_id: str) -> None: + existing = self.targets.get(target_id) + self.targets[target_id] = {**existing, "sessionId": None} if existing else {"targetId": target_id, "type": "page", "sessionId": None} + + def _recordExecutionContext(self, event_target_id: str | None, session_id: str | None, context: dict[str, object]) -> None: with self._lock: - if session_id in self._detached_sessions: + target_id = event_target_id or (self.targetId_from_sessionId.get(session_id) if session_id else None) + if target_id is None: return - self.execution_contexts[session_id] = context_id - waiters = self._execution_context_waiters.pop(session_id, []) - for event, result in waiters: + context_id = context.get("id") + if not isinstance(context_id, int): + raise RuntimeError("Runtime.executionContextCreated returned no numeric context id.") + raw_aux_data = context.get("auxData") + aux_data = raw_aux_data if _isObjectMap(raw_aux_data) else {} + frame_id = aux_data.get("frameId") if isinstance(aux_data.get("frameId"), str) else None + context_name = context.get("name") if isinstance(context.get("name"), str) else "" + aux_type = aux_data.get("type") + world = ( + "piercer" + if context_name == "__modcdp_piercer__" + else "main" + if aux_type == "default" + else context_name or str(aux_type or "isolated") + ) + topology_context = { + **dict(context), + "id": context_id, + "sessionId": session_id, + "targetId": target_id, + "frameId": frame_id, + "world": world, + } + context_key = self._contextKey(target_id, session_id, context_id, context.get("uniqueId")) + self.contexts[context_key] = topology_context + waiter_key = session_id or target_id + waiters = self._execution_context_waiters.get(waiter_key, []) + matched_waiters = [item for item in waiters if item[2](topology_context)] + remaining_waiters = [item for item in waiters if item not in matched_waiters] + if remaining_waiters: + self._execution_context_waiters[waiter_key] = remaining_waiters + else: + self._execution_context_waiters.pop(waiter_key, None) + for event, result, _matches in matched_waiters: result["context_id"] = context_id + result["context"] = topology_context event.set() + def _forgetTarget(self, target_id: str) -> None: + with self._lock: + session_id = self.sessionId_from_targetId.get(target_id) + self.targets.pop(target_id, None) + if session_id: + self._forgetSession(session_id) + self._forgetExecutionContextsForRoute(target_id) + def _forgetSession(self, session_id: str) -> None: with self._lock: - target_info = self.session_targets.pop(session_id, None) - target_id = target_info.get("targetId") if target_info else None - if isinstance(target_id, str): - self.target_sessions.pop(target_id, None) - self.execution_contexts.pop(session_id, None) - self._markDetachedSession(session_id) + target_id = self.targetId_from_sessionId.pop(session_id, None) + if target_id is not None: + self.sessionId_from_targetId.pop(target_id, None) + self._forgetExecutionContextsForRoute(session_id) waiters = self._execution_context_waiters.pop(session_id, []) error = RuntimeError(f"Runtime execution context wait cancelled because session {session_id} detached.") - for event, result in waiters: + for event, result, _matches in waiters: result["error"] = error event.set() - def _markDetachedSession(self, session_id: str) -> None: - self._detached_sessions.pop(session_id, None) - self._detached_sessions[session_id] = None - while len(self._detached_sessions) > max_detached_session_guards: - oldest_session_id = next(iter(self._detached_sessions), None) - if oldest_session_id is None: - break - self._detached_sessions.pop(oldest_session_id, None) + def _forgetExecutionContextById(self, route_key: str, context_id: int) -> None: + with self._lock: + for context_key, context in list(self.contexts.items()): + if (context.get("sessionId") == route_key or context.get("targetId") == route_key) and context.get("id") == context_id: + self.contexts.pop(context_key, None) + + def _forgetExecutionContextsForRoute(self, route_key: str) -> None: + for context_key, context in list(self.contexts.items()): + if context.get("sessionId") == route_key or context.get("targetId") == route_key: + self.contexts.pop(context_key, None) + + def _forgetExecutionContextsForFrame(self, session_id: str | None, target_id: str | None, frame_id: str) -> None: + with self._lock: + for context_key, context in list(self.contexts.items()): + if context.get("frameId") != frame_id: + continue + if session_id is not None and context.get("sessionId") == session_id: + self.contexts.pop(context_key, None) + elif target_id is not None and context.get("targetId") == target_id: + self.contexts.pop(context_key, None) + + def _resolveRootTarget(self, params: Mapping[str, Any], target_infos: list[dict[str, Any]]) -> dict[str, Any] | None: + requested_target_id = params.get("rootTargetId") or params.get("targetId") + if isinstance(requested_target_id, str) and requested_target_id: + return next((target for target in target_infos if target.get("targetId") == requested_target_id), None) + return next( + ( + target + for target in target_infos + if target.get("type") == "page" and not (isinstance(target.get("url"), str) and str(target["url"]).startswith("devtools://")) + ), + None, + ) + + def _enableTarget(self, target_id: str) -> tuple[str, str | None]: + route_target_id, session_id = self.ensureRouteForTarget(target_id) + for method, params in ( + ("Page.enable", {}), + ("DOM.enable", {}), + ("Runtime.enable", {}), + ("Target.setAutoAttach", targetAutoAttachParams), + ): + try: + self.upstream.send(method, params, session_id) + except Exception: + if method != "Target.setAutoAttach": + raise + return route_target_id, session_id + + def _recordFrameTree(self, tree: dict[str, object], target_id: str, parent_frame_id: str | None, frames: dict[str, dict[str, Any]]) -> str: + frame = tree.get("frame") + if not _isObjectMap(frame): + raise RuntimeError("frame tree entry is missing frame.") + frame_id = frame.get("id") + if not isinstance(frame_id, str): + raise RuntimeError("frame tree entry is missing frame.id.") + frames[frame_id] = { + "targetId": target_id, + "url": frame.get("url"), + "parentFrameId": frame.get("parentId") if isinstance(frame.get("parentId"), str) else parent_frame_id, + } + child_frames = tree.get("childFrames") + if isinstance(child_frames, list): + for child in child_frames: + if _isObjectMap(child): + self._recordFrameTree(child, target_id, frame_id, frames) + return frame_id + + def _recordShadowRoots( + self, + node: dict[str, object], + frames: dict[str, dict[str, Any]], + roots: dict[str, dict[str, Any]], + object_group: str, + frame_id: str | None = None, + outer_backend_node_id: int | None = None, + ) -> None: + raw_frame_id = node.get("frameId") + current_frame_id = raw_frame_id if isinstance(raw_frame_id, str) else frame_id + raw_node_backend_node_id = node.get("backendNodeId") + node_backend_node_id = raw_node_backend_node_id if isinstance(raw_node_backend_node_id, int) else None + shadow_roots = node.get("shadowRoots") + if isinstance(shadow_roots, list): + for shadow_root in shadow_roots: + if not _isObjectMap(shadow_root): + continue + if current_frame_id: + frame = frames.get(current_frame_id) + context = self._findExecutionContext(str(frame["targetId"]), None, current_frame_id, {"world": "piercer"}) if frame else None + if frame and context and isinstance(shadow_root.get("backendNodeId"), int): + resolved = self.upstream.send( + "DOM.resolveNode", + {"backendNodeId": shadow_root["backendNodeId"], "executionContextId": context["id"], "objectGroup": object_group}, + context.get("sessionId") if isinstance(context.get("sessionId"), str) else None, + ) + remote_object = resolved.get("object") + if not _isObjectMap(remote_object): + raise RuntimeError("DOM.resolveNode returned no remote object.") + object_id = remote_object.get("objectId") + if isinstance(object_id, str): + roots[object_id] = { + "kind": "shadow", + "frameId": current_frame_id, + "outerBackendNodeId": outer_backend_node_id if outer_backend_node_id is not None else node_backend_node_id, + "innerBackendNodeId": shadow_root.get("backendNodeId"), + "mode": shadow_root.get("shadowRootType"), + "executionContextId": context["id"], + **({"uniqueContextId": context["uniqueId"]} if isinstance(context.get("uniqueId"), str) else {}), + } + self._recordShadowRoots(shadow_root, frames, roots, object_group, current_frame_id, node_backend_node_id) + children = node.get("children") + if isinstance(children, list): + for child in children: + if _isObjectMap(child): + self._recordShadowRoots(child, frames, roots, object_group, current_frame_id, outer_backend_node_id) + content_document = node.get("contentDocument") + if _isObjectMap(content_document): + raw_content_frame_id = content_document.get("frameId") + self._recordShadowRoots( + content_document, + frames, + roots, + object_group, + raw_content_frame_id if isinstance(raw_content_frame_id, str) else current_frame_id, + outer_backend_node_id, + ) + + def _callFunctionOnParamsForRoute(self, params: Mapping[str, Any], target_id: str, session_id: str | None) -> dict[str, Any]: + call_params = dict(params) + if call_params.get("executionContextId") is not None or call_params.get("uniqueContextId") is not None or call_params.get("objectId") is not None: + return call_params + context = self._waitForExecutionContextMatching( + lambda current_context: current_context.get("targetId") == target_id + and (session_id is None or current_context.get("sessionId") == session_id), + session_id or target_id, + ) + call_params["executionContextId"] = context["id"] + return call_params + + def _findExecutionContext(self, target_id: str, session_id: str | None, frame_id: str, selector: Mapping[str, str]) -> dict[str, Any] | None: + for context in self.contexts.values(): + if context.get("targetId") != target_id or context.get("frameId") != frame_id: + continue + if session_id is not None and context.get("sessionId") != session_id: + continue + if selector.get("world") == "piercer" and context.get("world") == "piercer": + return context + if selector.get("world") == "isolated" and context.get("name") == selector.get("worldName"): + return context + if selector.get("world") == "main" and context.get("world") == "main": + return context + if context.get("world") == selector.get("world"): + return context + return None + + def _waitForExecutionContextMatching( + self, + matches: Callable[[dict[str, Any]], bool], + waiter_key: str | None, + timeout_ms: int | None = None, + ) -> dict[str, Any]: + effective_timeout_ms = timeout_ms if timeout_ms is not None else self.config.loopback_execution_context_timeout_ms + with self._lock: + for context in self.contexts.values(): + if matches(context): + return context + if not waiter_key: + raise RuntimeError("Cannot wait for a Runtime execution context without a route.") + event = threading.Event() + result: dict[str, Any] = {} + self._execution_context_waiters.setdefault(waiter_key, []).append((event, result, matches)) + if not event.wait(effective_timeout_ms / 1000): + with self._lock: + waiters = self._execution_context_waiters.get(waiter_key, []) + remaining_waiters = [item for item in waiters if item[0] is not event] + if remaining_waiters: + self._execution_context_waiters[waiter_key] = remaining_waiters + else: + self._execution_context_waiters.pop(waiter_key, None) + raise RuntimeError(f"Timed out waiting for Runtime.executionContextCreated for route {waiter_key}.") + error = result.get("error") + if isinstance(error, BaseException): + raise error + return result["context"] + + def _resolveTargetId(self, params: Mapping[str, Any]) -> str | None: + explicit_target_id = self.upstream.resolveTargetId(dict(params)) + if explicit_target_id: + return explicit_target_id + target_infos = self.upstream.getTargets() + for raw_target_info in target_infos: + self._recordTarget(raw_target_info) + for raw_target_info in target_infos: + if raw_target_info.get("type") == "page": + url = raw_target_info.get("url") + if isinstance(url, str) and not url.startswith("devtools://"): + target_id = raw_target_info.get("targetId") + return target_id if isinstance(target_id, str) else None + return None + + def _contextKey(self, target_id: str, session_id: str | None, context_id: int, unique_id: object) -> str: + return unique_id if isinstance(unique_id, str) else f"{session_id or target_id}:{context_id}" + + +def _protocol_result(value: object) -> ProtocolResult: + if not isinstance(value, Mapping): + return {} + return {str(key): raw_value for key, raw_value in value.items()} diff --git a/python/modcdp/router/__init__.py b/python/modcdp/router/__init__.py index ef53de58..b7323efa 100644 --- a/python/modcdp/router/__init__.py +++ b/python/modcdp/router/__init__.py @@ -1 +1,5 @@ -from .AutoSessionRouter import AutoSessionRouter +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/router/AutoSessionRouter.ts +# - ./go/modcdp/router/AutoSessionRouter.go +from .AutoSessionRouter import AutoSessionRouter, RouterConfig diff --git a/python/modcdp/translate/__init__.py b/python/modcdp/translate/__init__.py index b806683a..7ea08e23 100644 --- a/python/modcdp/translate/__init__.py +++ b/python/modcdp/translate/__init__.py @@ -1 +1,5 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/translate/translate.ts +# - ./go/modcdp/translate/translate.go from .translate import * diff --git a/python/modcdp/translate/translate.py b/python/modcdp/translate/translate.py index 648ba165..bfd7afbf 100644 --- a/python/modcdp/translate/translate.py +++ b/python/modcdp/translate/translate.py @@ -1,13 +1,14 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/translate/translate.ts +# - ./go/modcdp/translate/translate.go """Pure ModCDP <-> CDP translation helpers for the Python client.""" import json -import time -from typing import cast from ..types.modcdp import ( + ModCDPBindingPayload, ModCDPRoutes, - JsonObject, - JsonValue, ProtocolParams, ProtocolPayload, ProtocolResult, @@ -15,6 +16,7 @@ TranslatedCommand, TranslatedStep, UnwrappedModCDPEvent, + _isObjectMap, ) UPSTREAM_EVENT_BINDING_NAME = "__ModCDP_event_from_upstream__" @@ -27,7 +29,7 @@ } -def route_for(method: str, routes: ModCDPRoutes) -> str: +def route_for(method: str, routes: ModCDPRoutes | None = None) -> str: routes = routes or {} if method in routes: return routes[method] @@ -47,13 +49,6 @@ def route_for(method: str, routes: ModCDPRoutes) -> str: return "direct_cdp" -def _required_string(params: ProtocolParams, name: str) -> str: - value = params.get(name) - if not isinstance(value, str) or not value: - raise TypeError(f"{name} must be a non-empty string") - return value - - def _optional_string(params: ProtocolParams, name: str) -> str | None: value = params.get(name) if value is None: @@ -63,8 +58,8 @@ def _optional_string(params: ProtocolParams, name: str) -> str | None: return value -def _object_or_empty(value: JsonValue | None) -> JsonObject: - return value if isinstance(value, dict) else {} +def _object_or_empty(value: object | None) -> dict[str, object]: + return value if _isObjectMap(value) else {} def _call_function_params(function_declaration: str) -> RuntimeCallFunctionOnParams: @@ -75,83 +70,7 @@ def _call_function_params(function_declaration: str) -> RuntimeCallFunctionOnPar } -def _wrap_modcdp_evaluate( - params: ProtocolParams, - session_id: str, - target_session_id: str | None = None, -) -> RuntimeCallFunctionOnParams: - expression = _required_string(params, "expression") - user_params = params.get("params", {}) - cdp_session_id = target_session_id or _optional_string(params, "cdpSessionId") or session_id - return _call_function_params( - "async function() {\n" - f" const params = {json.dumps(user_params)};\n" - f" const cdp = globalThis.ModCDP.attachToSession({json.dumps(cdp_session_id)});\n" - " const ModCDP = globalThis.ModCDP;\n" - " const chrome = globalThis.chrome;\n" - f" const value = ({expression});\n" - " return typeof value === 'function' ? await value(params) : value;\n" - "}" - ) - - -def _wrap_modcdp_add_custom_command(params: ProtocolParams) -> RuntimeCallFunctionOnParams: - name = _required_string(params, "name") - expression = _required_string(params, "expression") - return _call_function_params( - "function() {\n" - " return globalThis.ModCDP.addCustomCommand({\n" - f" name: {json.dumps(name)},\n" - " params_schema: null,\n" - " result_schema: null,\n" - f" expression: {json.dumps(expression)},\n" - " handler: async (params, cdpSessionId, method) => {\n" - " const cdp = globalThis.ModCDP.attachToSession(cdpSessionId);\n" - " const ModCDP = globalThis.ModCDP;\n" - " const chrome = globalThis.chrome;\n" - f" const handler = ({expression});\n" - " return await handler(params || {}, method);\n" - " },\n" - " });\n" - "}" - ) - - -def _wrap_modcdp_add_custom_event(params: ProtocolParams) -> RuntimeCallFunctionOnParams: - name = _required_string(params, "name") - return _call_function_params( - "function() {\n" - " return globalThis.ModCDP.addCustomEvent({\n" - f" name: {json.dumps(name)},\n" - " event_schema: null,\n" - " });\n" - "}" - ) - - -def _wrap_modcdp_add_middleware(params: ProtocolParams) -> RuntimeCallFunctionOnParams: - phase = _required_string(params, "phase") - expression = _required_string(params, "expression") - name = _optional_string(params, "name") or "*" - return _call_function_params( - "function() {\n" - " return globalThis.ModCDP.addMiddleware({\n" - f" name: {json.dumps(name)},\n" - f" phase: {json.dumps(phase)},\n" - f" expression: {json.dumps(expression)},\n" - " handler: async (payload, next, context = {}) => {\n" - " const cdp = globalThis.ModCDP.attachToSession(context.cdpSessionId ?? null);\n" - " const ModCDP = globalThis.ModCDP;\n" - " const chrome = globalThis.chrome;\n" - f" const middleware = ({expression});\n" - " return await middleware(payload, next, context);\n" - " },\n" - " });\n" - "}" - ) - - -def _wrap_custom_command(method: str, params: ProtocolParams, session_id: str) -> RuntimeCallFunctionOnParams: +def _wrap_custom_command(method: str, params: ProtocolParams, session_id: str | None) -> RuntimeCallFunctionOnParams: runtime_params = _call_function_params( "async function(method, paramsJson, cdpSessionId) { " "return JSON.stringify(await globalThis.ModCDP.handleCommand(method, JSON.parse(paramsJson), cdpSessionId)); " @@ -164,31 +83,15 @@ def _wrap_custom_command(method: str, params: ProtocolParams, session_id: str) - def _wrap_service_worker_command( method: str, params: ProtocolParams, - session_id: str, - target_session_id: str | None = None, + cdp_session_id: str | None = None, ) -> list[TranslatedStep]: - if method == "Mod.ping" and "sent_at" not in params: - params = {**params, "sent_at": int(time.time() * 1000)} - - if method == "Mod.addCustomEvent": - return [ - { - "method": "Runtime.callFunctionOn", - "params": _wrap_modcdp_add_custom_event(params), - "unwrap": "runtime", - }, - ] - unwrap = "runtime" - if method == "Mod.evaluate": - runtime_params = _wrap_modcdp_evaluate(params, session_id, target_session_id) - elif method == "Mod.addCustomCommand": - runtime_params = _wrap_modcdp_add_custom_command(params) - elif method == "Mod.addMiddleware": - runtime_params = _wrap_modcdp_add_middleware(params) - else: - runtime_params = _wrap_custom_command(method, params, target_session_id or _optional_string(params, "cdpSessionId") or session_id) - unwrap = "runtime_json" - return [{"method": "Runtime.callFunctionOn", "params": runtime_params, "unwrap": unwrap}] + return [ + TranslatedStep( + method="Runtime.callFunctionOn", + params=_wrap_custom_command(method, params, _optional_string(params, "cdpSessionId") or cdp_session_id), + unwrap="runtime_json", + ) + ] def wrap_command_if_needed( @@ -197,27 +100,24 @@ def wrap_command_if_needed( *, routes: ModCDPRoutes | None = None, cdp_session_id: str | None = None, - target_cdp_session_id: str | None = None, ) -> TranslatedCommand: params = params or {} route = route_for(method, routes or DEFAULT_CLIENT_ROUTES) if route == "direct_cdp": - step: TranslatedStep = {"method": method, "params": params} - if target_cdp_session_id: - step["sessionId"] = target_cdp_session_id - return {"route": route, "target": "direct_cdp", "steps": [step]} + step = TranslatedStep(method=method, params=params) + if cdp_session_id: + step.sessionId = cdp_session_id + return TranslatedCommand(route=route, target="direct_cdp", steps=[step]) if route == "service_worker": - if cdp_session_id is None: - raise RuntimeError(f"service_worker route requires a CDP session id for {method}") - return { - "route": route, - "target": "service_worker", - "steps": _wrap_service_worker_command(method, params, cdp_session_id, target_cdp_session_id), - } + return TranslatedCommand( + route=route, + target="service_worker", + steps=_wrap_service_worker_command(method, params, cdp_session_id), + ) raise RuntimeError(f"Unsupported client route '{route}' for {method}") -def _unwrap_evaluate_response(result: ProtocolResult) -> JsonValue: +def _unwrap_evaluate_response(result: ProtocolResult) -> object: if result.get("exceptionDetails"): ex = _object_or_empty(result.get("exceptionDetails")) exception = _object_or_empty(ex.get("exception")) @@ -235,10 +135,10 @@ def _unwrap_evaluate_response(result: ProtocolResult) -> JsonValue: return inner.get("value") -def unwrap_response_if_needed(result: ProtocolResult, unwrap: str | None = None) -> JsonValue: +def unwrap_response_if_needed(result: ProtocolResult, unwrap: str | None = None) -> object: if unwrap == "runtime_json": value = _unwrap_evaluate_response(result) - return cast(JsonValue, json.loads(value)) if isinstance(value, str) else value + return json.loads(value) if isinstance(value, str) else value return _unwrap_evaluate_response(result) if unwrap == "runtime" else (result or {}) @@ -260,9 +160,9 @@ def unwrap_event_if_needed( parsed: object = json.loads(raw_payload) except json.JSONDecodeError: return None - if not isinstance(parsed, dict): + if not _isObjectMap(parsed): return None - payload = cast(ProtocolPayload, parsed) + payload: ProtocolPayload = parsed is_upstream_event_binding = binding_name == UPSTREAM_EVENT_BINDING_NAME is_custom_event_binding = binding_name == CUSTOM_EVENT_BINDING_NAME if not is_upstream_event_binding and not is_custom_event_binding: @@ -275,9 +175,18 @@ def unwrap_event_if_needed( resolved_event = payload_event if resolved_event == UPSTREAM_EVENT_BINDING_NAME or resolved_event == CUSTOM_EVENT_BINDING_NAME: return None - data_value = payload["data"] if "data" in payload else payload - data: ProtocolPayload = data_value if isinstance(data_value, dict) else {"value": data_value} + data = payload["data"] if "data" in payload else payload raw_source_session_id = payload.get("cdpSessionId") source_session_id = raw_source_session_id if isinstance(raw_source_session_id, str) else session_id - unwrapped: UnwrappedModCDPEvent = {"event": resolved_event, "data": data, "sessionId": source_session_id} - return unwrapped + return UnwrappedModCDPEvent(event=resolved_event, data=data, sessionId=source_session_id) + + +def encode_binding_payload(payload: ModCDPBindingPayload) -> str: + return json.dumps( + { + "event": payload["event"], + "data": payload.get("data"), + "cdpSessionId": payload.get("cdpSessionId"), + }, + separators=(",", ":"), + ) diff --git a/python/modcdp/transport/NativeMessagingUpstreamTransport.py b/python/modcdp/transport/NativeMessagingUpstreamTransport.py deleted file mode 100644 index a378463b..00000000 --- a/python/modcdp/transport/NativeMessagingUpstreamTransport.py +++ /dev/null @@ -1,353 +0,0 @@ -from __future__ import annotations - -import json -import socket -import struct -import subprocess -import sys -import threading -import time -from pathlib import Path -from collections.abc import Mapping -from typing import Any, cast - -from ..injector.ExtensionInjector import DEFAULT_MODCDP_EXTENSION_ID -from ..transport.UpstreamTransport import UpstreamTransport - - -DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME = "com.modcdp.bridge" -DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS = 10_000 - - -class NativeMessagingUpstreamTransport(UpstreamTransport): - mode = "nativemessaging" - endpoint_kind = "modcdp_server" - - def __init__( - self, - options: Mapping[str, Any] | None = None, - ) -> None: - super().__init__() - normalized_options = dict(options or {}) - self.upstream_nativemessaging_manifest = cast(str | None, normalized_options.get("upstream_nativemessaging_manifest")) - self.upstream_nativemessaging_manifests = list(cast(list[str], normalized_options.get("upstream_nativemessaging_manifests") or [])) - self.include_default_manifest_paths = self.upstream_nativemessaging_manifest is None and not self.upstream_nativemessaging_manifests - self.upstream_nativemessaging_host_name = str(normalized_options.get("upstream_nativemessaging_host_name") or DEFAULT_UPSTREAM_NATIVEMESSAGING_HOST_NAME) - self.extension_id = str(normalized_options.get("injector_extension_id") or DEFAULT_MODCDP_EXTENSION_ID) - self.wait_timeout_ms = int(normalized_options.get("upstream_nativemessaging_wait_timeout_ms") or DEFAULT_UPSTREAM_NATIVEMESSAGING_WAIT_TIMEOUT_MS) - self.socket: socket.socket | None = None - self.server: socket.socket | None = None - self.peer_seen = threading.Event() - self._peer_condition = threading.Condition() - self._close_generation = 0 - self.bound_port: int | None = None - self.cdp_url: str | None = None - self.user_data_dir: str | None = None - self.url = "" - - def update(self, config: dict[str, Any] | None = None) -> "NativeMessagingUpstreamTransport": - config = config or {} - should_install_native_host = False - if "upstream_nativemessaging_manifest" in config: - self.upstream_nativemessaging_manifest = config.get("upstream_nativemessaging_manifest") - should_install_native_host = True - if "upstream_nativemessaging_manifests" in config: - self.upstream_nativemessaging_manifests = list(config.get("upstream_nativemessaging_manifests") or []) - should_install_native_host = True - self.include_default_manifest_paths = self.upstream_nativemessaging_manifest is None and not self.upstream_nativemessaging_manifests - upstream_nativemessaging_host_name = config.get("upstream_nativemessaging_host_name") - if isinstance(upstream_nativemessaging_host_name, str) and upstream_nativemessaging_host_name: - self.upstream_nativemessaging_host_name = upstream_nativemessaging_host_name - should_install_native_host = True - wait_timeout_ms = config.get("upstream_nativemessaging_wait_timeout_ms") - if isinstance(wait_timeout_ms, int | float): - self.wait_timeout_ms = int(wait_timeout_ms) - extension_id = config.get("injector_extension_id") - if isinstance(extension_id, str) and extension_id: - self.extension_id = extension_id - should_install_native_host = True - user_data_dir = config.get("user_data_dir") - if isinstance(user_data_dir, str) and user_data_dir and user_data_dir != self.user_data_dir: - self._set_profile_manifest_paths(user_data_dir) - self.user_data_dir = user_data_dir - should_install_native_host = True - if should_install_native_host and self.bound_port is not None: - self._install_native_host(self.bound_port) - cdp_url = config.get("cdp_url") - if isinstance(cdp_url, str) and cdp_url: - self.cdp_url = cdp_url - return self - - def getServerConfig(self) -> dict[str, Any]: - return {"server_loopback_cdp_url": self.cdp_url} if self.cdp_url else {} - - def getInjectorConfig(self) -> dict[str, Any]: - return {"upstream_nativemessaging_host_name": self.upstream_nativemessaging_host_name} - - def connect(self) -> None: - with self._peer_condition: - self._close_generation += 1 - self.peer_seen.clear() - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.bind(("127.0.0.1", 0)) - server.listen(1) - self.server = server - self.bound_port = int(server.getsockname()[1]) - self.url = f"native://{self.upstream_nativemessaging_host_name}@127.0.0.1:{self.bound_port}" - self._install_native_host(self.bound_port) - threading.Thread(target=self._accept_loop, daemon=True).start() - - def send(self, message: dict[str, Any]) -> None: - if self.socket is None: - raise RuntimeError(f"No native messaging peer is connected for {self.upstream_nativemessaging_host_name}.") - _write_length_prefixed_json(self.socket, message) - - def waitForPeer(self) -> None: - deadline = time.monotonic() + self.wait_timeout_ms / 1000 - with self._peer_condition: - close_generation = self._close_generation - while self.socket is None: - if close_generation != self._close_generation: - raise RuntimeError( - f"Native messaging transport for {self.upstream_nativemessaging_host_name} closed before a peer connected." - ) - remaining = deadline - time.monotonic() - if remaining <= 0: - raise RuntimeError( - f"Timed out waiting {self.wait_timeout_ms}ms for native messaging host {self.upstream_nativemessaging_host_name}." - ) - self._peer_condition.wait(remaining) - - def close(self) -> None: - for sock in (self.socket, self.server): - try: - if sock is not None: - sock.close() - except Exception: - pass - self.socket = None - self.server = None - self.peer_seen.clear() - with self._peer_condition: - self._close_generation += 1 - self._peer_condition.notify_all() - - def _accept_loop(self) -> None: - server = self.server - if server is None: - return - while self.server is server: - try: - conn, _ = server.accept() - except OSError: - return - except Exception as error: - self._emit_close(error if isinstance(error, Exception) else Exception(str(error))) - return - if self.socket is not None: - try: - self.socket.close() - except Exception: - pass - with self._peer_condition: - self.socket = conn - self.peer_seen.set() - self._peer_condition.notify_all() - threading.Thread(target=self._read_loop, args=(conn,), daemon=True).start() - - def _read_loop(self, conn: socket.socket) -> None: - try: - for message in _read_length_prefixed_json_messages(conn): - if message.get("type") == "modcdp.native.hello": - continue - self._emit_recv(message) - except Exception as error: - if self.socket is conn: - with self._peer_condition: - if self.socket is conn: - self.socket = None - self.peer_seen.clear() - self._peer_condition.notify_all() - self._emit_close(error if isinstance(error, Exception) else Exception(str(error))) - finally: - try: - conn.close() - except Exception: - pass - - def _install_native_host(self, port: int) -> None: - host_dir = Path.home() / ".modcdp" / "native-messaging" - host_dir.mkdir(parents=True, exist_ok=True) - config_path = host_dir / f"{self.upstream_nativemessaging_host_name}.config.json" - host_script_path = host_dir / f"{self.upstream_nativemessaging_host_name}.py" - host_executable_path = host_dir / f"{self.upstream_nativemessaging_host_name}{'.cmd' if sys.platform.startswith('win') else '.sh'}" - config_path.write_text(json.dumps({"host": "127.0.0.1", "port": port}, indent=2) + "\n") - host_script_path.write_text(_native_host_script(str(config_path))) - host_executable_path.write_text(_native_host_wrapper(sys.executable, str(host_script_path))) - host_executable_path.chmod(0o755) - - upstream_nativemessaging_manifests = [] - if self.upstream_nativemessaging_manifest: - upstream_nativemessaging_manifests.append(self.upstream_nativemessaging_manifest) - upstream_nativemessaging_manifests.extend(self.upstream_nativemessaging_manifests) - if self.include_default_manifest_paths: - upstream_nativemessaging_manifests.extend(_default_native_messaging_manifest_paths(self.upstream_nativemessaging_host_name)) - manifest = { - "name": self.upstream_nativemessaging_host_name, - "description": "ModCDP Native Messaging bridge", - "path": str(host_executable_path), - "type": "stdio", - "allowed_origins": [f"chrome-extension://{self.extension_id}/"], - } - manifest_text = json.dumps(manifest, indent=2) + "\n" - for upstream_nativemessaging_manifest in upstream_nativemessaging_manifests: - path = Path(upstream_nativemessaging_manifest) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(manifest_text) - if sys.platform.startswith("win") and upstream_nativemessaging_manifests: - _register_windows_native_messaging_host(self.upstream_nativemessaging_host_name, upstream_nativemessaging_manifests[0]) - - def _set_profile_manifest_paths(self, user_data_dir: str) -> None: - previous_profile_manifest_paths = ( - [ - str(Path(self.user_data_dir) / "NativeMessagingHosts" / f"{self.upstream_nativemessaging_host_name}.json"), - str(Path(self.user_data_dir) / "Default" / "NativeMessagingHosts" / f"{self.upstream_nativemessaging_host_name}.json"), - ] - if self.user_data_dir - else [] - ) - profile_manifest_paths = [ - str(Path(user_data_dir) / "NativeMessagingHosts" / f"{self.upstream_nativemessaging_host_name}.json"), - str(Path(user_data_dir) / "Default" / "NativeMessagingHosts" / f"{self.upstream_nativemessaging_host_name}.json"), - ] - self.upstream_nativemessaging_manifests = [ - *profile_manifest_paths, - *[ - upstream_nativemessaging_manifest - for upstream_nativemessaging_manifest in self.upstream_nativemessaging_manifests - if upstream_nativemessaging_manifest not in previous_profile_manifest_paths and upstream_nativemessaging_manifest not in profile_manifest_paths - ], - ] - - -def _default_native_messaging_manifest_paths(upstream_nativemessaging_host_name: str) -> list[str]: - home = str(Path.home()) - if sys.platform == "darwin": - return [ - f"{home}/Library/Application Support/Google/Chrome/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/Library/Application Support/Google/Chrome Canary/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/Library/Application Support/Google/ChromeForTesting/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/Library/Application Support/Google/Chrome for Testing/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/Library/Application Support/Google/Chrome SxS/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/Library/Application Support/Chromium/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - ] - if sys.platform.startswith("linux"): - return [ - f"{home}/.config/google-chrome/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/.config/google-chrome-for-testing/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/.config/chromium/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - f"{home}/.config/chromium-browser/NativeMessagingHosts/{upstream_nativemessaging_host_name}.json", - ] - if sys.platform.startswith("win"): - return [str(Path.home() / ".modcdp" / "native-messaging" / f"{upstream_nativemessaging_host_name}.json")] - raise RuntimeError("upstream_nativemessaging_manifest is required on this platform.") - - -def _native_host_wrapper(python_path: str, host_script_path: str) -> str: - if sys.platform.startswith("win"): - return f"@echo off\r\n{_cmd_quote(python_path)} {_cmd_quote(host_script_path)}\r\n" - return f"#!/bin/sh\nexec {json.dumps(python_path)} {json.dumps(host_script_path)}\n" - - -def _cmd_quote(value: str) -> str: - return f'"{value.replace(chr(34), chr(34) + chr(34))}"' - - -def _register_windows_native_messaging_host(upstream_nativemessaging_host_name: str, upstream_nativemessaging_manifest: str) -> None: - subprocess.run( - [ - "reg", - "add", - rf"HKCU\Software\Google\Chrome\NativeMessagingHosts\{upstream_nativemessaging_host_name}", - "/ve", - "/t", - "REG_SZ", - "/d", - upstream_nativemessaging_manifest, - "/f", - ], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - - -def _write_length_prefixed_json(sock: socket.socket, message: dict[str, Any]) -> None: - body = json.dumps(message).encode() - sock.sendall(struct.pack(" bytes: - chunks: list[bytes] = [] - remaining = length - while remaining > 0: - chunk = sock.recv(remaining) - if not chunk: - raise RuntimeError("native messaging socket closed") - chunks.append(chunk) - remaining -= len(chunk) - return b"".join(chunks) - - -def _read_length_prefixed_json_messages(sock: socket.socket): - while True: - header = _read_exact(sock, 4) - length = struct.unpack(" str: - return f""" -import json -import socket -import struct -import sys -import threading - -config = json.loads(open({config_path!r}, "r", encoding="utf-8").read()) -sock = socket.create_connection((config["host"], config["port"])) - -def write_native(message): - body = json.dumps(message).encode() - sys.stdout.buffer.write(struct.pack(" 0: - chunk = source.read(remaining) if hasattr(source, "read") else source.recv(remaining) - if not chunk: - raise SystemExit(0) - chunks.append(chunk) - remaining -= len(chunk) - return b"".join(chunks) - -def read_messages(source, on_message): - while True: - header = read_exact(source, 4) - length = struct.unpack(" None: - super().__init__() - normalized_options = dict(options or {}) - normalized_url, normalized_nats_subject_prefix = _normalize_nats_url( - cast(str | None, normalized_options.get("upstream_nats_url")) or DEFAULT_UPSTREAM_NATS_URL, - cast(str | None, normalized_options.get("upstream_nats_subject_prefix")), - ) - self.url = normalized_url - self.upstream_nats_subject_prefix = normalized_nats_subject_prefix - self.upstream_nats_role = str(normalized_options.get("upstream_nats_role") or "client") - self.wait_timeout_ms = int(normalized_options.get("upstream_nats_wait_timeout_ms") or DEFAULT_UPSTREAM_NATS_WAIT_TIMEOUT_MS) - self.socket: WebSocket | socket.socket | None = None - self.connected = False - self.closed = False - self.peer_seen = threading.Event() - self._peer_condition = threading.Condition() - self._close_generation = 0 - self.write_lock = threading.Lock() - self.buffer = "" - - def update(self, config: dict[str, Any] | None = None) -> "NatsUpstreamTransport": - config = config or {} - upstream_nats_url = config.get("upstream_nats_url") - upstream_nats_subject_prefix = config.get("upstream_nats_subject_prefix") - if isinstance(upstream_nats_url, str) and upstream_nats_url or isinstance(upstream_nats_subject_prefix, str) and upstream_nats_subject_prefix: - current_url = self.url or DEFAULT_UPSTREAM_NATS_URL - self.url, self.upstream_nats_subject_prefix = _normalize_nats_url( - upstream_nats_url if isinstance(upstream_nats_url, str) and upstream_nats_url else current_url, - upstream_nats_subject_prefix if isinstance(upstream_nats_subject_prefix, str) and upstream_nats_subject_prefix else self.upstream_nats_subject_prefix, - ) - upstream_nats_role = config.get("upstream_nats_role") - if upstream_nats_role in ("client", "browser"): - self.upstream_nats_role = str(upstream_nats_role) - wait_timeout_ms = config.get("upstream_nats_wait_timeout_ms") - if isinstance(wait_timeout_ms, int | float): - self.wait_timeout_ms = int(wait_timeout_ms) - return self - - def getInjectorConfig(self) -> dict[str, Any]: - return {"upstream_nats_url": self.url, "upstream_nats_subject_prefix": self.upstream_nats_subject_prefix} - - def connect(self) -> None: - if self.connected: - return - self.closed = False - with self._peer_condition: - self._close_generation += 1 - close_generation = self._close_generation - self.peer_seen.clear() - parsed = urlparse(self.url) - if parsed.scheme in ("ws", "wss"): - ws = WebSocket() - ws.connect(self.url) - self.socket = ws - self._write_protocol(f"CONNECT {json.dumps(_connect_options())}\r\nPING\r\n") - threading.Thread(target=self._read_websocket_loop, args=(ws, close_generation), daemon=True).start() - elif parsed.scheme in ("nats", "tls"): - port = parsed.port or 4222 - host = cast(str, parsed.hostname or "127.0.0.1") - raw_socket = socket.create_connection((host, port)) - tcp_socket = ssl.create_default_context().wrap_socket(raw_socket, server_hostname=host) if parsed.scheme == "tls" else raw_socket - self.socket = tcp_socket - self._write_protocol(f"CONNECT {json.dumps(_connect_options())}\r\nPING\r\n") - threading.Thread(target=self._read_tcp_loop, args=(tcp_socket, close_generation), daemon=True).start() - else: - raise RuntimeError(f"upstream.upstream_mode=nats requires ws://, wss://, nats://, or tls:// URL, got {self.url}.") - self.connected = True - self._subscribe() - self._publish(self._outgoing_subject(), {"type": "modcdp.nats.hello", "role": self.upstream_nats_role, "version": 1}) - - def send(self, message: dict[str, Any]) -> None: - if not self.connected or self.socket is None: - raise RuntimeError("NATS transport is not connected.") - self._publish(self._outgoing_subject(), {"type": "modcdp.nats.message", "message": message}) - - def waitForPeer(self) -> None: - deadline = time.monotonic() + self.wait_timeout_ms / 1000 - with self._peer_condition: - close_generation = self._close_generation - while not self.peer_seen.is_set(): - if close_generation != self._close_generation: - raise RuntimeError(f"NATS transport for {self.upstream_nats_subject_prefix} closed before a peer connected.") - remaining = deadline - time.monotonic() - if remaining <= 0: - raise RuntimeError(f"Timed out waiting {self.wait_timeout_ms}ms for NATS ModCDP peer.") - self._peer_condition.wait(remaining) - - def close(self) -> None: - self.closed = True - try: - if isinstance(self.socket, WebSocket): - self.socket.close() - elif self.socket is not None: - self.socket.close() - except Exception: - pass - self.socket = None - self.connected = False - self.peer_seen.clear() - with self._peer_condition: - self._close_generation += 1 - self._peer_condition.notify_all() - - def _subscribe(self) -> None: - self._write_protocol(f"SUB {self._incoming_subject()} 1\r\n") - - def _publish(self, subject: str, message: dict[str, Any]) -> None: - body = json.dumps(message, separators=(",", ":")) - self._write_protocol(f"PUB {subject} {len(body.encode())}\r\n{body}\r\n") - - def _write_protocol(self, data: str) -> None: - if self.socket is None: - raise RuntimeError("NATS transport is not connected.") - with self.write_lock: - if isinstance(self.socket, WebSocket): - self.socket.send(data) - else: - self.socket.sendall(data.encode()) - - def _incoming_subject(self) -> str: - return f"{self.upstream_nats_subject_prefix}.{'browser_to_client' if self.upstream_nats_role == 'client' else 'client_to_browser'}" - - def _outgoing_subject(self) -> str: - return f"{self.upstream_nats_subject_prefix}.{'client_to_browser' if self.upstream_nats_role == 'client' else 'browser_to_client'}" - - def _generation_closed(self, close_generation: int) -> bool: - with self._peer_condition: - return self.closed or close_generation != self._close_generation - - def _read_websocket_loop(self, ws: WebSocket, close_generation: int) -> None: - try: - while not self._generation_closed(close_generation): - data = ws.recv() - if isinstance(data, bytes): - self.buffer += data.decode() - else: - self.buffer += str(data) - self.buffer = self._consume_protocol(self.buffer) - except Exception as error: - if not self._generation_closed(close_generation): - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) - - def _read_tcp_loop(self, tcp_socket: socket.socket, close_generation: int) -> None: - try: - while not self._generation_closed(close_generation): - chunk = tcp_socket.recv(65536) - if not chunk: - break - self.buffer += chunk.decode() - self.buffer = self._consume_protocol(self.buffer) - except Exception as error: - if not self._generation_closed(close_generation): - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) - - def _consume_protocol(self, buffer: str) -> str: - while True: - line_end = buffer.find("\r\n") - if line_end < 0: - return buffer - line = buffer[:line_end] - upper = line.upper() - if upper.startswith("MSG "): - parts = line.split() - size = int(parts[-1]) if parts and parts[-1].isdigit() else -1 - payload_start = line_end + 2 - payload_end = payload_start + size - if size < 0 or len(buffer) < payload_end + 2: - return buffer - payload = buffer[payload_start:payload_end] - buffer = buffer[payload_end + 2 :] - self._handle_payload(payload) - continue - buffer = buffer[line_end + 2 :] - if upper == "PING": - self._write_protocol("PONG\r\n") - elif upper.startswith("-ERR"): - self._emit_close(RuntimeError(f"NATS error: {line}")) - - def _handle_payload(self, payload: str) -> None: - try: - parsed = json.loads(payload) - except Exception: - return - if isinstance(parsed, dict) and parsed.get("type") == "modcdp.nats.hello": - with self._peer_condition: - self.peer_seen.set() - self._peer_condition.notify_all() - return - message = parsed.get("message") if isinstance(parsed, dict) and parsed.get("type") == "modcdp.nats.message" else parsed - if isinstance(message, dict): - self._emit_recv(message) - - -def _connect_options() -> dict[str, Any]: - return {"verbose": False, "pedantic": False, "lang": "modcdp", "version": "1", "protocol": 1} - - -def _normalize_nats_url(url: str, upstream_nats_subject_prefix: str | None = None) -> tuple[str, str]: - parsed = urlparse(url) - query = parse_qs(parsed.query) - subject = upstream_nats_subject_prefix or (query.get("upstream_nats_subject_prefix") or [None])[0] - query.pop("upstream_nats_subject_prefix", None) - normalized_query = urlencode(query, doseq=True) - normalized_path = parsed.path or ("/" if parsed.scheme in ("ws", "wss") else "") - normalized_url = urlunparse((parsed.scheme, parsed.netloc, normalized_path, parsed.params, normalized_query, parsed.fragment)) - return normalized_url, _sanitize_nats_subject_prefix(subject or DEFAULT_UPSTREAM_NATS_SUBJECT_PREFIX) - - -def _sanitize_nats_subject_prefix(value: str) -> str: - subject = value.strip() - if not subject or any(char.isspace() or char in "*>" for char in subject): - raise ValueError(f"Invalid NATS subject prefix {value}") - return subject diff --git a/python/modcdp/transport/PipeUpstreamTransport.py b/python/modcdp/transport/PipeUpstreamTransport.py deleted file mode 100644 index c9497a1c..00000000 --- a/python/modcdp/transport/PipeUpstreamTransport.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import annotations - -import json -import threading -from typing import Any - -from ..transport.UpstreamTransport import UpstreamTransport - - -class PipeUpstreamTransport(UpstreamTransport): - mode = "pipe" - endpoint_kind = "raw_cdp" - - def __init__(self, options: dict[str, Any] | None = None) -> None: - super().__init__() - options = options or {} - self.pipe_read = options.get("pipe_read") - self.pipe_write = options.get("pipe_write") - self.url = str(options.get("cdp_url") or "pipe://unknown") - self._thread: threading.Thread | None = None - self._connected = False - self._closed = False - - def update(self, config: dict[str, Any] | None = None) -> "PipeUpstreamTransport": - config = config or {} - self.pipe_read = config.get("pipe_read") or self.pipe_read - self.pipe_write = config.get("pipe_write") or self.pipe_write - self.url = str(config.get("cdp_url") or self.url) - return self - - def getLauncherConfig(self) -> dict[str, Any]: - return {"remote_debugging": "pipe"} - - def connect(self) -> None: - if self.pipe_read is None or self.pipe_write is None: - raise RuntimeError("upstream.upstream_mode=pipe requires launcher-provided remote-debugging pipe handles.") - if self._connected: - return - self._connected = True - self._closed = False - self._thread = threading.Thread(target=self._read_loop, daemon=True) - self._thread.start() - - def send(self, message: dict[str, Any]) -> None: - if not self._connected or self.pipe_write is None: - raise RuntimeError("CDP pipe is not connected.") - self.pipe_write.write(json.dumps(message).encode() + b"\0") - self.pipe_write.flush() - - def close(self) -> None: - self._closed = True - for pipe in (self.pipe_read, self.pipe_write): - try: - if pipe is not None: - pipe.close() - except Exception: - pass - self._connected = False - - def _read_loop(self) -> None: - buffer = b"" - try: - while not self._closed and self.pipe_read is not None: - chunk = self.pipe_read.read(1) - if not chunk: - if not self._closed: - self._handle_close(RuntimeError("CDP pipe closed")) - break - buffer += chunk - if b"\0" not in buffer: - continue - raw, buffer = buffer.split(b"\0", 1) - if raw: - self._parse_and_emit_recv(raw) - except Exception as error: - if not self._closed: - self._handle_close(error if isinstance(error, Exception) else Exception(str(error))) - - def _handle_close(self, error: Exception) -> None: - self._connected = False - self._emit_close(error) diff --git a/python/modcdp/transport/ReverseWebSocketUpstreamTransport.py b/python/modcdp/transport/ReverseWebSocketUpstreamTransport.py deleted file mode 100644 index 0fb940b6..00000000 --- a/python/modcdp/transport/ReverseWebSocketUpstreamTransport.py +++ /dev/null @@ -1,250 +0,0 @@ -from __future__ import annotations - -import base64 -import hashlib -import json -import socket -import struct -import threading -import time -from typing import Any -from urllib.parse import urlparse - -from ..transport.UpstreamTransport import UpstreamTransport - - -DEFAULT_UPSTREAM_REVERSEWS_BIND = "127.0.0.1:29292" -DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS = 10_000 -_WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - - -class ReverseWebSocketUpstreamTransport(UpstreamTransport): - mode = "reversews" - endpoint_kind = "modcdp_server" - - def __init__(self, options: dict[str, Any] | None = None) -> None: - super().__init__() - options = options or {} - self.wait_timeout_ms = int(options.get("upstream_reversews_wait_timeout_ms") or DEFAULT_UPSTREAM_REVERSEWS_WAIT_TIMEOUT_MS) - self.server_socket: socket.socket | None = None - self.socket: socket.socket | None = None - self.accept_thread: threading.Thread | None = None - self.reader_thread: threading.Thread | None = None - self.peer_info: dict[str, Any] | None = None - self.peer_event = threading.Event() - self._peer_condition = threading.Condition() - self._close_generation = 0 - self.closed = False - self.write_lock = threading.Lock() - self._setBind(str(options.get("upstream_reversews_bind") or DEFAULT_UPSTREAM_REVERSEWS_BIND)) - - def update(self, config: dict[str, Any] | None = None) -> "ReverseWebSocketUpstreamTransport": - config = config or {} - bind = config.get("upstream_reversews_bind") - if isinstance(bind, str) and bind: - self._setBind(bind) - wait_timeout_ms = config.get("upstream_reversews_wait_timeout_ms") - if isinstance(wait_timeout_ms, int | float): - self.wait_timeout_ms = int(wait_timeout_ms) - return self - - def getInjectorConfig(self) -> dict[str, Any]: - return {} - - def _setBind(self, bind: str) -> None: - parsed = urlparse(bind if "://" in bind else f"ws://{bind}") - host = parsed.hostname or "127.0.0.1" - port = parsed.port or 29292 - if port <= 0 or port > 65535: - raise ValueError(f"Invalid host:port {bind}") - self.url = f"ws://{host}:{port}" - - def connect(self) -> None: - parsed = urlparse(self.url or "") - host = parsed.hostname or "127.0.0.1" - port = parsed.port or 29292 - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind((host, port)) - server_socket.listen(1) - self.server_socket = server_socket - with self._peer_condition: - self._close_generation += 1 - self.peer_event.clear() - self.closed = False - self.accept_thread = threading.Thread(target=self._accept_loop, daemon=True) - self.accept_thread.start() - - def send(self, message: dict[str, Any]) -> None: - if self.socket is None: - raise RuntimeError(f"No reverse ModCDP extension peer is connected at {self.url}.") - payload = json.dumps(message).encode() - with self.write_lock: - self.socket.sendall(_encode_server_text_frame(payload)) - - def waitForPeer(self) -> None: - if self.socket is not None: - return - deadline = time.monotonic() + self.wait_timeout_ms / 1000 - with self._peer_condition: - close_generation = self._close_generation - while self.socket is None: - if close_generation != self._close_generation: - raise RuntimeError(f"Reverse websocket transport at {self.url} closed before a peer connected.") - remaining = deadline - time.monotonic() - if remaining <= 0: - raise RuntimeError(f"Timed out waiting {self.wait_timeout_ms}ms for reverse ModCDP extension connection.") - self._peer_condition.wait(remaining) - - def close(self) -> None: - self.closed = True - for sock in (self.socket, self.server_socket): - try: - sock.close() if sock is not None else None - except Exception: - pass - self.socket = None - self.server_socket = None - self.peer_info = None - self.peer_event.clear() - with self._peer_condition: - self._close_generation += 1 - self._peer_condition.notify_all() - - def _accept_loop(self) -> None: - while not self.closed and self.server_socket is not None: - try: - sock, _ = self.server_socket.accept() - self._accept(sock) - except OSError: - return - except Exception as error: - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) - - def _accept(self, sock: socket.socket) -> None: - try: - _perform_server_handshake(sock) - hello_raw = _read_client_text_frame(sock, self.wait_timeout_ms / 1000) - if hello_raw is None: - raise RuntimeError("reverse hello socket closed") - hello = json.loads(hello_raw) - if not isinstance(hello, dict) or hello.get("type") != "modcdp.reverse.hello": - raise RuntimeError("invalid reverse hello") - old_socket = self.socket - if old_socket is not None and old_socket is not sock: - try: - old_socket.close() - except Exception: - pass - with self._peer_condition: - self.socket = sock - self.peer_info = hello - self.peer_event.set() - self._peer_condition.notify_all() - self.reader_thread = threading.Thread(target=self._read_loop, args=(sock,), daemon=True) - self.reader_thread.start() - except Exception as error: - try: - sock.close() - except Exception: - pass - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) - - def _read_loop(self, sock: socket.socket) -> None: - try: - while not self.closed and self.socket is sock: - data = _read_client_text_frame(sock, None) - if data is None: - break - self._parse_and_emit_recv(data) - except Exception as error: - if not self.closed: - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) - finally: - if self.socket is sock: - with self._peer_condition: - if self.socket is sock: - self.socket = None - self.peer_info = None - self.peer_event.clear() - self._peer_condition.notify_all() - try: - sock.close() - except Exception: - pass - - -def _perform_server_handshake(sock: socket.socket) -> None: - request = b"" - while b"\r\n\r\n" not in request: - chunk = sock.recv(4096) - if not chunk: - raise RuntimeError("websocket handshake closed") - request += chunk - headers: dict[str, str] = {} - for line in request.decode(errors="replace").split("\r\n")[1:]: - if ":" in line: - key, value = line.split(":", 1) - headers[key.strip().lower()] = value.strip() - ws_key = headers.get("sec-websocket-key") - if not ws_key: - raise RuntimeError("websocket handshake missing Sec-WebSocket-Key") - accept = base64.b64encode(hashlib.sha1((ws_key + _WS_GUID).encode()).digest()).decode() - sock.sendall( - ( - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - f"Sec-WebSocket-Accept: {accept}\r\n" - "\r\n" - ).encode() - ) - - -def _read_exact(sock: socket.socket, length: int) -> bytes: - chunks: list[bytes] = [] - remaining = length - while remaining > 0: - chunk = sock.recv(remaining) - if not chunk: - raise EOFError("websocket closed") - chunks.append(chunk) - remaining -= len(chunk) - return b"".join(chunks) - - -def _read_client_text_frame(sock: socket.socket, timeout_s: float | None) -> str | None: - old_timeout = sock.gettimeout() - if timeout_s is not None: - sock.settimeout(timeout_s) - try: - header = _read_exact(sock, 2) - opcode = header[0] & 0x0F - masked = bool(header[1] & 0x80) - length = header[1] & 0x7F - if length == 126: - length = struct.unpack("!H", _read_exact(sock, 2))[0] - elif length == 127: - length = struct.unpack("!Q", _read_exact(sock, 8))[0] - mask = _read_exact(sock, 4) if masked else b"" - payload = bytearray(_read_exact(sock, length)) - if masked: - for index, value in enumerate(payload): - payload[index] = value ^ mask[index % 4] - if opcode == 0x8: - return None - if opcode != 0x1: - return "" - return payload.decode() - finally: - sock.settimeout(old_timeout) - - -def _encode_server_text_frame(payload: bytes) -> bytes: - if len(payload) < 126: - header = bytes([0x81, len(payload)]) - elif len(payload) < 65536: - header = bytes([0x81, 126]) + struct.pack("!H", len(payload)) - else: - header = bytes([0x81, 127]) + struct.pack("!Q", len(payload)) - return header + payload diff --git a/python/modcdp/transport/UpstreamTransport.py b/python/modcdp/transport/UpstreamTransport.py index 0fdc2d6b..18118f45 100644 --- a/python/modcdp/transport/UpstreamTransport.py +++ b/python/modcdp/transport/UpstreamTransport.py @@ -1,43 +1,111 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/transport/UpstreamTransport.ts +# - ./go/modcdp/transport/UpstreamTransport.go from __future__ import annotations import json -from collections.abc import Callable -from typing import Any, Literal, TypedDict +import threading +from collections.abc import Callable, Mapping +from queue import Empty, Queue +from typing import Any, Literal, TypeAlias, overload +from pydantic import BaseModel -UpstreamMode = Literal["ws", "pipe", "nativemessaging", "reversews", "nats"] -UpstreamEndpointKind = Literal["raw_cdp", "modcdp_server"] +from ..types.modcdp import ModCDPUpstreamConfig, ProtocolPayload, ProtocolResult, _isObjectMap +from ..types.toJSON import modCDPToJSON + + +UpstreamMode: TypeAlias = Literal["ws"] +UpstreamPeerKind: TypeAlias = Literal["browser_cdp", "modcdp_server"] +UpstreamTransportConfig: TypeAlias = ModCDPUpstreamConfig class UpstreamTransport: - mode: UpstreamMode - endpoint_kind: UpstreamEndpointKind + upstream_mode: UpstreamMode = "ws" + peer_kind: UpstreamPeerKind = "browser_cdp" url: str | None = None - def __init__(self) -> None: + def __init__(self, config: UpstreamTransportConfig | dict[str, Any] | None = None) -> None: + self.config = _upstream_transport_config(config) + self._next_id = 0 + self._pending: dict[int, tuple[str, Queue[dict[str, Any]]]] = {} + self._lock = threading.Lock() self._recv_listeners: list[Callable[[dict[str, Any]], None]] = [] self._close_listeners: list[Callable[[Exception], None]] = [] + self._event_listeners: dict[str, list[Callable[[dict[str, Any], str | None, str | None], None]]] = {} def connect(self) -> None: raise NotImplementedError(f"{type(self).__name__}.connect is not implemented.") - def update(self, config: dict[str, Any] | None = None) -> "UpstreamTransport": + def update(self, config: UpstreamTransportConfig | dict[str, Any] | None = None) -> "UpstreamTransport": + incoming = _upstream_transport_config(config) + self.config = UpstreamTransportConfig.model_validate({**self.config.model_dump(), **incoming.model_dump(exclude_unset=True)}) return self - def getLauncherConfig(self) -> dict[str, Any]: - return {} - - def getInjectorConfig(self) -> dict[str, Any]: - return {} - - def getServerConfig(self) -> dict[str, Any]: + def configForLauncher(self) -> dict[str, Any]: return {} def close(self) -> None: return None - def send(self, message: dict[str, Any]) -> None: - raise NotImplementedError(f"{type(self).__name__}.send is not implemented.") + @overload + def send( + self, + command: str, + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> ProtocolResult: ... + + @overload + def send( + self, + command: dict[str, Any], + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> None: ... + + def send( + self, + command: dict[str, Any] | str | object, + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> ProtocolResult | None: + if isinstance(command, dict): + raise NotImplementedError(f"{type(self).__name__}.send is not implemented.") + method = _cdp_name(command) + effective_timeout_ms = timeout_ms if timeout_ms is not None else self.config.upstream_cdp_send_timeout_ms + with self._lock: + self._next_id += 1 + msg_id = self._next_id + done: Queue[dict[str, Any]] = Queue() + self._pending[msg_id] = (method, done) + message: dict[str, Any] = {"id": msg_id, "method": method, "params": params or {}} + if session_id: + message["sessionId"] = session_id + try: + self.send(message) + except Exception: + with self._lock: + self._pending.pop(msg_id, None) + raise + try: + response = done.get(timeout=effective_timeout_ms / 1000 if effective_timeout_ms > 0 else None) + except Empty: + with self._lock: + self._pending.pop(msg_id, None) + raise RuntimeError(f"{method} timed out after {effective_timeout_ms}ms") + err = response.get("error") + if err: + raise RuntimeError(f"{method} failed: {err.get('message', err) if isinstance(err, dict) else err}") + result = response.get("result") + return result if isinstance(result, dict) else {} def onRecv(self, listener: Callable[[dict[str, Any]], None]) -> Callable[[], None]: self._recv_listeners.append(listener) @@ -73,26 +141,168 @@ def stop() -> None: return stop - def waitForPeer(self) -> None: + def on(self, event: Any, listener: Callable[[dict[str, Any], str | None, str | None], None]) -> Callable[[], None]: + event_name = _cdp_event_name(event) + + def typed_listener(payload: dict[str, Any], target_id: str | None, session_id: str | None) -> None: + listener(_parse_event_payload(event, payload), target_id, session_id) + + listeners = self._event_listeners.setdefault(event_name, []) + listeners.append(typed_listener) + + removed = False + + def stop() -> None: + nonlocal removed + if removed: + return + removed = True + current_listeners = self._event_listeners.get(event_name) + if current_listeners is None: + return + try: + current_listeners.remove(typed_listener) + except ValueError: + return + if not current_listeners: + self._event_listeners.pop(event_name, None) + + return stop + + def getTargets(self) -> list[dict[str, Any]]: + result = self.send("Target.getTargets", {}) + target_infos = result.get("targetInfos") if isinstance(result, dict) else None + return [target for target in target_infos if _isObjectMap(target)] if isinstance(target_infos, list) else [] + + def resolveTargetId(self, params: dict[str, Any] | None = None) -> str | None: + target_id = (params or {}).get("targetId") + return target_id if isinstance(target_id, str) and target_id else None + + def createTarget(self, url: str) -> str: + result = self.send("Target.createTarget", {"url": url}) + target_id = result.get("targetId") if isinstance(result, dict) else None + if not isinstance(target_id, str) or not target_id: + raise RuntimeError("Target.createTarget returned no targetId") + return target_id + + def attachToTarget(self, target_id: str) -> str | None: + result = self.send("Target.attachToTarget", {"targetId": target_id, "flatten": True}) + session_id = result.get("sessionId") if isinstance(result, dict) else None + return session_id if isinstance(session_id, str) and session_id else None + + def detachFromTarget(self, session_id: str) -> None: + self.send("Target.detachFromTarget", {"sessionId": session_id}) + + def waitForPeer(self, config: dict[str, Any] | None = None) -> None: return None + def toJSON(self) -> dict[str, object]: + config = self.config.model_dump(mode="json") + return modCDPToJSON( + self, + { + "config": config, + "state": { + "pending": len(self._pending), + "recv_listeners": len(self._recv_listeners), + "close_listeners": len(self._close_listeners), + "event_listeners": len(self._event_listeners), + } + }, + ) + def _emit_recv(self, message: dict[str, Any]) -> None: for listener in list(self._recv_listeners): listener(message) def _emit_close(self, error: Exception) -> None: + self._settle_pending(error) for listener in list(self._close_listeners): listener(error) + def _settle_pending(self, error: Exception) -> None: + with self._lock: + pending = list(self._pending.values()) + self._pending.clear() + for _, done in pending: + done.put({"error": {"message": str(error)}}) + def _parse_and_emit_recv(self, data: str | bytes) -> None: - try: - raw = data.decode() if isinstance(data, bytes) else data - parsed = json.loads(raw) - if isinstance(parsed, dict): - self._emit_recv(parsed) - except Exception: + raw = data.decode() if isinstance(data, bytes) else data + parsed = json.loads(raw) + if not isinstance(parsed, dict): + return + if isinstance(parsed.get("id"), int): + with self._lock: + entry = self._pending.pop(parsed["id"], None) + if entry: + entry[1].put(parsed) + self._emit_recv(parsed) return + method = parsed.get("method") + params = parsed.get("params") + session_id = parsed.get("sessionId") + if isinstance(method, str): + event_params = params if isinstance(params, dict) else {} + self._emit_upstream_event(method, event_params, None, session_id if isinstance(session_id, str) else None) + self._emit_recv(parsed) + + def _emit_upstream_event( + self, + method: str, + payload: dict[str, Any], + target_id: str | None, + session_id: str | None, + ) -> None: + for listener in list(self._event_listeners.get(method, [])): + listener(payload, target_id, session_id) +def _upstream_transport_config(config: UpstreamTransportConfig | dict[str, Any] | None = None) -> UpstreamTransportConfig: + if isinstance(config, UpstreamTransportConfig): + return config + return UpstreamTransportConfig.model_validate(config or {}) + + +def _cdp_name(command: object) -> str: + if isinstance(command, str): + return command + meta_fn = getattr(command, "meta", None) + meta = meta_fn() if callable(meta_fn) else None + candidates = ( + getattr(command, "cdp_command_name", None), + getattr(command, "id", None), + getattr(meta, "cdp_command_name", None) if meta is not None else None, + meta.get("cdp_command_name") if isinstance(meta, Mapping) else None, + meta.get("id") if isinstance(meta, Mapping) else None, + getattr(command, "name", None), + ) + name = next((candidate for candidate in candidates if isinstance(candidate, str) and candidate), None) + if name is None: + raise TypeError("command must be a CDP method string or generated command object") + return name + + +def _cdp_event_name(event: object) -> str: + if isinstance(event, str): + return event + meta_fn = getattr(event, "meta", None) + meta = meta_fn() if callable(meta_fn) else None + candidates = ( + getattr(event, "cdp_event_name", None), + getattr(meta, "cdp_event_name", None) if meta is not None else None, + meta.get("cdp_event_name") if isinstance(meta, Mapping) else None, + getattr(event, "id", None), + getattr(meta, "id", None) if meta is not None else None, + meta.get("id") if isinstance(meta, Mapping) else None, + getattr(event, "name", None), + ) + name = next((candidate for candidate in candidates if isinstance(candidate, str) and candidate), None) + if name is None: + raise TypeError("event must be a CDP event name string or generated event object") + return name -def endpoint_kind_for_upstream(mode: str) -> UpstreamEndpointKind: - return "raw_cdp" if mode in ("ws", "pipe") else "modcdp_server" +def _parse_event_payload(event: object, payload: dict[str, Any]) -> dict[str, Any]: + if isinstance(event, type) and issubclass(event, BaseModel): + parsed = event.model_validate(payload) + return parsed.model_dump(mode="json", exclude_none=True, by_alias=True) + return payload diff --git a/python/modcdp/transport/WSUpstreamTransport.py b/python/modcdp/transport/WSUpstreamTransport.py new file mode 100644 index 00000000..1f557afc --- /dev/null +++ b/python/modcdp/transport/WSUpstreamTransport.py @@ -0,0 +1,125 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/transport/WSUpstreamTransport.ts +# - ./go/modcdp/transport/WSUpstreamTransport.go +from __future__ import annotations + +import json +import threading +from typing import Any, overload + +from websocket import create_connection + +from ..launcher.BrowserLauncher import resolveCdpWebSocketUrl +from ..transport.UpstreamTransport import UpstreamTransport, UpstreamTransportConfig +from ..types.modcdp import ProtocolPayload, ProtocolResult + + +class WSUpstreamTransport(UpstreamTransport): + upstream_mode = "ws" + + def __init__(self, config: UpstreamTransportConfig | dict[str, Any] | None = None) -> None: + super().__init__(config) + self.url = self.config.upstream_ws_cdp_url or "" + self.ws: Any | None = None + self._reader_thread: threading.Thread | None = None + self._generation = 0 + self._connect_lock = threading.Lock() + + def update(self, config: UpstreamTransportConfig | dict[str, Any] | None = None) -> "WSUpstreamTransport": + super().update(config) + if self.config.upstream_ws_cdp_url: + self.url = self.config.upstream_ws_cdp_url + return self + + def connect(self) -> None: + with self._connect_lock: + if self.ws is not None: + return + if not self.url: + raise RuntimeError("WSUpstreamTransport requires upstream_ws_cdp_url or launcher-provided cdp_url.") + # cdp_url may start as an HTTP discovery endpoint; from here on it is the resolved WebSocket CDP endpoint. + self.url = resolveCdpWebSocketUrl(self.url, "upstream_ws_cdp_url") + self.config = UpstreamTransportConfig.model_validate({**self.config.model_dump(), "upstream_ws_cdp_url": self.url}) + self._generation += 1 + generation = self._generation + self.ws = create_connection(self.url, timeout=10) + self._reader_thread = threading.Thread(target=lambda: self._read_loop(generation), daemon=True) + self._reader_thread.start() + + @overload + def send( + self, + command: str, + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> ProtocolResult: ... + + @overload + def send( + self, + command: dict[str, Any], + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> None: ... + + def send( + self, + command: dict[str, Any] | str, + params: ProtocolPayload | None = None, + session_id: str | None = None, + *, + timeout_ms: int | None = None, + ) -> ProtocolResult | None: + if isinstance(command, str): + if self.ws is None: + self.connect() + return super().send(command, params, session_id, timeout_ms=timeout_ms) + message = command + if self.ws is None: + raise RuntimeError("CDP websocket is not connected.") + self.ws.send(json.dumps(message)) + return None + + def _recv(self) -> Any: + if self.ws is None: + raise RuntimeError("CDP websocket is not connected.") + return self.ws.recv() + + def close(self) -> None: + self._generation += 1 + had_connection = self.ws is not None + if self.ws is not None: + self.ws.close() + self.ws = None + if had_connection: + self._settle_pending(RuntimeError("CDP websocket closed")) + if self._reader_thread is not None and self._reader_thread.is_alive(): + self._reader_thread.join(timeout=1) + self._reader_thread = None + + def toJSON(self) -> dict[str, object]: + json_value = super().toJSON() + state_raw = json_value.get("state") + state: dict[str, object] = {str(key): value for key, value in state_raw.items()} if isinstance(state_raw, dict) else {} + state["connected"] = self.ws is not None + return {**json_value, "state": state} + + def _read_loop(self, generation: int | None = None) -> None: + generation = self._generation if generation is None else generation + ws = self.ws + if ws is None: + return + try: + while self.ws is ws and self._generation == generation: + raw = ws.recv() + if not raw: + break + self._parse_and_emit_recv(raw) + except Exception as error: + if self.ws is ws and self._generation == generation: + self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) diff --git a/python/modcdp/transport/WebSocketUpstreamTransport.py b/python/modcdp/transport/WebSocketUpstreamTransport.py deleted file mode 100644 index e59b05c4..00000000 --- a/python/modcdp/transport/WebSocketUpstreamTransport.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import annotations - -import json -import threading -from typing import Any - -from websocket import create_connection - -from ..launcher.BrowserLauncher import resolveCdpWebSocketUrl -from ..transport.UpstreamTransport import UpstreamTransport - - -class WebSocketUpstreamTransport(UpstreamTransport): - mode = "ws" - endpoint_kind = "raw_cdp" - - def __init__(self, options: dict[str, Any] | None = None) -> None: - super().__init__() - options = options or {} - self.url = str(options.get("cdp_url") or "") - self.ws: Any | None = None - self._reader_thread: threading.Thread | None = None - self._closed = False - self._generation = 0 - - def update(self, config: dict[str, Any] | None = None) -> "WebSocketUpstreamTransport": - config = config or {} - cdp_url = config.get("cdp_url") - if cdp_url: - self.url = str(cdp_url) - return self - - def getServerConfig(self) -> dict[str, Any]: - return {"server_loopback_cdp_url": self.url} if self.url else {} - - def connect(self) -> None: - if not self.url: - raise RuntimeError("upstream.upstream_mode=ws requires upstream.upstream_cdp_url or launcher-provided cdp_url.") - # cdp_url may start as an HTTP discovery endpoint; from here on it is the resolved WebSocket CDP endpoint. - self.url = resolveCdpWebSocketUrl(self.url, "upstream_cdp_url") - self._generation += 1 - generation = self._generation - previous_ws = self.ws - if previous_ws is not None: - previous_ws.close() - self.ws = create_connection(self.url, timeout=10) - self._closed = False - self._reader_thread = threading.Thread(target=lambda: self._read_loop(generation), daemon=True) - self._reader_thread.start() - - def send(self, message: dict[str, Any]) -> None: - if self.ws is None: - raise RuntimeError("CDP websocket is not connected.") - self.ws.send(json.dumps(message)) - - def _recv(self) -> Any: - if self.ws is None: - raise RuntimeError("CDP websocket is not connected.") - return self.ws.recv() - - def close(self) -> None: - self._closed = True - self._generation += 1 - if self.ws is not None: - self.ws.close() - self.ws = None - if self._reader_thread is not None and self._reader_thread.is_alive(): - self._reader_thread.join(timeout=1) - self._reader_thread = None - - def _read_loop(self, generation: int | None = None) -> None: - generation = self._generation if generation is None else generation - ws = self.ws - if ws is None: - return - try: - while not self._closed and self.ws is ws and self._generation == generation: - raw = ws.recv() - if not raw: - break - self._parse_and_emit_recv(raw) - except Exception as error: - if not self._closed and self.ws is ws and self._generation == generation: - self._emit_close(error if isinstance(error, Exception) else RuntimeError(str(error))) diff --git a/python/modcdp/transport/__init__.py b/python/modcdp/transport/__init__.py index c31a2d3c..675b652c 100644 --- a/python/modcdp/transport/__init__.py +++ b/python/modcdp/transport/__init__.py @@ -1,6 +1,8 @@ -from .NativeMessagingUpstreamTransport import NativeMessagingUpstreamTransport -from .NatsUpstreamTransport import NatsUpstreamTransport -from .PipeUpstreamTransport import PipeUpstreamTransport -from .ReverseWebSocketUpstreamTransport import ReverseWebSocketUpstreamTransport -from .UpstreamTransport import UpstreamTransport -from .WebSocketUpstreamTransport import WebSocketUpstreamTransport +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/transport/UpstreamTransport.ts +# - ./js/src/transport/WSUpstreamTransport.ts +# - ./go/modcdp/transport/UpstreamTransport.go +# - ./go/modcdp/transport/WSUpstreamTransport.go +from .UpstreamTransport import UpstreamTransport, UpstreamTransportConfig +from .WSUpstreamTransport import WSUpstreamTransport diff --git a/python/modcdp/types/CDPTypes.py b/python/modcdp/types/CDPTypes.py new file mode 100644 index 00000000..bd6acc18 --- /dev/null +++ b/python/modcdp/types/CDPTypes.py @@ -0,0 +1,968 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/CDPTypes.ts +# - ./go/modcdp/client/CDPTypes.go +from __future__ import annotations + +import json +import re +import threading +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Literal, TypeAlias + +from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter, ValidationError +from pydantic_core import to_jsonable_python + +from ..types.generated import cdp as generated_cdp +from ..types.generated.cdp import CDPEvent, CDPModel, CDPParams +from ..types.jsonschema import type_adapter_from_json_schema +from ..types.modcdp import ( + JsonObject, + JsonValue, + ModCDPAddCustomCommandParams, + ModCDPAddCustomEventObjectParams, + ModCDPAddCustomEventParams, + ModCDPAddMiddlewareParams, + ModCDPPayloadSchemaSpec, + ProtocolParams, + ProtocolPayload, + ProtocolResult, + TranslatedStep, +) +from ..types.toJSON import modCDPToJSON + +JsonSchema: TypeAlias = dict[str, JsonValue] +CustomCommandConfig: TypeAlias = Mapping[str, ModCDPPayloadSchemaSpec | str | None] +CustomEventConfig: TypeAlias = Mapping[str, ModCDPPayloadSchemaSpec | str | None] +CustomMiddlewareConfig: TypeAlias = Mapping[str, object] +CustomCommandRegistration: TypeAlias = dict[str, object] +CustomEventRegistration: TypeAlias = dict[str, object] +CustomMiddlewareRegistration: TypeAlias = dict[str, object] +CustomCommandRegistrations: TypeAlias = Sequence[Mapping[str, object]] | dict[str, object] +CustomEventRegistrations: TypeAlias = Sequence[str | Mapping[str, object]] | dict[str, object] +CustomMiddlewareRegistrations: TypeAlias = Sequence[Mapping[str, object]] + + +class _ModCDPAddCustomCommand(BaseModel): + model_config = ConfigDict(extra="forbid") + + name: object + expression: str | None = None + params_schema: object = None + result_schema: object = None + + +class _ModCDPAddCustomEvent(BaseModel): + model_config = ConfigDict(extra="forbid") + + name: object + event_schema: object = None + + +class _CustomCommandConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + expression: str | None = None + params_schema: object = None + result_schema: object = None + + +class _CustomCommandConfigMap(RootModel[dict[str, _CustomCommandConfig]]): + pass + + +class _CustomEventConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + event_schema: object = None + + +class _CustomEventConfigMap(RootModel[dict[str, _CustomEventConfig]]): + pass + + +class _ModCDPAddMiddleware(BaseModel): + model_config = ConfigDict(extra="forbid") + + phase: Literal["request", "response", "event"] + expression: str + name: object | None = None + + +@dataclass(frozen=True) +class _AdapterRegistration: + adapter: TypeAdapter[object] | None = None + json_schema: dict[str, JsonValue] | None = None + + +@dataclass(frozen=True) +class CommandSchema: + params: TypeAdapter[object] | None = None + result: TypeAdapter[object] | None = None + + +@dataclass(frozen=True) +class CommandPreparation: + params: Mapping[str, object] + local_result: ProtocolResult | None = None + custom_command_name: str | None = None + + +class CDPTypesConfig(BaseModel): + model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) + + custom_commands: CustomCommandRegistrations | None = None + custom_events: CustomEventRegistrations | None = None + custom_middlewares: CustomMiddlewareRegistrations | None = None + + +JSON_SCHEMA_OBJECT: JsonSchema = {"type": "object"} +JSON_SCHEMA_ANY: JsonSchema = {} +MOD_ADD_CUSTOM_COMMAND_PARAMS_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "expression": {"type": ["string", "null"]}, + "params_schema": {"type": ["object", "null"]}, + "result_schema": {"type": ["object", "null"]}, + }, + "required": ["name"], + "additionalProperties": False, +} +MOD_ADD_CUSTOM_EVENT_PARAMS_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "event_schema": {"type": ["object", "null"]}, + }, + "required": ["name"], + "additionalProperties": False, +} +MOD_ADD_MIDDLEWARE_PARAMS_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": ["string", "null"]}, + "phase": {"enum": ["request", "response", "event"]}, + "expression": {"type": "string"}, + }, + "required": ["phase", "expression"], + "additionalProperties": False, +} +MOD_COMMAND_REGISTRATION_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "expression": {"type": ["string", "null"]}, + "params_schema": {"type": ["object", "null"]}, + "result_schema": {"type": ["object", "null"]}, + }, + "required": ["name"], + "additionalProperties": False, +} +MOD_EVENT_REGISTRATION_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "event_schema": {"type": ["object", "null"]}, + }, + "required": ["name"], + "additionalProperties": False, +} +MOD_MIDDLEWARE_REGISTRATION_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "name": {"type": ["string", "null"]}, + "phase": {"enum": ["request", "response", "event"]}, + "expression": {"type": "string"}, + }, + "required": ["phase", "expression"], + "additionalProperties": False, +} +MOD_CONFIGURE_PARAMS_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "upstream": { + "type": "object", + "properties": { + "upstream_mode": {"enum": ["ws"]}, + "upstream_ws_cdp_url": {"type": "string"}, + "upstream_ws_connect_error_settle_timeout_ms": {"type": "number"}, + "upstream_cdp_send_timeout_ms": {"type": "number"}, + }, + "additionalProperties": False, + }, + "router": { + "type": "object", + "properties": { + "router_routes": {"type": "object", "additionalProperties": {"type": "string"}}, + "loopback_execution_context_timeout_ms": {"type": "number"}, + }, + "additionalProperties": False, + }, + "client_config": { + "type": "object", + "properties": { + "client_hydrate_aliases": {"type": "boolean"}, + "client_mirror_upstream_events": {"type": "boolean"}, + "client_cdp_send_timeout_ms": {"type": "number"}, + "client_event_wait_timeout_ms": {"type": "number"}, + "client_heartbeat_interval_ms": {"type": "number"}, + }, + "additionalProperties": False, + }, + "downstream": { + "type": "object", + "properties": { + "downstream_client_timeout_ms": {"type": "number"}, + "downstream_close_browser_on_disconnect": {"type": "boolean"}, + }, + "additionalProperties": False, + }, + "server_browser_token": {"type": "string"}, + "custom_commands": {"type": "array", "items": MOD_COMMAND_REGISTRATION_SCHEMA}, + "custom_events": {"type": "array", "items": MOD_EVENT_REGISTRATION_SCHEMA}, + "custom_middlewares": {"type": "array", "items": MOD_MIDDLEWARE_REGISTRATION_SCHEMA}, + }, + "additionalProperties": False, +} +MOD_TOPOLOGY_PARAMS_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "rootTargetId": {"type": ["string", "null"]}, + "targetId": {"type": ["string", "null"]}, + "active": {"type": ["boolean", "null"]}, + }, + "additionalProperties": False, +} +MOD_TOPOLOGY_FRAME_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "targetId": {"type": "string"}, + "url": {"type": ["string", "null"]}, + "parentFrameId": {"type": ["string", "null"]}, + "outerBackendNodeId": {"type": ["integer", "null"]}, + }, + "required": ["targetId"], + "additionalProperties": False, +} +MOD_TOPOLOGY_DOM_ROOT_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "kind": {"enum": ["document", "shadow"]}, + "frameId": {"type": "string"}, + "outerBackendNodeId": {"type": ["integer", "null"]}, + "innerBackendNodeId": {"type": ["integer", "null"]}, + "mode": {"enum": ["open", "closed", "user-agent", None]}, + "executionContextId": {"type": ["integer", "null"]}, + "uniqueContextId": {"type": ["string", "null"]}, + }, + "required": ["kind", "frameId"], + "additionalProperties": False, +} +MOD_TOPOLOGY_TARGET_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "targetId": {"type": "string"}, + "type": {"type": "string"}, + "title": {"type": ["string", "null"]}, + "url": {"type": ["string", "null"]}, + "attached": {"type": ["boolean", "null"]}, + "parentId": {"type": ["string", "null"]}, + "parentFrameId": {"type": ["string", "null"]}, + "sessionId": {"type": ["string", "null"]}, + }, + "required": ["targetId", "type"], +} +MOD_TOPOLOGY_EXECUTION_CONTEXT_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "origin": {"type": ["string", "null"]}, + "name": {"type": ["string", "null"]}, + "uniqueId": {"type": ["string", "null"]}, + "auxData": {"type": ["object", "null"]}, + "sessionId": {"type": ["string", "null"]}, + "targetId": {"type": "string"}, + "frameId": {"type": ["string", "null"]}, + "world": {"type": "string"}, + }, + "required": ["id", "sessionId", "targetId", "world"], + "additionalProperties": False, +} +MOD_TOPOLOGY_RESPONSE_SCHEMA: JsonSchema = { + "type": "object", + "properties": { + "objectGroup": {"type": "string"}, + "rootFrameId": {"type": "string"}, + "frames": {"type": "object", "additionalProperties": MOD_TOPOLOGY_FRAME_SCHEMA}, + "roots": {"type": "object", "additionalProperties": MOD_TOPOLOGY_DOM_ROOT_SCHEMA}, + "targets": {"type": "object", "additionalProperties": MOD_TOPOLOGY_TARGET_SCHEMA}, + "contexts": {"type": "object", "additionalProperties": MOD_TOPOLOGY_EXECUTION_CONTEXT_SCHEMA}, + }, + "required": ["objectGroup", "rootFrameId", "frames", "roots", "targets", "contexts"], + "additionalProperties": False, +} +DEFAULT_BUILTIN_COMMANDS: tuple[CustomCommandRegistration, ...] = ( + { + "name": "Mod.ping", + "params_schema": {"type": "object", "properties": {"sent_at": {"type": "number"}}, "additionalProperties": False}, + "result_schema": {"type": "object", "properties": {"ok": {"type": "boolean"}}, "required": ["ok"], "additionalProperties": False}, + "expression": """ + async (params) => { + const received_at = Date.now(); + const message = { + method: "Mod.pong", + params: { + sent_at: + typeof params.sent_at === "number" + ? params.sent_at + : received_at, + received_at, + from: "extension-service-worker", + }, + }; + if (cdpSessionId) message.sessionId = cdpSessionId; + downstream.sendEvent(message); + return { ok: true }; + } + """, + }, + { + "name": "Mod.configure", + "params_schema": MOD_CONFIGURE_PARAMS_SCHEMA, + "result_schema": JSON_SCHEMA_OBJECT, + "expression": "async (params) => { await ModCDP.configure(params); return params; }", + }, + { + "name": "Mod.evaluate", + "params_schema": { + "type": "object", + "properties": { + "expression": {"type": "string"}, + "params": {"type": ["object", "null"]}, + "cdpSessionId": {"type": ["string", "null"]}, + }, + "required": ["expression"], + "additionalProperties": False, + }, + "result_schema": JSON_SCHEMA_ANY, + "expression": """ + async ({ expression, params = {}, cdpSessionId = null }) => + ModCDP.evaluateInServiceWorker({ expression, params, cdpSessionId }) + """, + }, + { + "name": "Mod.getTopology", + "params_schema": MOD_TOPOLOGY_PARAMS_SCHEMA, + "result_schema": MOD_TOPOLOGY_RESPONSE_SCHEMA, + "expression": "async (params) => ModCDP.client.router.getTopology(params)", + }, + { + "name": "Mod.addCustomCommand", + "params_schema": MOD_ADD_CUSTOM_COMMAND_PARAMS_SCHEMA, + "result_schema": {"type": "object", "properties": {"name": {"type": "string"}, "registered": {"type": "boolean"}}, "required": ["name", "registered"], "additionalProperties": False}, + "expression": "async (params) => ModCDP.addCustomCommand(params)", + }, + { + "name": "Mod.addCustomEvent", + "params_schema": MOD_ADD_CUSTOM_EVENT_PARAMS_SCHEMA, + "result_schema": {"type": "object", "properties": {"name": {"type": "string"}, "registered": {"type": "boolean"}}, "required": ["name", "registered"], "additionalProperties": False}, + "expression": "async (params) => ModCDP.addCustomEvent(params)", + }, + { + "name": "Mod.addMiddleware", + "params_schema": MOD_ADD_MIDDLEWARE_PARAMS_SCHEMA, + "result_schema": {"type": "object", "properties": {"name": {"type": "string"}, "phase": {"enum": ["request", "response", "event"]}, "registered": {"type": "boolean"}}, "required": ["name", "phase", "registered"], "additionalProperties": False}, + "expression": "async (params) => ModCDP.addMiddleware(params)", + }, +) +DEFAULT_BUILTIN_EVENTS: tuple[CustomEventRegistration, ...] = ( + { + "name": "Mod.pong", + "event_schema": { + "type": "object", + "properties": { + "sent_at": {"type": "number"}, + "received_at": {"type": "number"}, + "from": {"type": "string"}, + }, + "required": ["sent_at", "received_at", "from"], + "additionalProperties": False, + }, + }, +) + + +def normalizeModCDPName(value: object) -> str: + if isinstance(value, str): + name = value.strip() + else: + meta_fn = getattr(value, "meta", None) + meta = meta_fn() if callable(meta_fn) else None + candidates = ( + getattr(value, "cdp_command_name", None), + getattr(value, "cdp_event_name", None), + getattr(meta, "cdp_command_name", None) if meta is not None else None, + getattr(meta, "cdp_event_name", None) if meta is not None else None, + meta.get("cdp_command_name") if isinstance(meta, Mapping) else None, + meta.get("cdp_event_name") if isinstance(meta, Mapping) else None, + getattr(value, "id", None), + getattr(meta, "id", None) if meta is not None else None, + meta.get("id") if isinstance(meta, Mapping) else None, + getattr(meta, "name", None) if meta is not None else None, + meta.get("name") if isinstance(meta, Mapping) else None, + getattr(value, "name", None), + ) + name = next((candidate for candidate in candidates if isinstance(candidate, str) and candidate.strip()), "") + if not name: + name = _bound_cdp_method_name(value) or "" + name = name.strip() + if not name: + raise ValueError("Expected a CDP name string or named CDP schema.") + return name + + +def _bound_cdp_method_name(value: object) -> str | None: + method = getattr(value, "__name__", None) + owner = getattr(value, "__self__", None) + owner_class = type(owner) if owner is not None else None + owner_name = getattr(owner_class, "__name__", None) + if not isinstance(method, str) or not method or not isinstance(owner_name, str): + return None + if not owner_name.endswith("Domain"): + return None + domain = owner_name.removesuffix("Domain").removeprefix("_") + if not domain: + return None + return f"{domain}.{method}" + + +def _json_object(value: object) -> JsonObject: + if isinstance(value, Mapping): + return {str(key): _json_value(raw_value) for key, raw_value in value.items()} + raise TypeError("expected a JSON object") + + +def _json_value(value: object) -> JsonValue: + if value is None or isinstance(value, bool | int | float | str): + return value + if isinstance(value, type) and issubclass(value, BaseModel): + return _json_object(value.model_json_schema()) + if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray): + return [_json_value(item) for item in value] + if isinstance(value, Mapping): + return {str(key): _json_value(raw_value) for key, raw_value in value.items()} + raise TypeError(f"expected a JSON value, got {type(value).__name__}") + + +def _model_or_json_object(value: object) -> ProtocolResult: + if isinstance(value, BaseModel): + return _json_object(value.model_dump(mode="json", exclude_none=True, by_alias=True)) + return _json_object(to_jsonable_python(value)) + + +class CDPTypes: + def __init__(self, config: CDPTypesConfig | Mapping[str, object] | None = None, **kwargs: object) -> None: + raw_config = config.model_dump() if isinstance(config, CDPTypesConfig) else dict(config or {}) + raw_config.update(kwargs) + parsed_config = CDPTypesConfig.model_validate(raw_config) + self.custom_commands: dict[str, CustomCommandRegistration] = {} + self.custom_events: dict[str, CustomEventRegistration] = {} + self.custom_middlewares: list[CustomMiddlewareRegistration] = [] + self.command_schemas: dict[str, CommandSchema] = {} + self.event_schemas: dict[str, TypeAdapter[object]] = {} + self.native_command_names: set[str] = set() + self.event_classes: dict[str, type[CDPEvent]] = {} + self.service_worker_expression_builders: dict[str, Callable[[ProtocolParams, str | None], str]] = {} + self._lock = threading.RLock() + self.hydrateNativeProtocolSchemas() + for command in DEFAULT_BUILTIN_COMMANDS: + self.addCustomCommand(command) + for event in DEFAULT_BUILTIN_EVENTS: + self.addCustomEvent(event) + for command in _custom_command_entries(parsed_config.custom_commands): + self.addCustomCommand(command) + for event in _custom_event_entries(parsed_config.custom_events): + self.addCustomEvent({"name": event} if isinstance(event, str) else event) + for middleware in parsed_config.custom_middlewares or []: + self.addCustomMiddleware(middleware) + self.service_worker_expression_builders["Mod.evaluate"] = lambda params, _cdp_session_id: ( + "\n async ({ params = {}, cdpSessionId = null }) => {\n" + f" const value = ({params['expression']});\n" + " return typeof value === \"function\" ? await value(params) : value;\n" + " }\n " + ) + + def update( + self, + config: CDPTypesConfig | Mapping[str, object] | None = None, + **kwargs: object, + ) -> "CDPTypes": + raw_config = config.model_dump() if isinstance(config, CDPTypesConfig) else dict(config or {}) + raw_config.update(kwargs) + parsed_config = CDPTypesConfig.model_validate(raw_config) + commands = [*self.custom_commands.values(), *_custom_command_entries(parsed_config.custom_commands)] + events = [*self.custom_events.values(), *_custom_event_entries(parsed_config.custom_events)] + middlewares = [*self.custom_middlewares, *(parsed_config.custom_middlewares or [])] + return CDPTypes( + { + "custom_commands": commands, + "custom_events": events, + "custom_middlewares": middlewares, + } + ) + + def toJSON(self) -> dict[str, object]: + custom_commands = [] + for command in self.customCommandWireRegistrations(): + command = dict(command) + command.pop("expression", None) + custom_commands.append(command) + custom_middlewares = [] + for middleware in self.customMiddlewareWireRegistrations(): + middleware = dict(middleware) + middleware.pop("expression", None) + custom_middlewares.append(middleware) + return modCDPToJSON( + self, + { + "config": { + "custom_commands": custom_commands, + "custom_events": self.customEventWireRegistrations(), + "custom_middlewares": custom_middlewares, + }, + "state": { + "custom_commands": len(self.custom_commands), + "custom_events": len(self.custom_events), + "custom_middlewares": len(self.custom_middlewares), + "command_params_schemas": len(self.command_schemas), + "command_result_schemas": len(self.command_schemas), + "event_schemas": len(self.event_schemas), + }, + }, + ) + + def hydrateNativeProtocolSchemas(self) -> None: + with self._lock: + for domain_name, domain_class in vars(generated_cdp).items(): + if not domain_name.endswith("Domain") or not isinstance(domain_class, type): + continue + domain = domain_name.removesuffix("Domain") + nested_classes = { + name: value + for name, value in vars(domain_class).items() + if isinstance(value, type) and issubclass(value, CDPModel) + } + for class_name, params_class in nested_classes.items(): + if issubclass(params_class, CDPEvent): + event_name = getattr(params_class, "cdp_event_name", None) + if isinstance(event_name, str): + self.event_schemas[event_name] = TypeAdapter(params_class) + self.event_classes[event_name] = params_class + continue + if not class_name.startswith("_") or not class_name.endswith("Params"): + continue + command_base = class_name[1:-6] + result_class = nested_classes.get(f"_{command_base}Result") + if result_class is None: + continue + method = f"{domain}.{command_base[:1].lower()}{command_base[1:]}" + params_adapter: TypeAdapter[object] | None = None + if issubclass(params_class, CDPParams): + params_adapter = TypeAdapter(params_class) + self.command_schemas[method] = CommandSchema(params=params_adapter, result=TypeAdapter(result_class)) + self.native_command_names.add(method) + + def nativeCommandSchema(self, method: str) -> CommandSchema | None: + with self._lock: + if method not in self.native_command_names: + return None + return self.command_schemas.get(method) + + def commandParamsSchema(self, method: str) -> TypeAdapter[object] | None: + with self._lock: + schema = self.command_schemas.get(method) + if schema is None: + return None + return schema.params + + def commandResultSchema(self, method: str) -> TypeAdapter[object] | None: + with self._lock: + schema = self.command_schemas.get(method) + if schema is None: + return None + return schema.result + + def eventPayloadSchema(self, event: str) -> TypeAdapter[object] | None: + with self._lock: + return self.event_schemas.get(event) + + def prepareCommand(self, method: str, params: object = None, can_register_locally: bool = False) -> CommandPreparation: + if method == "Mod.addCustomCommand": + parsed = _ModCDPAddCustomCommand.model_validate(params or {}) + name = normalizeModCDPName(parsed.name) + command_registration: CustomCommandRegistration = {"name": name} + if parsed.expression is not None: + command_registration["expression"] = parsed.expression + if parsed.params_schema is not None: + command_registration["params_schema"] = _json_value(parsed.params_schema) + if parsed.result_schema is not None: + command_registration["result_schema"] = _json_value(parsed.result_schema) + name = self.addCustomCommand(command_registration) + if not parsed.expression and can_register_locally: + return CommandPreparation(params={"name": name}, local_result={"name": name, "registered": True}, custom_command_name=name) + return CommandPreparation(params=self.customCommandWireRegistration(name), custom_command_name=name) + if method == "Mod.addCustomEvent": + parsed = _ModCDPAddCustomEvent.model_validate(params or {}) + name = normalizeModCDPName(parsed.name) + event_registration: CustomEventRegistration = {"name": name} + if parsed.event_schema is not None: + event_registration["event_schema"] = _json_value(parsed.event_schema) + name = self.addCustomEvent(event_registration) + if can_register_locally: + return CommandPreparation(params={"name": name}, local_result={"name": name, "registered": True}) + return CommandPreparation(params=self.customEventWireRegistration(name)) + command_params = self.parseCommandParams(method, params or {}) + if method == "Mod.addMiddleware": + parsed = _ModCDPAddMiddleware.model_validate(command_params) + middleware_registration: CustomMiddlewareRegistration = {"phase": parsed.phase, "expression": parsed.expression} + if parsed.name is not None: + middleware_registration["name"] = "*" if parsed.name == "*" else normalizeModCDPName(parsed.name) + name = self.addCustomMiddleware(middleware_registration) + if can_register_locally: + return CommandPreparation(params=command_params, local_result={"name": name, "phase": parsed.phase, "registered": True}) + return CommandPreparation(params=command_params) + + def parseCommandParams(self, method: str, params: object = None) -> ProtocolParams: + if method == "Mod.addCustomCommand": + parsed = _ModCDPAddCustomCommand.model_validate(params or {}) + command_params: dict[str, object] = {"name": normalizeModCDPName(parsed.name)} + if parsed.expression is not None: + command_params["expression"] = parsed.expression + if parsed.params_schema is not None: + command_params["params_schema"] = _json_value(parsed.params_schema) + if parsed.result_schema is not None: + command_params["result_schema"] = _json_value(parsed.result_schema) + return command_params + if method == "Mod.addCustomEvent": + parsed = _ModCDPAddCustomEvent.model_validate(params or {}) + event_params: dict[str, object] = {"name": normalizeModCDPName(parsed.name)} + if parsed.event_schema is not None: + event_params["event_schema"] = _json_value(parsed.event_schema) + return event_params + if method == "Mod.addMiddleware": + parsed = _ModCDPAddMiddleware.model_validate(params or {}) + middleware_params: dict[str, object] = {"phase": parsed.phase, "expression": parsed.expression} + if parsed.name is not None: + middleware_params["name"] = "*" if parsed.name == "*" else normalizeModCDPName(parsed.name) + return middleware_params + adapter = self.commandParamsSchema(method) + if adapter is None: + return _json_object(params or {}) + try: + validated = adapter.validate_python(params or {}, strict=True) + except ValidationError as error: + raise ValueError(f"{method} params did not match params_schema: {error}") from error + return _model_or_json_object(validated) + + def parseCommandResult(self, method: str, result: object) -> object: + adapter = self.commandResultSchema(method) + if adapter is None: + return result + try: + validated = adapter.validate_python(result, strict=True) + except ValidationError as error: + raise ValueError(f"{method} result did not match result_schema: {error}") from error + if isinstance(validated, BaseModel): + return _json_object(validated.model_dump(mode="json", exclude_none=True, by_alias=True)) + return to_jsonable_python(validated) + + def parseEventPayload(self, event: str, payload: object = None) -> ProtocolPayload: + adapter = self.eventPayloadSchema(event) + if adapter is None: + return _json_object(payload or {}) + try: + validated = adapter.validate_python(payload or {}, strict=True) + except ValidationError as direct_error: + if not isinstance(payload, Mapping): + raise ValueError(f"{event} event did not match event_schema: {direct_error}") from direct_error + payload_mapping = dict(payload) + if set(payload_mapping.keys()) != {"value"}: + raise ValueError(f"{event} event did not match event_schema: {direct_error}") from direct_error + try: + validated = adapter.validate_python(payload_mapping["value"], strict=True) + except ValidationError as value_error: + raise ValueError(f"{event} event did not match event_schema: {value_error}") from value_error + if isinstance(validated, BaseModel): + jsonable = _json_value(validated.model_dump(mode="json", exclude_none=True, by_alias=True)) + else: + jsonable = _json_value(to_jsonable_python(validated)) + return jsonable if isinstance(jsonable, dict) else {"value": jsonable} + + def addCustomCommand(self, registration: Mapping[str, object]) -> str: + parsed = _ModCDPAddCustomCommand.model_validate(registration) + name = normalizeModCDPName(parsed.name) + if not re.match(r"^[^.]+\.[^.]+$", name): + raise ValueError("name must be in Domain.method form") + params_schema = self._adapterFromOptionalSchema(parsed.params_schema, "params_schema") + result_schema = self._adapterFromOptionalSchema(parsed.result_schema, "result_schema") + with self._lock: + existing = self.command_schemas.get(name, CommandSchema()) + if params_schema.adapter is not None: + existing = CommandSchema(params=params_schema.adapter, result=existing.result) + if result_schema.adapter is not None: + existing = CommandSchema(params=existing.params, result=result_schema.adapter) + self.command_schemas[name] = existing + command: CustomCommandRegistration = {"name": name} + if parsed.expression: + command["expression"] = parsed.expression + if params_schema.json_schema: + command["params_schema"] = params_schema.json_schema + if result_schema.json_schema: + command["result_schema"] = result_schema.json_schema + self.custom_commands[name] = command + return name + + def customCommandWireRegistration(self, name: str) -> dict[str, object]: + for registration in self.customCommandWireRegistrations(): + if registration["name"] == name: + return registration + return {"name": name} + + def customCommandWireRegistrations(self, expression_required: bool = False) -> list[dict[str, object]]: + registrations: list[dict[str, object]] = [] + with self._lock: + commands = list(self.custom_commands.values()) + for command in commands: + expression = command.get("expression") + if expression_required and not expression: + continue + raw_name = command.get("name") + if not isinstance(raw_name, str): + raise ValueError("custom command registration is missing name") + name = normalizeModCDPName(raw_name) + wire: dict[str, object] = {"name": name} + if expression is not None: + wire["expression"] = expression + params_schema = command.get("params_schema") + result_schema = command.get("result_schema") + if isinstance(params_schema, dict): + wire["params_schema"] = params_schema + if isinstance(result_schema, dict): + wire["result_schema"] = result_schema + registrations.append(wire) + return registrations + + def addCustomEvent(self, registration: Mapping[str, object]) -> str: + parsed = _ModCDPAddCustomEvent.model_validate(registration) + name = normalizeModCDPName(parsed.name) + if not re.match(r"^[^.]+\.[^.]+$", name): + raise ValueError("name must be in Domain.event form") + event_schema = self._adapterFromOptionalSchema(parsed.event_schema, "event_schema") + with self._lock: + if event_schema.adapter is not None: + self.event_schemas[name] = event_schema.adapter + event: CustomEventRegistration = {"name": name} + if event_schema.json_schema: + event["event_schema"] = event_schema.json_schema + self.custom_events[name] = event + return name + + def customEventWireRegistration(self, name: str) -> dict[str, object]: + event = self.custom_events.get(name) + if event is None: + return {"name": name} + wire: dict[str, object] = {"name": name} + event_schema = event.get("event_schema") + if isinstance(event_schema, dict): + wire["event_schema"] = event_schema + return wire + + def customEventWireRegistrations(self) -> list[dict[str, object]]: + return [self.customEventWireRegistration(name) for name in self.custom_events] + + def addCustomMiddleware(self, registration: Mapping[str, object]) -> str: + parsed = _ModCDPAddMiddleware.model_validate(registration) + name = "*" if parsed.name is None or parsed.name == "*" else normalizeModCDPName(parsed.name) + if name != "*" and "." not in name: + raise ValueError("name must be '*' or Domain.name form") + middleware: CustomMiddlewareRegistration = {"phase": parsed.phase, "expression": parsed.expression} + if name != "*": + middleware["name"] = name + self.custom_middlewares.append(middleware) + return name + + def customMiddlewareWireRegistrations(self) -> list[CustomMiddlewareRegistration]: + return list(self.custom_middlewares) + + def customMiddlewareRegistrations(self, phase: str, name: str) -> list[CustomMiddlewareRegistration]: + return [ + middleware + for middleware in self.custom_middlewares + if middleware["phase"] == phase and (middleware.get("name") in (None, "*", name)) + ] + + def serviceWorkerCommandStep( + self, + method: str, + params: ProtocolParams | None = None, + cdp_session_id: str | None = None, + execution_context_id: int | None = None, + ) -> TranslatedStep: + command = self.custom_commands.get(method) + command_expression = command.get("expression") if command else None + command_params = dict(params or {}) + if isinstance(command_expression, str) and command_expression: + expression_builder = self.service_worker_expression_builders.get(method) + expression = expression_builder(command_params, cdp_session_id) if expression_builder else command_expression + runtime_params: dict[str, object] = { + "expression": self._serviceWorkerRuntimeExpression(method, command_params, cdp_session_id, expression), + "awaitPromise": True, + "returnByValue": True, + } + if execution_context_id is not None: + runtime_params["contextId"] = execution_context_id + return TranslatedStep(method="Runtime.evaluate", params=runtime_params, unwrap="runtime") + runtime_params: dict[str, object] = { + "functionDeclaration": ( + "async function(method, paramsJson, cdpSessionId) { " + "return JSON.stringify(await globalThis.ModCDP.handleCommand(method, JSON.parse(paramsJson), cdpSessionId)); " + "}" + ), + "arguments": [{"value": method}, {"value": json.dumps(command_params)}, {"value": cdp_session_id}], + "awaitPromise": True, + "returnByValue": True, + } + if execution_context_id is not None: + runtime_params["executionContextId"] = execution_context_id + return TranslatedStep(method="Runtime.callFunctionOn", params=runtime_params, unwrap="runtime_json") + + def _serviceWorkerRuntimeExpression( + self, + method: str, + params: ProtocolParams, + cdp_session_id: str | None, + command_expression: str, + ) -> str: + request_middlewares = ",".join(self._serviceWorkerMiddlewareExpressions("request", method)) + response_middlewares = ",".join(self._serviceWorkerMiddlewareExpressions("response", method)) + return f""" + (async () => {{ + const method = {json.dumps(method)}; + let commandParams = {json.dumps(dict(params or {}))}; + const cdpSessionId = {json.dumps(cdp_session_id)}; + const upstream = globalThis.ModCDP.client; + const downstream = globalThis.ModCDP.downstream; + const ModCDP = globalThis.ModCDP; + const cdp = {{ + upstream, + client: upstream, + downstream, + send: (method, params = {{}}, targetCdpSessionId = cdpSessionId) => + ModCDP.handleCommand(method, params, targetCdpSessionId), + }}; + const chrome = globalThis.chrome; + const runMiddlewares = async (middlewares, payload, context = {{}}) => {{ + const dispatch = async (index, value) => {{ + const middleware = middlewares[index]; + if (!middleware) return value; + let nextCalled = false; + const next = async (nextValue = value) => {{ + if (nextCalled) throw new Error("Middleware called next() more than once."); + nextCalled = true; + return await dispatch(index + 1, nextValue); + }}; + const result = await middleware(value, next, context); + if (result && result.__ModCDP_middleware_next__ === true) {{ + const nextResult = await next(result.value); + const {{ __ModCDP_middleware_next__, value: _value, ...overrides }} = result; + if (Object.keys(overrides).length === 0) return nextResult; + return nextResult && typeof nextResult === "object" && !Array.isArray(nextResult) + ? {{ ...nextResult, ...overrides }} + : overrides; + }} + return result; + }}; + return await dispatch(0, payload); + }}; + const requestMiddlewares = [{request_middlewares}]; + const responseMiddlewares = [{response_middlewares}]; + const request = {{ method, params: commandParams, cdpSessionId }}; + commandParams = await runMiddlewares(requestMiddlewares, commandParams, {{ + cdpSessionId, + request, + name: method, + phase: "request", + }}); + if (commandParams == null) throw new Error("Request middleware returned no params."); + commandParams = ModCDP.types.parseCommandParams(method, commandParams); + const handler = ({command_expression}); + let result = await handler(commandParams || {{}}, method); + result = await runMiddlewares(responseMiddlewares, result, {{ + cdpSessionId, + request: {{ ...request, params: commandParams }}, + response: {{ result }}, + name: method, + phase: "response", + }}); + return ModCDP.types.parseCommandResult(method, result); + }})() + """ + + def _serviceWorkerMiddlewareExpressions(self, phase: str, method: str) -> list[str]: + return [ + f""" + async (payload, next, context = {{}}) => {{ + const middleware = ({middleware["expression"]}); + return await middleware(payload, next, context); + }} + """ + for middleware in self.customMiddlewareRegistrations(phase, method) + ] + + def _adapterFromOptionalSchema(self, schema: object, field_name: str) -> _AdapterRegistration: + if schema is None: + return _AdapterRegistration() + if isinstance(schema, type) and issubclass(schema, BaseModel): + return _AdapterRegistration(adapter=TypeAdapter(schema), json_schema=schema.model_json_schema()) + if not isinstance(schema, Mapping): + raise TypeError(f"{field_name} must be a JSON Schema object") + json_schema = _json_object(schema) + return _AdapterRegistration(adapter=type_adapter_from_json_schema(json_schema), json_schema=json_schema) + + +def _custom_command_entries( + custom_commands: CustomCommandRegistrations | None, +) -> list[CustomCommandRegistration]: + if custom_commands is None: + return [] + if isinstance(custom_commands, dict): + entries: list[CustomCommandRegistration] = [] + for name, command in _CustomCommandConfigMap.model_validate(custom_commands).root.items(): + entry: CustomCommandRegistration = {"name": name} + if command.expression is not None: + entry["expression"] = command.expression + if command.params_schema is not None: + entry["params_schema"] = command.params_schema + if command.result_schema is not None: + entry["result_schema"] = command.result_schema + entries.append(entry) + return entries + return [dict(command) for command in custom_commands] + + +def _custom_event_entries( + custom_events: CustomEventRegistrations | None, +) -> list[str | CustomEventRegistration]: + if custom_events is None: + return [] + if isinstance(custom_events, dict): + entries: list[str | CustomEventRegistration] = [] + for name, event in _CustomEventConfigMap.model_validate(custom_events).root.items(): + entry: CustomEventRegistration = {"name": name} + if event.event_schema is not None: + entry["event_schema"] = event.event_schema + entries.append(entry) + return entries + return [event if isinstance(event, str) else dict(event) for event in custom_events] diff --git a/python/modcdp/types/__init__.py b/python/modcdp/types/__init__.py index a13bf1c5..c4d9bce9 100644 --- a/python/modcdp/types/__init__.py +++ b/python/modcdp/types/__init__.py @@ -1 +1,10 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/modcdp.ts +# - ./js/src/types/codegen.ts +# - ./js/src/types/CDPTypes.ts +# - ./go/modcdp/types/types.go +# - ./go/modcdp/types/codegen.go from .modcdp import * +from .CDPTypes import CDPTypes +from .toJSON import modCDPToJSON diff --git a/python/modcdp/types/codegen.py b/python/modcdp/types/codegen.py index 3a3544aa..aaccaf1d 100644 --- a/python/modcdp/types/codegen.py +++ b/python/modcdp/types/codegen.py @@ -1,3 +1,7 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/codegen.ts +# - ./go/modcdp/types/codegen.go """Python CDP generated-surface entrypoint.""" from __future__ import annotations diff --git a/python/modcdp/types/jsonschema.py b/python/modcdp/types/jsonschema.py index ca5cddc2..85da186e 100644 --- a/python/modcdp/types/jsonschema.py +++ b/python/modcdp/types/jsonschema.py @@ -1,3 +1,7 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/modcdp.ts +# - ./go/modcdp/client/ModCDPClient.go """Pydantic v2 runtime adapters for JSON Schema. This intentionally follows abxbus' dynamic JSON Schema loading pattern: raw @@ -7,13 +11,15 @@ from __future__ import annotations -from collections.abc import Mapping, Sequence -from typing import Annotated, Any, Literal, TypeAlias, Union, cast +from collections.abc import Callable, Mapping, Sequence +from typing import Annotated, Any, Literal, TypeAlias from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model JsonSchema: TypeAlias = Mapping[str, Any] FieldDefinition: TypeAlias = Any | tuple[Any, Any] +CreateModel: TypeAlias = Callable[..., type[BaseModel]] +_create_model: CreateModel = create_model _TYPE_MAPPING: dict[str, Any] = { "string": str, @@ -42,12 +48,12 @@ def _as_string_key_dict(value: object) -> dict[str, Any] | None: if not isinstance(value, Mapping): return None - return {key: raw_value for key, raw_value in cast(Mapping[object, Any], value).items() if isinstance(key, str)} + return {key: raw_value for key, raw_value in value.items() if isinstance(key, str)} def _as_sequence(value: object) -> Sequence[Any] | None: if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): - return cast(Sequence[Any], value) + return value return None @@ -66,11 +72,15 @@ def _iter_schema_objects(value: object) -> list[dict[str, Any]]: def _combine_union(types: list[Any]) -> Any: if not types: return Any - return cast(Any, Union).__getitem__(tuple(types)) + combined = types[0] + for item in types[1:]: + combined = combined | item + return combined def _literal_type(values: Sequence[Any]) -> Any: - return cast(Any, Literal).__getitem__(tuple(values)) + literal_getitem = getattr(Literal, "__getitem__") + return literal_getitem(tuple(values)) def _create_dynamic_model( @@ -78,8 +88,8 @@ def _create_dynamic_model( model_schema: Mapping[str, Any], fields: Mapping[str, FieldDefinition] | None = None, ) -> type[BaseModel]: - field_definitions = cast(Any, dict(fields or {})) - return create_model( + field_definitions: dict[str, Any | tuple[Any, Any]] = dict(fields or {}) + return _create_model( model_name, __config__=ConfigDict(extra="forbid" if model_schema.get("additionalProperties") is False else "allow"), __doc__=str(model_schema.get("description", "")), diff --git a/python/modcdp/types/modcdp.py b/python/modcdp/types/modcdp.py index 16417854..0af7dd6c 100644 --- a/python/modcdp/types/modcdp.py +++ b/python/modcdp/types/modcdp.py @@ -1,77 +1,190 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/modcdp.ts +# - ./go/modcdp/types/types.go from __future__ import annotations from collections.abc import Callable, Mapping from queue import Queue -from typing import Any, Literal, Protocol, TypeAlias, TypedDict +from typing import Literal, Protocol, TypeAlias, TypeGuard + +from pydantic import BaseModel, ConfigDict, Field -from typing_extensions import NotRequired JsonPrimitive: TypeAlias = None | bool | int | float | str JsonValue: TypeAlias = JsonPrimitive | list["JsonValue"] | dict[str, "JsonValue"] JsonObject: TypeAlias = dict[str, JsonValue] +ModCDPPayloadSchemaSpec: TypeAlias = object + +CdpCommandParams: TypeAlias = dict[str, object] +CdpCommandResult: TypeAlias = dict[str, object] +CdpEventParams: TypeAlias = dict[str, object] -ProtocolParams: TypeAlias = Mapping[str, JsonValue] -ProtocolResult: TypeAlias = dict[str, JsonValue] -ProtocolPayload: TypeAlias = dict[str, JsonValue] +ProtocolParams: TypeAlias = Mapping[str, object] +ProtocolResult: TypeAlias = Mapping[str, object] +ProtocolPayload: TypeAlias = Mapping[str, object] MessageParams: TypeAlias = Mapping[str, object] ModCDPRoutes: TypeAlias = dict[str, str] +RuntimeCallFunctionOnParams: TypeAlias = dict[str, object] +CdpMessage: TypeAlias = dict[str, object] + + +def _isObjectMap(value: object) -> TypeGuard[dict[str, object]]: + return isinstance(value, dict) and all(isinstance(key, str) for key in value) + + +class ModCDPModel(BaseModel): + model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True, validate_assignment=True) + + def __getitem__(self, key: str) -> object: + return getattr(self, key) + + def __setitem__(self, key: str, value: object) -> None: + setattr(self, key, value) + def __contains__(self, key: object) -> bool: + return isinstance(key, str) and key in self.model_fields_set -class _ModCDPAddCustomCommandRequired(TypedDict): + def get(self, key: str, default: object = None) -> object: + value = getattr(self, key, default) + return default if value is None else value + + def __eq__(self, other: object) -> bool: + if isinstance(other, Mapping): + return self.model_dump(exclude_none=True, by_alias=True) == dict(other) + return super().__eq__(other) + + +class RuntimeBindingCalledEvent(ModCDPModel): name: str + payload: str + executionContextId: int | None = None -class ModCDPAddCustomCommandParams(_ModCDPAddCustomCommandRequired, total=False): - expression: str | None - params_schema: JsonValue - result_schema: JsonValue +class TargetAttachedToTargetEvent(ModCDPModel): + sessionId: str + targetInfo: dict[str, str] + waitingForDebugger: bool -class _ModCDPAddCustomEventObjectRequired(TypedDict): +class ModCDPAddCustomCommandParams(ModCDPModel): name: str + expression: str | None = None + params_schema: ModCDPPayloadSchemaSpec | None = None + result_schema: ModCDPPayloadSchemaSpec | None = None -class ModCDPAddCustomEventObjectParams(_ModCDPAddCustomEventObjectRequired, total=False): - event_schema: JsonValue +class ModCDPAddCustomEventObjectParams(ModCDPModel): + name: str + event_schema: ModCDPPayloadSchemaSpec | None = None ModCDPAddCustomEventParams: TypeAlias = str | ModCDPAddCustomEventObjectParams -class _ModCDPAddMiddlewareRequired(TypedDict): +class ModCDPAddMiddlewareParams(ModCDPModel): phase: Literal["request", "response", "event"] expression: str + name: str | None = None -class ModCDPAddMiddlewareParams(_ModCDPAddMiddlewareRequired, total=False): - name: str +class ModCDPEvaluateParams(ModCDPModel): + expression: str + params: dict[str, object] | None = None + cdpSessionId: str | None = None -class ModCDPPingLatency(TypedDict): - sent_at: int +class ModCDPPingParams(ModCDPModel): + sent_at: int | float | None = None + + +class ModCDPPongEvent(ModCDPModel): + sent_at: int | float + received_at: int | float + from_: str = Field(alias="from") + + +class ModCDPPingLatency(ModCDPModel): + sent_at: int | float received_at: int | float | None - returned_at: int - round_trip_ms: int + returned_at: int | float + round_trip_ms: int | float service_worker_ms: int | float | None return_path_ms: int | float | None -class ModCDPConnectTiming(TypedDict): +class ModCDPGetTopologyParams(ModCDPModel): + rootTargetId: str | None = None + targetId: str | None = None + active: bool | None = None + + +class ModCDPTopologyFrame(ModCDPModel): + targetId: str + url: str | None = None + parentFrameId: str | None = None + outerBackendNodeId: int | None = None + + +class ModCDPTopologyDomRoot(ModCDPModel): + kind: Literal["document", "shadow"] + frameId: str + outerBackendNodeId: int | None = None + innerBackendNodeId: int | None = None + mode: Literal["open", "closed", "user-agent"] | None = None + executionContextId: int | None = None + uniqueContextId: str | None = None + + +class ModCDPTopologyTarget(ModCDPModel): + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True, validate_assignment=True) + + targetId: str + type: str + title: str | None = None + url: str | None = None + attached: bool | None = None + parentId: str | None = None + parentFrameId: str | None = None + sessionId: str | None = None + + +class ModCDPTopologyExecutionContext(ModCDPModel): + id: int + sessionId: str | None + targetId: str + world: str + origin: str | None = None + name: str | None = None + uniqueId: str | None = None + auxData: dict[str, object] | None = None + frameId: str | None = None + + +class ModCDPTopology(ModCDPModel): + objectGroup: str + rootFrameId: str + frames: dict[str, ModCDPTopologyFrame] + roots: dict[str, ModCDPTopologyDomRoot] + targets: dict[str, ModCDPTopologyTarget] + contexts: dict[str, ModCDPTopologyExecutionContext] + + +class ModCDPConnectTiming(ModCDPModel): started_at: int upstream_mode: str | None - upstream_endpoint_kind: Literal["raw_cdp", "modcdp_server"] transport_started_at: int transport_connected_at: int transport_duration_ms: int - injector_source: NotRequired[str | None] - injector_started_at: NotRequired[int] - injector_completed_at: NotRequired[int] - injector_duration_ms: NotRequired[int] connected_at: int duration_ms: int + injector_source: str | None = None + injector_started_at: int | None = None + injector_completed_at: int | None = None + injector_duration_ms: int | None = None -class ModCDPCommandTiming(TypedDict): +class ModCDPCommandTiming(ModCDPModel): method: str target: str started_at: int @@ -79,69 +192,147 @@ class ModCDPCommandTiming(TypedDict): duration_ms: int -class ModCDPRawTiming(TypedDict): +class ModCDPRawTiming(ModCDPModel): method: str started_at: int completed_at: int duration_ms: int -class ModCDPServerConfig(TypedDict, total=False): - server_loopback_cdp_url: str | None - server_routes: ModCDPRoutes - server_cdp_send_timeout_ms: int - server_loopback_execution_context_timeout_ms: int - server_ws_connect_error_settle_timeout_ms: int - server_downstream_client_timeout_ms: int - server_close_browser_on_downstream_disconnect: bool - server_browser_token: str | None - custom_commands: list[ModCDPAddCustomCommandParams] - custom_events: list[ModCDPAddCustomEventObjectParams] - custom_middlewares: list[ModCDPAddMiddlewareParams] +class ModCDPRouterConfig(ModCDPModel): + router_routes: ModCDPRoutes = Field(default_factory=dict) + loopback_execution_context_timeout_ms: int = Field(default=10_000, gt=0) + + +class ModCDPClientConfig(ModCDPModel): + client_hydrate_aliases: bool = True + client_mirror_upstream_events: bool = True + client_cdp_send_timeout_ms: int = Field(default=10_000, gt=0) + client_event_wait_timeout_ms: int = Field(default=10_000, gt=0) + client_heartbeat_interval_ms: int = Field(default=250, gt=0) + + +class ModCDPDownstreamConfig(ModCDPModel): + downstream_client_timeout_ms: int = Field(default=1_000, gt=0) + downstream_close_browser_on_disconnect: bool = False + + +class ModCDPUpstreamConfig(ModCDPModel): + upstream_mode: Literal["ws"] = "ws" + upstream_ws_cdp_url: str | None = None + upstream_ws_connect_error_settle_timeout_ms: int = Field(default=250, gt=0) + upstream_cdp_send_timeout_ms: int = Field(default=10_000, gt=0) + + +class ModCDPLauncherConfig(ModCDPModel): + launcher_mode: Literal["local", "remote", "bb", "none"] = "none" + launcher_local_executable_path: str | None = None + launcher_local_user_data_dir: str | None = None + launcher_remote_cdp_url: str | None = None + launcher_local_cdp_listen_port: int | None = Field(default=None, ge=0) + launcher_local_headless: bool | None = None + launcher_local_sandbox: bool | None = None + launcher_local_args: list[str] = Field(default_factory=list) + launcher_local_extra_args: list[str] = Field(default_factory=list) + launcher_local_loopback_cdp: bool = False + launcher_local_cleanup_user_data_dir: bool = False + launcher_local_chrome_ready_timeout_ms: int = Field(default=45_000, gt=0) + launcher_local_chrome_ready_poll_interval_ms: int = Field(default=100, gt=0) + launcher_bb_api_key: str | None = None + launcher_bb_base_url: str = "https://api.browserbase.com" + launcher_bb_session_id: str | None = None + launcher_bb_keep_alive: bool = False + launcher_bb_close_session_on_close: bool | None = None + launcher_bb_region: str | None = None + launcher_bb_timeout: int | None = Field(default=None, gt=0) + launcher_bb_extension_id: str | None = None + launcher_bb_browser_settings: dict[str, object] = {"viewport": {"width": 1288, "height": 711}} + launcher_bb_user_metadata: dict[str, object] = Field(default_factory=dict) + launcher_bb_session_create_params: dict[str, object] = {"userMetadata": {}} + + +class ModCDPServerConfig(ModCDPModel): + upstream: ModCDPUpstreamConfig | None = None + router: ModCDPRouterConfig | None = None + client_config: ModCDPClientConfig | None = None + downstream: ModCDPDownstreamConfig | None = None + server_browser_token: str | None = None + custom_commands: list[ModCDPAddCustomCommandParams] | None = None + custom_events: list[ModCDPAddCustomEventObjectParams] | None = None + custom_middlewares: list[ModCDPAddMiddlewareParams] | None = None + + +ModCDPConfigureParams: TypeAlias = ModCDPServerConfig +ModCDPCommandParams: TypeAlias = ( + ModCDPEvaluateParams + | ModCDPGetTopologyParams + | ModCDPAddCustomCommandParams + | ModCDPAddCustomEventParams + | ModCDPAddMiddlewareParams + | ModCDPConfigureParams + | ModCDPPingParams + | dict[str, object] +) + + +class ModCDPOkResponse(ModCDPModel): + ok: bool + + +ModCDPCommandResult: TypeAlias = ModCDPOkResponse | dict[str, JsonValue] +ModCDPEvaluateResponse: TypeAlias = JsonValue +ModCDPGetTopologyResponse: TypeAlias = ModCDPTopology + + +class ModCDPAddCustomCommandResponse(ModCDPModel): + name: str + registered: bool -RuntimeCallFunctionOnParams: TypeAlias = dict[str, JsonValue] +class ModCDPAddCustomEventResponse(ModCDPModel): + name: str + registered: bool -class _TranslatedStepRequired(TypedDict): - method: str +class ModCDPAddMiddlewareResponse(ModCDPModel): + name: str + phase: Literal["request", "response", "event"] + registered: bool -class TranslatedStep(_TranslatedStepRequired, total=False): - params: MessageParams - sessionId: str | None - unwrap: Literal["runtime", "runtime_json"] +ModCDPConfigureResponse: TypeAlias = Mapping[str, object] +ModCDPPingResponse: TypeAlias = ModCDPOkResponse -class TranslatedCommand(TypedDict): - route: str - target: Literal["direct_cdp", "service_worker"] - steps: list[TranslatedStep] +class ModCDPBindingPayload(ModCDPModel): + event: str + data: object + cdpSessionId: str | None = None -class CdpError(TypedDict, total=False): - message: str +class TranslatedStep(ModCDPModel): + method: str + params: MessageParams | None = None + sessionId: str | None = None + unwrap: Literal["runtime", "runtime_json"] | None = None -class CdpMessage(TypedDict, total=False): - id: int - method: str - params: MessageParams - sessionId: str - result: ProtocolResult - error: CdpError +class TranslatedCommand(ModCDPModel): + route: str + target: Literal["direct_cdp", "service_worker"] + steps: list[TranslatedStep] -class TargetInfo(TypedDict): +class TargetInfo(ModCDPModel): targetId: str type: str url: str -class ExtensionProbe(TypedDict): - extension_id: str +class ExtensionProbe(ModCDPModel): + extension_id: str | None = None target_id: str - url: str + url: str | None = None session_id: str @@ -149,18 +340,24 @@ class ExtensionInfo(ExtensionProbe): source: str -class BorrowedExtensionInfo(ExtensionInfo, total=False): - has_tabs: bool - has_debugger: bool - - -class UnwrappedModCDPEvent(TypedDict): +class UnwrappedModCDPEvent(ModCDPModel): event: str - data: ProtocolPayload + data: ProtocolPayload | object sessionId: str | None -Handler: TypeAlias = Callable[[Any], Any] +class LaunchedBrowser(ModCDPModel): + cdp_url: str | None + close: Callable[[], object] + loopback_cdp_url: str | None = None + profile_dir: str | None = None + browserbase_session_id: str | None = None + browserbase_session_url: str | None = None + browserbase_debug_url: str | None = None + cdp_listen_port: int | None = None + + +Handler: TypeAlias = Callable[..., object] PendingEntry: TypeAlias = tuple[str, Queue[CdpMessage]] diff --git a/python/modcdp/types/toJSON.py b/python/modcdp/types/toJSON.py new file mode 100644 index 00000000..2dfdbb46 --- /dev/null +++ b/python/modcdp/types/toJSON.py @@ -0,0 +1,53 @@ +# MODCDP_TRANSLATE: KEEP THIS FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# Keep all shapes, signatures, behavior, and tests 1:1 in sync with: +# - ./js/src/types/toJSON.ts +# - ./go/modcdp/types/toJSON.go +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Protocol + +from pydantic import BaseModel + + +class ModCDPJSONChild(Protocol): + def toJSON(self) -> object: ... + + +def modCDPToJSON(instance: object, config: Mapping[str, Any] | None = None) -> dict[str, object]: + json_config = dict(config or {}) + children: dict[str, object] = {} + for key, child in dict(json_config.get("children") or {}).items(): + if child is not None: + children[str(key)] = child.toJSON() + result: dict[str, object] = { + "type": type(instance).__name__, + "config": _jsonable(json_config.get("config", getattr(instance, "config", {}))), + "state": { + **_simple_state(vars(instance)), + **_simple_state(dict(json_config.get("state") or {})), + }, + } + if children: + result["children"] = children + return result + + +def _jsonable(value: object) -> object: + if isinstance(value, BaseModel): + return value.model_dump(mode="json") + if isinstance(value, Mapping): + return {str(key): _jsonable(item) for key, item in value.items()} + if isinstance(value, list): + return [_jsonable(item) for item in value] + return value + + +def _simple_state(input: Mapping[str, object]) -> dict[str, str | int | float | bool]: + state: dict[str, str | int | float | bool] = {} + for key, value in input.items(): + if key.startswith("_") or key == "config" or "token" in key or "secret" in key or "api_key" in key: + continue + if isinstance(value, str | int | float | bool): + state[key] = value + return state diff --git a/python/tests/test_AutoSessionRouter.py b/python/tests/test_AutoSessionRouter.py index 0b9cd57c..b9f8651a 100644 --- a/python/tests/test_AutoSessionRouter.py +++ b/python/tests/test_AutoSessionRouter.py @@ -1,147 +1,150 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.AutoSessionRouter.ts +# - ./go/modcdp/router/AutoSessionRouter_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import json +import glob +import os +import re +import sys import threading +import time import unittest +from pathlib import Path from queue import Queue -from typing import Any -from websocket import create_connection - -from modcdp.router.AutoSessionRouter import AutoSessionRouter -from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher +from modcdp import ModCDPClient + + +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + + +ROOT = Path(__file__).resolve().parents[2] +EXTENSION_PATH = ROOT / "dist" / "extension" +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() class AutoSessionRouterTests(unittest.TestCase): - def test_rejects_pending_execution_context_waiters_when_session_detaches(self) -> None: - router = AutoSessionRouter(lambda _method, _params, _session_id: {}, lambda: 5_000) - result: Queue[int | BaseException] = Queue() - threading.Thread( - target=lambda: _put_result(result, lambda: router.waitForExecutionContext("detached-session", 5_000)), - daemon=True, - ).start() - - router.recordProtocolEvent( - "Target.attachedToTarget", - {"sessionId": "detached-session", "targetInfo": {"targetId": "target-1", "type": "page"}}, - None, - ) - router.recordProtocolEvent("Target.detachedFromTarget", {"sessionId": "detached-session"}, None) - router.recordProtocolEvent( - "Runtime.executionContextCreated", - {"context": {"id": 42}}, - "detached-session", - ) - - error = result.get(timeout=1) - self.assertIsInstance(error, RuntimeError) - self.assertIn("Runtime execution context wait cancelled because session detached-session detached.", str(error)) - self.assertIsNone(router.sessionIdForTarget("target-1")) - self.assertNotIn("detached-session", router.execution_contexts) - - def test_bounds_detached_session_guards_and_clears_them_when_session_reattaches(self) -> None: - router = AutoSessionRouter(lambda _method, _params, _session_id: {}, lambda: 5_000) - - for index in range(1034): - router.recordProtocolEvent("Target.detachedFromTarget", {"sessionId": f"detached-session-{index}"}, None) - - self.assertLessEqual(len(router._detached_sessions), 1024) - - recent_session_id = "detached-session-1033" - router.recordProtocolEvent("Runtime.executionContextCreated", {"context": {"id": 42}}, recent_session_id) - self.assertNotIn(recent_session_id, router.execution_contexts) - - router.recordProtocolEvent( - "Target.attachedToTarget", - {"sessionId": recent_session_id, "targetInfo": {"targetId": "target-reattached", "type": "page"}}, - None, + def test_autosessionrouter_tracks_real_target_sessions_and_execution_contexts_from_live_cdp_events(self) -> None: + cdp = ModCDPClient( + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, ) - router.recordProtocolEvent("Runtime.executionContextCreated", {"context": {"id": 43}}, recent_session_id) - - self.assertEqual(router.sessionIdForTarget("target-reattached"), recent_session_id) - self.assertEqual(router.execution_contexts[recent_session_id], 43) - - def test_tracks_real_target_sessions_and_execution_contexts(self) -> None: - chrome = LocalBrowserLauncher({"headless": True}).launch() - ws = create_connection(str(chrome["cdp_url"]), timeout=10) - lock = threading.Lock() - next_id = 0 - pending: dict[int, Queue[dict[str, Any]]] = {} - closed = False - - def send(method: str, params: dict[str, Any] | None = None, session_id: str | None = None) -> dict[str, Any]: - nonlocal next_id - with lock: - next_id += 1 - msg_id = next_id - done: Queue[dict[str, Any]] = Queue() - pending[msg_id] = done - message = {"id": msg_id, "method": method, "params": params or {}} - if session_id: - message["sessionId"] = session_id - ws.send(json.dumps(message)) - response = done.get(timeout=10) - if response.get("error"): - raise RuntimeError(json.dumps(response["error"])) - result = response.get("result") - return result if isinstance(result, dict) else {} - - router = AutoSessionRouter(send, lambda: 30_000) - - def reader() -> None: - while not closed: - try: - raw = ws.recv() - except Exception: - return - if not raw: - return - try: - message = json.loads(raw) - except json.JSONDecodeError: - return - if isinstance(message.get("id"), int): - done = pending.pop(message["id"], None) - if done is not None: - done.put(message) - continue - method = message.get("method") - if isinstance(method, str): - session_id = message.get("sessionId") - router.recordProtocolEvent(method, message.get("params"), session_id if isinstance(session_id, str) else None) - - thread = threading.Thread(target=reader, daemon=True) - thread.start() target_id: str | None = None + pending_target_id: str | None = None try: - send("Target.setAutoAttach", {"autoAttach": True, "waitForDebuggerOnStart": False, "flatten": True}) - send("Target.setDiscoverTargets", {"discover": True}) - created = send("Target.createTarget", {"url": "about:blank#modcdp-auto-session-router"}) - target_id = str(created["targetId"]) - session_id = _wait_for(lambda: router.sessionIdForTarget(target_id)) + cdp.connect() + created = cdp.Target.createTarget(url="about:blank#modcdp-auto-session-router") + target_id = str(created.targetId) + session_id = _wait_for(lambda: cdp.router.sessionId_from_targetId.get(str(target_id))) + context_result: Queue[int | BaseException] = Queue() threading.Thread( - target=lambda: _put_result(context_result, lambda: router.waitForExecutionContext(session_id, 30_000)), + target=lambda: _put_result(context_result, lambda: cdp.router.waitForExecutionContext(session_id, 30_000)), daemon=True, ).start() - send("Runtime.enable", {}, session_id) + cdp.send("Runtime.enable", {}, session_id) context_id = context_result.get(timeout=35) if isinstance(context_id, BaseException): raise context_id self.assertIsInstance(context_id, int) - self.assertEqual(router.execution_contexts[session_id], context_id) - - send("Target.detachFromTarget", {"sessionId": session_id}) - _wait_for(lambda: None if router.sessionIdForTarget(target_id) else "detached") + self.assertTrue( + any( + context.get("sessionId") == session_id and context.get("id") == context_id + for context in cdp.router.contexts.values() + ) + ) + + cdp.Target.detachFromTarget(sessionId=session_id) + _expect_eventually(lambda: self.assertIsNone(cdp.router.sessionId_from_targetId.get(str(target_id)))) + self.assertFalse(any(context.get("sessionId") == session_id for context in cdp.router.contexts.values())) + try: + cdp.Target.closeTarget(targetId=target_id) + except Exception: + pass + target_id = None + + pending_created = cdp.Target.createTarget(url="about:blank#modcdp-auto-session-router-pending-context") + pending_target_id = str(pending_created.targetId) + pending_session_id = _wait_for(lambda: cdp.router.sessionId_from_targetId.get(str(pending_target_id))) + pending_result: Queue[int | BaseException] = Queue() + threading.Thread( + target=lambda: _put_result( + pending_result, + lambda: cdp.router.waitForExecutionContext(pending_session_id, 30_000), + ), + daemon=True, + ).start() + cdp.Target.detachFromTarget(sessionId=pending_session_id) + pending_error = pending_result.get(timeout=35) + self.assertIsInstance(pending_error, RuntimeError) + self.assertIn( + f"Runtime execution context wait cancelled because session {pending_session_id} detached.", + str(pending_error), + ) + _expect_eventually(lambda: self.assertIsNone(cdp.router.sessionId_from_targetId.get(str(pending_target_id)))) + try: + cdp.Target.closeTarget(targetId=pending_target_id) + except Exception: + pass + pending_target_id = None finally: if target_id: try: - send("Target.closeTarget", {"targetId": target_id}) + cdp.Target.closeTarget(targetId=target_id) + except Exception: + pass + if pending_target_id: + try: + cdp.Target.closeTarget(targetId=pending_target_id) except Exception: pass - closed = True - ws.close() - chrome["close"]() + cdp.close() def _put_result(queue: Queue[int | BaseException], fn) -> None: @@ -151,20 +154,30 @@ def _put_result(queue: Queue[int | BaseException], fn) -> None: queue.put(error) -def _wait_for(fn, timeout_s: float = 5) -> str: - deadline = threading.Event() - timer = threading.Timer(timeout_s, deadline.set) - timer.start() - try: - while not deadline.is_set(): - value = fn() - if value: - return value - deadline.wait(0.05) - finally: - timer.cancel() +def _wait_for(fn, timeout_s: float = 10) -> str: + deadline = time.time() + timeout_s + while time.time() < deadline: + value = fn() + if value: + return value + time.sleep(0.1) raise TimeoutError("timed out waiting for condition") +def _expect_eventually(assertion, timeout_s: float = 10) -> None: + deadline = time.time() + timeout_s + last_error: BaseException | None = None + while time.time() < deadline: + try: + assertion() + return + except BaseException as error: + last_error = error + time.sleep(0.1) + if last_error is not None: + raise last_error + raise TimeoutError("timed out waiting for assertion") + + if __name__ == "__main__": unittest.main() diff --git a/python/tests/test_BBBrowserExtensionInjector.py b/python/tests/test_BBBrowserExtensionInjector.py deleted file mode 100644 index ddf55ca4..00000000 --- a/python/tests/test_BBBrowserExtensionInjector.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -import unittest -from unittest.mock import patch - -from modcdp import ModCDPClient -from modcdp.injector.BBBrowserExtensionInjector import BBBrowserExtensionInjector - -HERE = Path(__file__).resolve().parent -EXTENSION_PATH = HERE.parents[1] / "dist" / "extension" - - -class BBBrowserExtensionInjectorTests(unittest.TestCase): - def test_prepares_default_packaged_extension_zip_when_path_is_omitted(self) -> None: - injector = BBBrowserExtensionInjector() - try: - with patch.object(injector, "_uploadExtension", return_value="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") as upload: - injector.prepare() - self.assertTrue(str(injector.options.get("injector_extension_path", "")).endswith("extension.zip")) - self.assertTrue(str(injector.zip_path or "").endswith("extension.zip")) - upload.assert_called_once_with(injector.zip_path) - self.assertEqual(injector.getLauncherConfig(), {"injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}) - finally: - injector.close() - - def test_uploads_real_extension_and_launches_browserbase_browser_with_it_installed(self) -> None: - if not os.environ.get("BROWSERBASE_API_KEY", "").strip(): - self.fail("BROWSERBASE_API_KEY is required for live Browserbase tests") - cdp = ModCDPClient( - launcher={ - "launcher_mode": "bb", - "launcher_options": { - "timeout": 120, - **({"region": os.environ["BROWSERBASE_REGION"]} if os.environ.get("BROWSERBASE_REGION") else {}), - }, - }, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "inject", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - - try: - cdp.connect() - self.assertEqual(cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, "bb") - self.assertIsInstance(cdp.extension_id, str) - service_worker_url = cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')") - self.assertRegex(str(service_worker_url), r"^chrome-extension://[a-z]{32}/modcdp/service_worker\.js$") - finally: - cdp.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_BBBrowserLauncher.py b/python/tests/test_BBBrowserLauncher.py new file mode 100644 index 00000000..9646a256 --- /dev/null +++ b/python/tests/test_BBBrowserLauncher.py @@ -0,0 +1,113 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.BBBrowserLauncher.ts +# - ./go/modcdp/launcher/BBBrowserLauncher_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import json +import os +import time +import unittest +import urllib.request + +from modcdp.launcher.BBBrowserLauncher import BBBrowserLauncher +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport + + +LIVE_BROWSERBASE_TIMEOUT_S = 120 + + +class BBBrowserLauncherTests(unittest.TestCase): + def test_creates_verifies_resumes_and_releases_a_real_browserbase_browser_session(self) -> None: + if not os.environ.get("BROWSERBASE_API_KEY", "").strip(): + self.fail("BROWSERBASE_API_KEY is required for live Browserbase tests") + launcher = BBBrowserLauncher( + { + "launcher_bb_timeout": 120, + **({"launcher_bb_region": os.environ["BROWSERBASE_REGION"]} if os.environ.get("BROWSERBASE_REGION") else {}), + "launcher_bb_browser_settings": { + "viewport": {"width": 900, "height": 700}, + "recordSession": False, + }, + "launcher_bb_user_metadata": { + "modcdp_launcher_test": "BBBrowserLauncher", + }, + } + ) + browser = launcher.launch() + resumed = None + transport = None + session_id = browser.browserbase_session_id + try: + if not isinstance(session_id, str): + self.fail(f"browserbase_session_id = {session_id!r}") + self.assertIn(session_id, browser.browserbase_session_url or "") + cdp_url = browser.cdp_url + if not isinstance(cdp_url, str): + self.fail(f"cdp_url = {cdp_url!r}") + self.assertRegex(cdp_url, r"^wss://") + transport = WSUpstreamTransport({"upstream_ws_cdp_url": cdp_url}) + transport.connect() + expect_cdp_browser_surface(transport) + + retrieved = retrieve_browserbase_session(session_id) + self.assertEqual(retrieved.get("id"), session_id) + self.assertEqual(retrieved.get("status"), "RUNNING") + + resumed = BBBrowserLauncher( + { + "launcher_bb_session_id": session_id, + "launcher_bb_close_session_on_close": False, + } + ).launch() + self.assertEqual(resumed.browserbase_session_id, session_id) + self.assertRegex(resumed.cdp_url or "", r"^wss://") + expect_cdp_browser_surface(transport) + finally: + if transport is not None: + transport.close() + if resumed is not None: + resumed.close() + browser.close() + browser.close() + + deadline = time.time() + 30 + while time.time() < deadline: + if retrieve_browserbase_session(session_id).get("status") != "RUNNING": + return + time.sleep(1) + self.fail("Browserbase session did not leave RUNNING status after release") + + +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep the setup semantics above 1:1 with translated tests; helpers here only call real Browserbase APIs and real CDP endpoints. +def retrieve_browserbase_session(session_id: str) -> dict[str, object]: + request = urllib.request.Request( + browserbase_api_url(f"/v1/sessions/{session_id}"), + headers={"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"]}, + ) + with urllib.request.urlopen(request, timeout=60) as response: + if response.status < 200 or response.status >= 300: + raise AssertionError(f"Browserbase session fetch returned {response.status}") + parsed: object = json.loads(response.read()) + if not isinstance(parsed, dict): + raise AssertionError(f"Browserbase session fetch returned {parsed!r}") + return {str(key): value for key, value in parsed.items()} + + +def browserbase_api_url(pathname: str) -> str: + base_url = os.environ.get("BROWSERBASE_BASE_URL", "https://api.browserbase.com").rstrip("/") + return f"{base_url}/{pathname.lstrip('/')}" + + +def expect_cdp_browser_surface(transport: WSUpstreamTransport) -> None: + result = transport.send("Browser.getVersion") + product = result.get("product") + if not isinstance(product, str) or ("Chrome" not in product and "Chromium" not in product): + raise AssertionError(f"Browser.getVersion result = {result!r}") + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_BBExtensionInjector.py b/python/tests/test_BBExtensionInjector.py new file mode 100644 index 00000000..c2f89214 --- /dev/null +++ b/python/tests/test_BBExtensionInjector.py @@ -0,0 +1,50 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.BBExtensionInjector.ts +# - ./go/modcdp/injector/BBExtensionInjector_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import os +from pathlib import Path +import unittest + +from modcdp import ModCDPClient + +HERE = Path(__file__).resolve().parent +EXTENSION_PATH = HERE.parents[1] / "dist" / "extension" + + +class BBExtensionInjectorTests(unittest.TestCase): + def test_uploads_the_real_extension_and_launches_a_browserbase_browser_with_it_installed(self) -> None: + if not os.environ.get("BROWSERBASE_API_KEY", "").strip(): + self.fail("BROWSERBASE_API_KEY is required for live Browserbase tests") + cdp = ModCDPClient( + launcher={ + "launcher_mode": "bb", + "launcher_bb_timeout": 120, + **({"launcher_bb_region": os.environ["BROWSERBASE_REGION"]} if os.environ.get("BROWSERBASE_REGION") else {}), + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "bb", + "injector_bb_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + ) + + try: + cdp.connect() + self.assertEqual(cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, "bb") + assert cdp.injector is not None + self.assertIsInstance(cdp.injector.extension_id, str) + service_worker_url = cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')") + self.assertRegex(str(service_worker_url), r"^chrome-extension://[a-z]{32}/modcdp/service_worker\.js$") + finally: + cdp.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_BorrowedExtensionInjector.py b/python/tests/test_BorrowedExtensionInjector.py deleted file mode 100644 index 38baa763..00000000 --- a/python/tests/test_BorrowedExtensionInjector.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path - -from modcdp import ModCDPClient - - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" - - -class BorrowedExtensionInjectorTests(unittest.TestCase): - def test_bootstraps_modcdp_inside_live_extension_service_worker(self) -> None: - owner = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - try: - owner.connect() - cdp = ModCDPClient( - launcher={"launcher_mode": "remote"}, - upstream={"upstream_mode": "ws", "upstream_cdp_url": owner.cdp_url}, - injector={ - "injector_mode": "borrow", - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - try: - cdp.connect() - self.assertEqual(cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, "borrowed") - self.assertEqual(cdp.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf") - self.assertEqual( - cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')"), - "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", - ) - finally: - cdp.close() - finally: - owner.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_BrowserLauncher.py b/python/tests/test_BrowserLauncher.py index 073b56aa..1e64514c 100644 --- a/python/tests/test_BrowserLauncher.py +++ b/python/tests/test_BrowserLauncher.py @@ -1,93 +1,55 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.BrowserLauncher.ts +# - ./go/modcdp/launcher/BrowserLauncher_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import json import unittest -from unittest.mock import patch -from modcdp.launcher.BrowserLauncher import BrowserLauncher, resolveCdpWebSocketUrl +from modcdp.launcher.BrowserLauncher import BrowserLauncher class BrowserLauncherTests(unittest.TestCase): - def test_merges_launch_config_and_exposes_transport_and_injector_config(self) -> None: + def test_merges_config_and_exposes_upstream_config(self) -> None: launcher = BrowserLauncher( { - "cdp_url": "ws://127.0.0.1:9222/devtools/browser/initial", - "user_data_dir": "/tmp/modcdp-browser-launcher", - "browserbase_api_key": "test-key", - "injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "args": ["--load-extension=/tmp/args-one"], - "extra_args": ["--load-extension=/tmp/one"], + "launcher_remote_cdp_url": "ws://127.0.0.1:9222/devtools/browser/initial", + "launcher_local_user_data_dir": "/tmp/modcdp-browser-launcher", } ) launcher.update( { - "cdp_url": "ws://127.0.0.1:9222/devtools/browser/updated", - "args": ["--load-extension=/tmp/args-two", "--lang=en-US"], - "extra_args": ["--load-extension=/tmp/two", "--window-size=900,700"], + "launcher_remote_cdp_url": "ws://127.0.0.1:9222/devtools/browser/updated", } ) self.assertEqual( - launcher.options.get("args"), - ["--lang=en-US", "--load-extension=/tmp/args-one,/tmp/args-two"], - ) - self.assertEqual( - launcher.options.get("extra_args"), - ["--window-size=900,700", "--load-extension=/tmp/one,/tmp/two"], - ) - self.assertEqual( - { - "cdp_url": launcher.getTransportConfig()["cdp_url"], - "user_data_dir": launcher.getTransportConfig()["user_data_dir"], - }, { - "cdp_url": "ws://127.0.0.1:9222/devtools/browser/updated", - "user_data_dir": "/tmp/modcdp-browser-launcher", - }, - ) - self.assertEqual( - { - "injector_browserbase_api_key": launcher.getInjectorConfig()["injector_browserbase_api_key"], - "injector_extension_id": launcher.getInjectorConfig()["injector_extension_id"], + "upstream_ws_cdp_url": launcher.configForUpstream()["upstream_ws_cdp_url"], }, { - "injector_browserbase_api_key": "test-key", - "injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "upstream_ws_cdp_url": "ws://127.0.0.1:9222/devtools/browser/updated", }, ) + self.assertEqual(launcher.config.launcher_local_user_data_dir, "/tmp/modcdp-browser-launcher") with self.assertRaisesRegex(NotImplementedError, "BrowserLauncher.launch is not implemented"): launcher.launch() - def test_resolve_cdp_websocket_url_accepts_host_http_https_ws_and_wss_shapes(self) -> None: - self.assertEqual( - resolveCdpWebSocketUrl("ws://127.0.0.1:9222/devtools/browser/one"), - "ws://127.0.0.1:9222/devtools/browser/one", + def test_carries_remote_cdp_config_separately_from_launch_args(self) -> None: + launcher = BrowserLauncher( + { + "launcher_remote_cdp_url": "ws://127.0.0.1:9222/devtools/browser/initial", + } ) - self.assertEqual( - resolveCdpWebSocketUrl("wss://example.test/devtools/browser/two"), - "wss://example.test/devtools/browser/two", + launcher.update( + { + "launcher_remote_cdp_url": "ws://127.0.0.1:9222/devtools/browser/updated", + } ) - class FakeResponse: - def __init__(self, cdp_url: str) -> None: - self.cdp_url = cdp_url - - def __enter__(self) -> "FakeResponse": - return self - - def __exit__(self, *_args: object) -> None: - return None - - def read(self) -> bytes: - return json.dumps({"webSocketDebuggerUrl": self.cdp_url}).encode() - - with patch("urllib.request.urlopen", return_value=FakeResponse("ws://127.0.0.1:9222/devtools/browser/three")) as urlopen: - self.assertEqual(resolveCdpWebSocketUrl("127.0.0.1:9222"), "ws://127.0.0.1:9222/devtools/browser/three") - urlopen.assert_called_once_with("http://127.0.0.1:9222/json/version", timeout=10) - - with patch("urllib.request.urlopen", return_value=FakeResponse("wss://example.test/devtools/browser/four")) as urlopen: - self.assertEqual(resolveCdpWebSocketUrl("https://example.test"), "wss://example.test/devtools/browser/four") - urlopen.assert_called_once_with("https://example.test/json/version", timeout=10) + self.assertEqual(launcher.config.launcher_remote_cdp_url, "ws://127.0.0.1:9222/devtools/browser/updated") if __name__ == "__main__": diff --git a/python/tests/test_BrowserbaseBrowserLauncher.py b/python/tests/test_BrowserbaseBrowserLauncher.py deleted file mode 100644 index fb1712f8..00000000 --- a/python/tests/test_BrowserbaseBrowserLauncher.py +++ /dev/null @@ -1,103 +0,0 @@ -from __future__ import annotations - -import json -import os -import time -import unittest -import urllib.request -from typing import Any, cast - -from websocket import create_connection - -from modcdp.launcher.BrowserbaseBrowserLauncher import BrowserbaseBrowserLauncher - - -LIVE_BROWSERBASE_TIMEOUT_S = 120 - - -class BrowserbaseBrowserLauncherTests(unittest.TestCase): - def test_creates_verifies_resumes_and_releases_real_browserbase_session(self) -> None: - if not os.environ.get("BROWSERBASE_API_KEY", "").strip(): - self.fail("BROWSERBASE_API_KEY is required for live Browserbase tests") - launcher = BrowserbaseBrowserLauncher( - cast(Any, { - "timeout": 120, - **({"region": os.environ["BROWSERBASE_REGION"]} if os.environ.get("BROWSERBASE_REGION") else {}), - "browserbase_browser_settings": { - "viewport": {"width": 900, "height": 700}, - "recordSession": False, - }, - "browserbase_user_metadata": { - "modcdp_launcher_test": "BrowserbaseBrowserLauncher", - }, - }) - ) - browser = launcher.launch() - resumed = None - ws = None - session_id = browser.get("browserbase_session_id") - try: - if not isinstance(session_id, str): - self.fail(f"browserbase_session_id = {session_id!r}") - self.assertIn(session_id, browser.get("browserbase_session_url") or "") - cdp_url = browser.get("cdp_url") - if not isinstance(cdp_url, str): - self.fail(f"cdp_url = {cdp_url!r}") - self.assertRegex(cdp_url, r"^wss://") - ws = create_connection(cdp_url, timeout=LIVE_BROWSERBASE_TIMEOUT_S) - _expect_cdp_browser_surface(ws) - - retrieved = _retrieve_browserbase_session(session_id) - self.assertEqual(retrieved.get("id"), session_id) - self.assertEqual(retrieved.get("status"), "RUNNING") - - resumed = BrowserbaseBrowserLauncher( - { - "browserbase_session_id": session_id, - "browserbase_close_session_on_close": False, - } - ).launch() - self.assertEqual(resumed.get("browserbase_session_id"), session_id) - self.assertRegex(resumed.get("cdp_url") or "", r"^wss://") - _expect_cdp_browser_surface(ws) - finally: - if ws is not None: - ws.close() - if resumed is not None: - resumed["close"]() - browser["close"]() - browser["close"]() - - deadline = time.time() + 30 - while time.time() < deadline: - if _retrieve_browserbase_session(session_id).get("status") != "RUNNING": - return - time.sleep(1) - self.fail("Browserbase session did not leave RUNNING status after release") - - -def _retrieve_browserbase_session(session_id: str) -> dict: - request = urllib.request.Request( - _browserbase_api_url(f"/v1/sessions/{session_id}"), - headers={"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"]}, - ) - with urllib.request.urlopen(request, timeout=60) as response: - if response.status < 200 or response.status >= 300: - raise AssertionError(f"Browserbase session fetch returned {response.status}") - return json.loads(response.read()) - - -def _browserbase_api_url(pathname: str) -> str: - base_url = os.environ.get("BROWSERBASE_BASE_URL", "https://api.browserbase.com").rstrip("/") - return f"{base_url}/{pathname.lstrip('/')}" - - -def _expect_cdp_browser_surface(ws) -> None: - ws.send(json.dumps({"id": 1, "method": "Browser.getVersion", "params": {}})) - message = json.loads(ws.recv()) - if not isinstance(message.get("result", {}).get("product"), str): - raise AssertionError(f"Browser.getVersion result = {message!r}") - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_CDPExtensionInjector.py b/python/tests/test_CDPExtensionInjector.py new file mode 100644 index 00000000..e499700a --- /dev/null +++ b/python/tests/test_CDPExtensionInjector.py @@ -0,0 +1,30 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.CDPExtensionInjector.ts +# - ./go/modcdp/injector/CDPExtensionInjector_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import unittest +from pathlib import Path + +from modcdp.injector.CDPExtensionInjector import CDPExtensionInjector + + +class CDPExtensionInjectorTests(unittest.TestCase): + def test_cdpextensioninjector_prepares_the_default_packaged_extension_zip(self) -> None: + injector = CDPExtensionInjector() + try: + injector.prepare() + unpacked_extension_path = injector.unpacked_extension_path + if not isinstance(unpacked_extension_path, str): + self.fail(f"unpacked_extension_path = {unpacked_extension_path!r}") + self.assertIn("modcdp-extension-", unpacked_extension_path) + self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) + finally: + injector.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_CDPTypes_payload_schema_normalization.py b/python/tests/test_CDPTypes_payload_schema_normalization.py new file mode 100644 index 00000000..49168454 --- /dev/null +++ b/python/tests/test_CDPTypes_payload_schema_normalization.py @@ -0,0 +1,75 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.CDPTypes_payload_schema_normalization.ts +# - ./go/modcdp/client/CDPTypes_payload_schema_normalization_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import unittest + +from modcdp.types.CDPTypes import CDPTypes +from modcdp.types.modcdp import _isObjectMap + + +class CDPTypesPayloadSchemaNormalizationTests(unittest.TestCase): + def test_validatezodschema_accepts_empty_zod_shapes(self) -> None: + types = CDPTypes() + types.addCustomCommand({"name": "Custom.empty", "params_schema": {}}) + self.assertEqual(types.parseCommandParams("Custom.empty", {"value": 1}), {"value": 1}) + + def test_validatezodschema_rejects_unsupported_schema_specs(self) -> None: + with self.assertRaises(TypeError): + CDPTypes().addCustomCommand({"name": "Custom.bad", "params_schema": "not-a-schema"}) + + def test_validatezodschema_accepts_non_empty_zod_shapes(self) -> None: + types = CDPTypes() + types.addCustomCommand( + { + "name": "Custom.nonEmpty", + "params_schema": { + "type": "object", + "properties": {"value": {"type": "string"}}, + "required": ["value"], + }, + }, + ) + + self.assertEqual( + types.parseCommandParams("Custom.nonEmpty", {"value": "ok", "extra": True}), + {"value": "ok", "extra": True}, + ) + + def test_cdp_types_serializes_builtin_mod_command_schemas_through_the_same_wire_path(self) -> None: + types = CDPTypes() + + for name in ["Mod.configure", "Mod.addCustomCommand", "Mod.addCustomEvent"]: + registration = next(command for command in types.customCommandWireRegistrations() if command["name"] == name) + self.assertIsInstance(registration.get("params_schema"), dict) + self.assertIsInstance(registration.get("result_schema"), dict) + + parsed_configure_params = types.parseCommandParams( + "Mod.configure", + { + "client_config": {"client_hydrate_aliases": False}, + "downstream": { + "downstream_client_timeout_ms": 1234, + "downstream_close_browser_on_disconnect": True, + }, + }, + ) + client_config = parsed_configure_params.get("client_config") + if not _isObjectMap(client_config): + raise AssertionError(f"client_config = {client_config!r}") + self.assertEqual(client_config["client_hydrate_aliases"], False) + downstream = parsed_configure_params.get("downstream") + if not _isObjectMap(downstream): + raise AssertionError(f"downstream = {downstream!r}") + self.assertEqual(downstream["downstream_client_timeout_ms"], 1234) + self.assertEqual(downstream["downstream_close_browser_on_disconnect"], True) + with self.assertRaisesRegex(ValueError, "downstream"): + types.parseCommandParams("Mod.configure", {"downstream": {"closeBrowser": "not allowed over the wire"}}) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_CLIExtensionInjector.py b/python/tests/test_CLIExtensionInjector.py new file mode 100644 index 00000000..1f481284 --- /dev/null +++ b/python/tests/test_CLIExtensionInjector.py @@ -0,0 +1,156 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.CLIExtensionInjector.ts +# - ./go/modcdp/injector/CLIExtensionInjector_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import glob +import os +import re +import sys +import tempfile +import unittest +import zipfile +from collections.abc import Mapping +from pathlib import Path + +from modcdp.injector.ExtensionInjector import DEFAULT_MODCDP_EXTENSION_ID +from modcdp.injector.CLIExtensionInjector import CLIExtensionInjector +from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport + + +ROOT = Path(__file__).resolve().parents[2] +EXTENSION_PATH = ROOT / "dist" / "extension" +DOES_NOT_EXIST_EXTENSION_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + + +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() + + +class CLIExtensionInjectorTests(unittest.TestCase): + def test_cliextensioninjector_rejects_zip_entries_outside_extraction_directory(self) -> None: + with tempfile.TemporaryDirectory(prefix="modcdp-bad-zip-") as temp_dir: + zip_path = Path(temp_dir) / "extension.zip" + with zipfile.ZipFile(zip_path, "w") as archive: + archive.writestr("../evil.txt", "evil") + + injector = CLIExtensionInjector({"injector_cli_extension_path": str(zip_path)}) + try: + with self.assertRaisesRegex(RuntimeError, "escapes extension extraction directory"): + injector.prepare() + self.assertFalse((Path(temp_dir) / "evil.txt").exists()) + finally: + injector.close() + + def test_cliextensioninjector_prepares_an_unpacked_extension_directory_for_load_extension(self) -> None: + injector = CLIExtensionInjector({"injector_cli_extension_path": str(EXTENSION_PATH)}) + try: + injector.prepare() + unpacked_extension_path = injector.unpacked_extension_path + if not isinstance(unpacked_extension_path, str): + self.fail(f"unpacked_extension_path = {unpacked_extension_path!r}") + self.assertNotEqual(unpacked_extension_path, str(EXTENSION_PATH)) + self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) + self.assertEqual(injector.extra_args, [f"--load-extension={unpacked_extension_path}"]) + self.assertEqual(injector.config.injector_service_worker_extension_id, DEFAULT_MODCDP_EXTENSION_ID) + finally: + injector.close() + + def test_cliextensioninjector_prepares_the_default_extension_zip_for_load_extension(self) -> None: + injector = CLIExtensionInjector() + try: + injector.prepare() + unpacked_extension_path = injector.unpacked_extension_path + if not isinstance(unpacked_extension_path, str): + self.fail(f"unpacked_extension_path = {unpacked_extension_path!r}") + self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) + self.assertIn("modcdp-extension-", unpacked_extension_path) + self.assertEqual(injector.extra_args, [f"--load-extension={unpacked_extension_path}"]) + self.assertEqual(injector.config.injector_service_worker_extension_id, DEFAULT_MODCDP_EXTENSION_ID) + finally: + injector.close() + + def test_cliextensioninjector_returns_null_when_a_trusted_does_not_exist_extension_id_is_absent_in_a_real_browser(self) -> None: + injector = CLIExtensionInjector( + { + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_cli_extension_id": DOES_NOT_EXIST_EXTENSION_ID, + "injector_trust_service_worker_target": True, + "injector_service_worker_ready_timeout_ms": 250, + "injector_service_worker_poll_interval_ms": 25, + } + ) + launcher = LocalBrowserLauncher( + { + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + } + ) + upstream = WSUpstreamTransport() + try: + injector.prepare() + launcher.update(injector.configForLauncher()) + launcher.launch() + upstream.update(launcher.configForUpstream()) + upstream.connect() + injector.update({"send": upstream.send}) + + targets = upstream.send("Target.getTargets", {}) + target_infos = targets.get("targetInfos") if isinstance(targets, Mapping) else None + if not isinstance(target_infos, list): + raise AssertionError(f"Target.getTargets returned no targetInfos: {targets!r}") + found_does_not_exist_target = False + for target in target_infos: + match target: + case {"url": str(target_url)}: + if target_url.startswith(f"chrome-extension://{DOES_NOT_EXIST_EXTENSION_ID}/"): + found_does_not_exist_target = True + self.assertFalse(found_does_not_exist_target) + + result = injector.inject() + self.assertIsNone(result) + finally: + upstream.close() + launcher.close() + injector.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_DiscoverExtensionInjector.py b/python/tests/test_DiscoverExtensionInjector.py new file mode 100644 index 00000000..38acf7d3 --- /dev/null +++ b/python/tests/test_DiscoverExtensionInjector.py @@ -0,0 +1,101 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.DiscoverExtensionInjector.ts +# - ./go/modcdp/injector/DiscoverExtensionInjector_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import glob +import unittest +from pathlib import Path +import os +import re +import sys + +from modcdp import ModCDPClient + + +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + + +ROOT = Path(__file__).resolve().parents[2] +EXTENSION_PATH = ROOT / "dist" / "extension" +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() + + +class DiscoverExtensionInjectorTests(unittest.TestCase): + def test_discoverextensioninjector_attaches_to_an_already_loaded_real_modcdp_extension(self) -> None: + owner = ModCDPClient( + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + ) + try: + owner.connect() + cdp = ModCDPClient( + launcher={"launcher_mode": "remote", "launcher_remote_cdp_url": owner.cdp_url}, + upstream={"upstream_mode": "ws", "upstream_ws_cdp_url": owner.cdp_url}, + injector={ + "injector_mode": "discover", + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + ) + try: + cdp.connect() + self.assertEqual(cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, "discover") + assert cdp.injector is not None + self.assertEqual(cdp.injector.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf") + self.assertEqual( + cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')"), + "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", + ) + finally: + cdp.close() + finally: + owner.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_DiscoveredExtensionInjector.py b/python/tests/test_DiscoveredExtensionInjector.py deleted file mode 100644 index 46c71a73..00000000 --- a/python/tests/test_DiscoveredExtensionInjector.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path - -from modcdp import ModCDPClient - - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" - - -class DiscoveredExtensionInjectorTests(unittest.TestCase): - def test_attaches_to_already_loaded_real_modcdp_extension(self) -> None: - owner = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - try: - owner.connect() - cdp = ModCDPClient( - launcher={"launcher_mode": "remote"}, - upstream={"upstream_mode": "ws", "upstream_cdp_url": owner.cdp_url}, - injector={ - "injector_mode": "discover", - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - try: - cdp.connect() - self.assertEqual(cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, "discovered") - self.assertEqual(cdp.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf") - self.assertEqual( - cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')"), - "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", - ) - finally: - cdp.close() - finally: - owner.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_ExtensionInjector.py b/python/tests/test_ExtensionInjector.py index f1ea3ecf..11acf7ec 100644 --- a/python/tests/test_ExtensionInjector.py +++ b/python/tests/test_ExtensionInjector.py @@ -1,3 +1,9 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.ExtensionInjector.ts +# - ./go/modcdp/injector/ExtensionInjector_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations import unittest @@ -6,16 +12,17 @@ class ExtensionInjectorTests(unittest.TestCase): - def test_owns_shared_injector_config(self) -> None: + def test_extensioninjector_owns_shared_injector_config(self) -> None: injector = ExtensionInjector( { - "injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "injector_service_worker_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], } ) - self.assertEqual(injector.getTransportConfig(), {"injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}) - self.assertEqual(injector.getLauncherConfig(), {}) + self.assertEqual(injector.config.injector_service_worker_extension_id, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + self.assertEqual(injector.configForUpstream(), {}) + self.assertEqual(injector.extra_args, []) self.assertTrue( injector._serviceWorkerTargetMatches( { @@ -35,7 +42,7 @@ def test_owns_shared_injector_config(self) -> None: ) ) - def test_base_inject_reports_the_class_name(self) -> None: + def test_extensioninjector_base_inject_reports_the_subclass_name(self) -> None: with self.assertRaisesRegex(NotImplementedError, "ExtensionInjector.inject is not implemented"): ExtensionInjector().inject() diff --git a/python/tests/test_ExtensionsLoadUnpackedInjector.py b/python/tests/test_ExtensionsLoadUnpackedInjector.py deleted file mode 100644 index 4a101d82..00000000 --- a/python/tests/test_ExtensionsLoadUnpackedInjector.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -import unittest -from pathlib import Path -from typing import cast - -from modcdp.injector.ExtensionsLoadUnpackedInjector import ExtensionsLoadUnpackedInjector - - -class ExtensionsLoadUnpackedInjectorTests(unittest.TestCase): - def test_prepares_default_packaged_extension_zip(self) -> None: - injector = ExtensionsLoadUnpackedInjector() - try: - injector.prepare() - unpacked_extension_path = injector.unpacked_extension_path - self.assertIsInstance(unpacked_extension_path, str) - unpacked_extension_path = cast(str, unpacked_extension_path) - self.assertIn("modcdp-extension-", unpacked_extension_path) - self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) - finally: - injector.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_LocalBrowserLaunchExtensionInjector.py b/python/tests/test_LocalBrowserLaunchExtensionInjector.py deleted file mode 100644 index 3252463e..00000000 --- a/python/tests/test_LocalBrowserLaunchExtensionInjector.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -import unittest -import time -import tempfile -import zipfile -from pathlib import Path -from typing import Any, cast - -from modcdp.injector.ExtensionInjector import DEFAULT_MODCDP_EXTENSION_ID -from modcdp.injector.LocalBrowserLaunchExtensionInjector import LocalBrowserLaunchExtensionInjector - - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" - - -class LocalBrowserLaunchExtensionInjectorTests(unittest.TestCase): - def test_rejects_zip_entries_outside_extraction_directory(self) -> None: - with tempfile.TemporaryDirectory(prefix="modcdp-bad-zip-") as temp_dir: - zip_path = Path(temp_dir) / "extension.zip" - with zipfile.ZipFile(zip_path, "w") as archive: - archive.writestr("../evil.txt", "evil") - - injector = LocalBrowserLaunchExtensionInjector({"injector_extension_path": str(zip_path)}) - try: - with self.assertRaisesRegex(RuntimeError, "escapes extension extraction directory"): - injector.prepare() - self.assertFalse((Path(temp_dir) / "evil.txt").exists()) - finally: - injector.close() - - def test_prepares_unpacked_extension_directory_for_load_extension(self) -> None: - injector = LocalBrowserLaunchExtensionInjector({"injector_extension_path": str(EXTENSION_PATH)}) - try: - injector.prepare() - unpacked_extension_path = injector.unpacked_extension_path - self.assertIsInstance(unpacked_extension_path, str) - unpacked_extension_path = cast(str, unpacked_extension_path) - self.assertNotEqual(unpacked_extension_path, str(EXTENSION_PATH)) - self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) - self.assertEqual(injector.getLauncherConfig(), {"extra_args": [f"--load-extension={unpacked_extension_path}"]}) - self.assertEqual(injector.options.get("injector_extension_id"), DEFAULT_MODCDP_EXTENSION_ID) - finally: - injector.close() - - def test_prepares_default_extension_zip_for_load_extension(self) -> None: - injector = LocalBrowserLaunchExtensionInjector() - try: - injector.prepare() - unpacked_extension_path = injector.unpacked_extension_path - self.assertIsInstance(unpacked_extension_path, str) - unpacked_extension_path = cast(str, unpacked_extension_path) - self.assertTrue((Path(unpacked_extension_path) / "manifest.json").exists()) - self.assertIn("modcdp-extension-", unpacked_extension_path) - self.assertEqual(injector.getLauncherConfig(), {"extra_args": [f"--load-extension={unpacked_extension_path}"]}) - self.assertEqual(injector.options.get("injector_extension_id"), DEFAULT_MODCDP_EXTENSION_ID) - finally: - injector.close() - - def test_returns_immediately_when_launched_extension_target_is_absent(self) -> None: - methods: list[str] = [] - - def send(method: str, params: dict[str, Any] | None = None, session_id: str | None = None) -> dict[str, Any]: - methods.append(method) - if method == "Target.getTargets": - return {"targetInfos": []} - raise RuntimeError(f"unexpected {method}") - - injector = LocalBrowserLaunchExtensionInjector( - cast(Any, { - "injector_extension_path": str(EXTENSION_PATH), - "injector_trust_service_worker_target": True, - "send": send, - }) - ) - try: - injector.prepare() - started_at = time.perf_counter() - result = injector.inject() - elapsed_ms = (time.perf_counter() - started_at) * 1000 - self.assertIsNone(result) - self.assertEqual(methods, ["Target.getTargets"]) - self.assertLess(elapsed_ms, 200) - finally: - injector.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_LocalBrowserLauncher.py b/python/tests/test_LocalBrowserLauncher.py index 2816a22a..788c499d 100644 --- a/python/tests/test_LocalBrowserLauncher.py +++ b/python/tests/test_LocalBrowserLauncher.py @@ -1,134 +1,110 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.LocalBrowserLauncher.ts +# - ./go/modcdp/launcher/LocalBrowserLauncher_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import json -import sys +from collections.abc import Mapping import tempfile import unittest from pathlib import Path -from websocket import create_connection - from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport class LocalBrowserLauncherTests(unittest.TestCase): - def test_class_helpers_match_ts_surface(self) -> None: + def test_class_helpers_match_the_local_launcher_surface(self) -> None: self.assertIsInstance(LocalBrowserLauncher.findChromeBinary(), str) self.assertIsInstance(LocalBrowserLauncher.freePort(), int) - def test_launches_real_browser_over_chosen_cdp_port_and_honors_launch_options(self) -> None: + def test_launches_a_real_browser_over_a_chosen_cdp_port_and_explicit_profile_dir(self) -> None: with tempfile.TemporaryDirectory(prefix="modcdp-python-local-profile-") as user_data_dir: + port = LocalBrowserLauncher.freePort() chrome = LocalBrowserLauncher( { - "headless": True, - "chrome_ready_timeout_ms": 45_000, - "chrome_ready_poll_interval_ms": 50, + "launcher_local_headless": True, + "launcher_local_chrome_ready_timeout_ms": 45_000, + "launcher_local_chrome_ready_poll_interval_ms": 50, } - ).launch({"user_data_dir": user_data_dir, "args": ["--window-size=900,700"]}) - cdp_url = chrome["cdp_url"] + ).launch({"launcher_local_cdp_listen_port": port, "launcher_local_user_data_dir": user_data_dir}) + cdp_url = chrome.cdp_url if cdp_url is None: raise AssertionError("expected launcher to return cdp_url") - ws = create_connection(cdp_url, timeout=10) + transport = WSUpstreamTransport({"upstream_ws_cdp_url": cdp_url}) + transport.connect() try: - self.assertEqual(chrome.get("profile_dir"), user_data_dir) - ws.send(json.dumps({"id": 1, "method": "Browser.getVersion", "params": {}})) - version = json.loads(ws.recv()) - self.assertEqual(version["id"], 1) - self.assertIn("Chrome", version["result"]["product"]) - self.assertIsInstance(version["result"]["protocolVersion"], str) - ws.send(json.dumps({"id": 2, "method": "SystemInfo.getInfo", "params": {}})) - system_info = json.loads(ws.recv()) - self.assertEqual(system_info["id"], 2) - command_line = system_info["result"]["commandLine"] - self.assertIsInstance(command_line, str) - self.assertIn("--window-size=900,700", command_line) - if sys.platform.startswith("linux"): - self.assertIn("--no-sandbox", command_line) - else: - self.assertNotIn("--no-sandbox", command_line) + self.assertEqual(chrome.cdp_listen_port, port) + self.assertRegex(cdp_url, rf"^ws://127\.0\.0\.1:{port}/") + self.assertEqual(chrome.profile_dir, user_data_dir) + expect_cdp_browser_surface(transport) finally: - ws.close() - chrome["close"]() + transport.close() + chrome.close() self.assertTrue(Path(user_data_dir).exists()) - def test_cleanup_user_data_dir_removes_explicit_profile(self) -> None: + def test_removes_an_explicit_user_data_dir_when_cleanup_user_data_dir_is_set(self) -> None: user_data_dir = tempfile.mkdtemp(prefix="modcdp-python-local-profile-") chrome = LocalBrowserLauncher( { - "headless": True, - "chrome_ready_timeout_ms": 45_000, + "launcher_local_headless": True, + "launcher_local_chrome_ready_timeout_ms": 45_000, } - ).launch({"user_data_dir": user_data_dir, "cleanup_user_data_dir": True}) + ).launch({"launcher_local_user_data_dir": user_data_dir, "launcher_local_cleanup_user_data_dir": True}) try: - self.assertEqual(chrome.get("profile_dir"), user_data_dir) + self.assertEqual(chrome.profile_dir, user_data_dir) finally: - chrome["close"]() + chrome.close() self.assertFalse(Path(user_data_dir).exists()) - def test_launches_real_browser_over_remote_debugging_pipe(self) -> None: - chrome = LocalBrowserLauncher( - { - "headless": True, - "remote_debugging": "pipe", - "chrome_ready_timeout_ms": 45_000, - } - ).launch() - pipe_read = chrome.get("pipe_read") - pipe_write = chrome.get("pipe_write") - if pipe_read is None or pipe_write is None: - raise AssertionError("expected launcher to return pipe handles") +def expect_cdp_browser_surface(transport: WSUpstreamTransport) -> None: + version = transport.send("Browser.getVersion") + expect_version_result(version) + + created = transport.send("Target.createTarget", {"url": "about:blank#modcdp-launcher-test"}) + target_id = created.get("targetId") + if not isinstance(target_id, str): + raise AssertionError(f"Target.createTarget result = {created!r}") + + try: + attached = transport.send("Target.attachToTarget", {"targetId": target_id, "flatten": True}) + session_id = attached.get("sessionId") + if not isinstance(session_id, str): + raise AssertionError(f"Target.attachToTarget result = {attached!r}") + transport.send("Runtime.enable", {}, session_id) + evaluated = transport.send( + "Runtime.evaluate", + {"expression": "(() => ({ ok: true, value: 42 }))()", "returnByValue": True}, + session_id, + ) + result = object_dict(evaluated.get("result")) + if result.get("type") != "object" or result.get("value") != {"ok": True, "value": 42}: + raise AssertionError(f"Runtime.evaluate result = {evaluated!r}") + finally: try: - self.assertRegex(chrome["cdp_url"] or "", r"^pipe://\d+$") - self.assertNotIn("loopback_cdp_url", chrome) - pipe_write.write(json.dumps({"id": 10, "method": "Browser.getVersion", "params": {}}).encode() + b"\0") - pipe_write.flush() - response = _read_pipe_message(pipe_read) - self.assertEqual(response["id"], 10) - self.assertIn("Chrome", response["result"]["product"]) - finally: - chrome["close"]() + transport.send("Target.closeTarget", {"targetId": target_id}) + except Exception: + pass - def test_launches_pipe_browser_with_auxiliary_loopback_only_when_requested(self) -> None: - chrome = LocalBrowserLauncher( - { - "headless": True, - "remote_debugging": "pipe", - "loopback_cdp": True, - "chrome_ready_timeout_ms": 45_000, - } - ).launch() - loopback_cdp_url = chrome.get("loopback_cdp_url") - if not isinstance(loopback_cdp_url, str): - raise AssertionError("expected launcher to return loopback_cdp_url") - ws = create_connection(loopback_cdp_url, timeout=10) - try: - self.assertRegex(chrome["cdp_url"] or "", r"^pipe://\d+$") - self.assertRegex(loopback_cdp_url, r"^ws://127\.0\.0\.1:\d+/") - ws.send(json.dumps({"id": 1, "method": "Browser.getVersion", "params": {}})) - version = json.loads(ws.recv()) - self.assertEqual(version["id"], 1) - self.assertIn("Chrome", version["result"]["product"]) - finally: - ws.close() - chrome["close"]() - - -def _read_pipe_message(pipe_read) -> dict: - buffer = b"" - while True: - chunk = pipe_read.read(1) - if not chunk: - raise AssertionError("pipe closed before CDP response") - buffer += chunk - if b"\0" not in buffer: - continue - raw, _ = buffer.split(b"\0", 1) - return json.loads(raw.decode()) +def expect_version_result(version: Mapping[str, object]) -> None: + product = version.get("product") + if not isinstance(product, str) or ("Chrome" not in product and "Chromium" not in product): + raise AssertionError(f"Browser.getVersion product = {product!r}") + if not isinstance(version.get("protocolVersion"), str): + raise AssertionError(f"Browser.getVersion protocolVersion = {version.get('protocolVersion')!r}") + + +def object_dict(value: object) -> dict[str, object]: + if not isinstance(value, Mapping): + raise AssertionError(f"expected object mapping, got {value!r}") + return {str(key): raw_value for key, raw_value in value.items()} if __name__ == "__main__": diff --git a/python/tests/test_ModCDPClient.py b/python/tests/test_ModCDPClient.py index cd2060c5..372e0db6 100644 --- a/python/tests/test_ModCDPClient.py +++ b/python/tests/test_ModCDPClient.py @@ -1,51 +1,84 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.ModCDPClient.ts +# - ./go/modcdp/client/ModCDPClient_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import asyncio -import importlib -import json +import glob +import os +import re +import sys import time import unittest from collections.abc import Mapping from pathlib import Path from queue import Empty, Queue -from typing import Any, cast - -from websocket import create_connection +from typing import Any from modcdp import ModCDPClient from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher -from modcdp.types import JsonValue -from tests.test_ReverseWebSocketUpstreamTransport import reversews_test_browser_path +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport + + +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") HERE = Path(__file__).resolve().parent EXTENSION_PATH = HERE.parents[1] / "dist" / "extension" +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() class ModCDPClientTests(unittest.TestCase): - def test_constructor_normalizes_nested_config_owners(self) -> None: + def test_modcdpclient_uses_flat_owner_prefixed_config(self) -> None: cdp = ModCDPClient( launcher={ "launcher_mode": "local", - "launcher_executable_path": "/tmp/chrome", - "launcher_user_data_dir": "/tmp/profile", - "launcher_options": {"headless": True}, + "launcher_local_executable_path": "/tmp/chrome", + "launcher_local_user_data_dir": "/tmp/profile", + "launcher_local_headless": True, }, upstream={ "upstream_mode": "ws", - "upstream_cdp_url": "http://127.0.0.1:9222", - "upstream_nats_wait_timeout_ms": 345, - "upstream_reversews_wait_timeout_ms": 456, - "upstream_nativemessaging_manifest": "/tmp/native-host.json", - "upstream_nativemessaging_manifests": ["/tmp/native-host-extra.json"], - "upstream_nativemessaging_host_name": "com.modcdp.custom", - "upstream_nativemessaging_wait_timeout_ms": 567, + "upstream_ws_cdp_url": "http://127.0.0.1:9222", "upstream_ws_connect_error_settle_timeout_ms": 321, }, injector={ "injector_mode": "discover", - "injector_extension_path": "/tmp/ext", - "injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "injector_discover_extension_path": "/tmp/ext", + "injector_service_worker_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "injector_service_worker_url_includes": ["modcdp"], "injector_service_worker_url_suffixes": ["/custom/service_worker.js"], "injector_trust_service_worker_target": True, @@ -56,42 +89,44 @@ def test_constructor_normalizes_nested_config_owners(self) -> None: "injector_service_worker_poll_interval_ms": 76, "injector_target_session_poll_interval_ms": 87, }, - client={ - "client_routes": {"*.*": "direct_cdp"}, + router={"router_routes": {"*.*": "direct_cdp"}, "loopback_execution_context_timeout_ms": 4321}, + client_config={ "client_hydrate_aliases": False, "client_mirror_upstream_events": False, "client_cdp_send_timeout_ms": 1234, "client_event_wait_timeout_ms": 2345, + "client_heartbeat_interval_ms": 3456, }, - server={ - "server_routes": {"*.*": "loopback_cdp"}, + server_config={ + "router": {"router_routes": {"*.*": "loopback_cdp"}}, + "client_config": {"client_cdp_send_timeout_ms": 9876}, + "upstream": {"upstream_ws_connect_error_settle_timeout_ms": 7654}, + "downstream": {"downstream_client_timeout_ms": 4567}, "server_browser_token": "token-1", - "server_cdp_send_timeout_ms": 9876, - "server_loopback_execution_context_timeout_ms": 8765, - "server_ws_connect_error_settle_timeout_ms": 7654, }, ) - self.assertEqual(cdp.launcher["launcher_options"], {"headless": True}) - self.assertEqual(cdp._launch_options().get("executable_path"), "/tmp/chrome") - self.assertEqual(cdp._launch_options().get("user_data_dir"), "/tmp/profile") - self.assertEqual(cdp.upstream["upstream_nats_wait_timeout_ms"], 345) - self.assertEqual(cdp.upstream["upstream_reversews_wait_timeout_ms"], 456) - self.assertEqual(cdp.upstream["upstream_nativemessaging_manifest"], "/tmp/native-host.json") - self.assertEqual(cdp.upstream["upstream_nativemessaging_manifests"], ["/tmp/native-host-extra.json"]) - self.assertEqual(cdp.upstream["upstream_nativemessaging_host_name"], "com.modcdp.custom") - self.assertEqual(cdp.upstream["upstream_nativemessaging_wait_timeout_ms"], 567) - self.assertEqual(cdp.upstream["upstream_ws_connect_error_settle_timeout_ms"], 321) - self.assertEqual(cdp.injector["injector_execution_context_timeout_ms"], 4321) - self.assertEqual(cdp.injector["injector_service_worker_probe_timeout_ms"], 5432) - self.assertEqual(cdp.injector["injector_service_worker_ready_timeout_ms"], 6543) - self.assertEqual(cdp.injector["injector_service_worker_poll_interval_ms"], 76) - self.assertEqual(cdp.injector["injector_target_session_poll_interval_ms"], 87) - self.assertEqual(cdp.client["client_routes"]["*.*"], "direct_cdp") - self.assertEqual(cdp.client["client_hydrate_aliases"], False) - self.assertEqual(cdp.client["client_mirror_upstream_events"], False) - self.assertEqual(cdp.client["client_cdp_send_timeout_ms"], 1234) - self.assertEqual(cdp.client["client_event_wait_timeout_ms"], 2345) + self.assertEqual(cdp.launcher.config.launcher_local_headless, True) + self.assertEqual(cdp.launcher.config.launcher_local_executable_path, "/tmp/chrome") + self.assertEqual(cdp.launcher.config.launcher_local_user_data_dir, "/tmp/profile") + self.assertEqual(cdp.upstream.config.upstream_ws_connect_error_settle_timeout_ms, 321) + self.assertIsNotNone(cdp.injector) + injector = cdp.injector + assert injector is not None + self.assertEqual(injector.config.injector_execution_context_timeout_ms, 4321) + self.assertEqual(injector.config.injector_service_worker_probe_timeout_ms, 5432) + self.assertEqual(injector.config.injector_service_worker_ready_timeout_ms, 6543) + self.assertEqual(injector.config.injector_service_worker_poll_interval_ms, 76) + self.assertEqual(injector.config.injector_target_session_poll_interval_ms, 87) + router_routes = cdp.router.config.router_routes + if not isinstance(router_routes, Mapping): + self.fail("router_routes must be a mapping") + self.assertEqual(router_routes["*.*"], "direct_cdp") + self.assertEqual(cdp.config.client_hydrate_aliases, False) + self.assertEqual(cdp.config.client_mirror_upstream_events, False) + self.assertEqual(cdp.config.client_cdp_send_timeout_ms, 1234) + self.assertEqual(cdp.config.client_event_wait_timeout_ms, 2345) + self.assertEqual(cdp.config.client_heartbeat_interval_ms, 3456) self.assertNotIn("Browser", cdp.__dict__) with self.assertRaises(AttributeError): _ = cdp.Browser @@ -99,154 +134,175 @@ def test_constructor_normalizes_nested_config_owners(self) -> None: self.assertNotIn("cdp_send_timeout_ms", cdp.__dict__) self.assertNotIn("service_worker_probe_timeout_ms", cdp.__dict__) - params = cast(dict[str, Any], cdp._server_configure_params()) - self.assertEqual(params["client"]["client_routes"]["*.*"], "direct_cdp") - self.assertEqual(params["server"]["server_browser_token"], "token-1") - self.assertEqual(params["server"]["server_cdp_send_timeout_ms"], 9876) - self.assertEqual(params["server"]["server_loopback_execution_context_timeout_ms"], 8765) - self.assertEqual(params["server"]["server_ws_connect_error_settle_timeout_ms"], 7654) - - def test_preserves_explicit_zero_timeout_config(self) -> None: - cdp = ModCDPClient( - upstream={ - "upstream_nats_wait_timeout_ms": 0, - "upstream_reversews_wait_timeout_ms": 0, - "upstream_nativemessaging_wait_timeout_ms": 0, - "upstream_ws_connect_error_settle_timeout_ms": 0, - }, - injector={ - "injector_execution_context_timeout_ms": 0, - "injector_service_worker_probe_timeout_ms": 0, - "injector_service_worker_ready_timeout_ms": 0, - "injector_service_worker_poll_interval_ms": 0, - "injector_target_session_poll_interval_ms": 0, - }, - client={ - "client_cdp_send_timeout_ms": 0, - "client_event_wait_timeout_ms": 0, - }, - ) - - self.assertEqual(cdp.upstream["upstream_nats_wait_timeout_ms"], 0) - self.assertEqual(cdp.upstream["upstream_reversews_wait_timeout_ms"], 0) - self.assertEqual(cdp.upstream["upstream_nativemessaging_wait_timeout_ms"], 0) - self.assertEqual(cdp.upstream["upstream_ws_connect_error_settle_timeout_ms"], 0) - self.assertEqual(cdp.injector["injector_execution_context_timeout_ms"], 0) - self.assertEqual(cdp.injector["injector_service_worker_probe_timeout_ms"], 0) - self.assertEqual(cdp.injector["injector_service_worker_ready_timeout_ms"], 0) - self.assertEqual(cdp.injector["injector_service_worker_poll_interval_ms"], 0) - self.assertEqual(cdp.injector["injector_target_session_poll_interval_ms"], 0) - self.assertEqual(cdp.client["client_cdp_send_timeout_ms"], 0) - self.assertEqual(cdp.client["client_event_wait_timeout_ms"], 0) + params = cdp._server_configure_params() + router_config = object_map(params.get("router")) + client_config = object_map(params.get("client_config")) + upstream_config = object_map(params.get("upstream")) + self.assertEqual(object_map(router_config.get("router_routes")).get("*.*"), "loopback_cdp") + self.assertEqual(params.get("server_browser_token"), "token-1") + self.assertEqual(client_config.get("client_cdp_send_timeout_ms"), 9876) + self.assertEqual(router_config.get("loopback_execution_context_timeout_ms"), 4321) + self.assertEqual(upstream_config.get("upstream_ws_connect_error_settle_timeout_ms"), 7654) + self.assertEqual(object_map(params.get("downstream")).get("downstream_client_timeout_ms"), 4567) - def test_preserves_explicit_empty_service_worker_suffix_config(self) -> None: - cdp = ModCDPClient(injector={"injector_mode": "borrow", "injector_service_worker_url_suffixes": []}) + def test_modcdpclient_preserves_explicit_empty_service_worker_suffix_config(self) -> None: + cdp = ModCDPClient(injector={"injector_mode": "discover", "injector_service_worker_url_suffixes": []}) - self.assertEqual(cdp.injector["injector_service_worker_url_suffixes"], []) - self.assertEqual(cdp._base_extension_injector_config(None).get("injector_service_worker_url_suffixes"), []) + self.assertIsNotNone(cdp.injector) + injector = cdp.injector + assert injector is not None + self.assertEqual(injector.config.injector_service_worker_url_suffixes, []) - def test_defaults_service_worker_suffix_config_to_modcdp_worker(self) -> None: - cdp = ModCDPClient() + def test_modcdpclient_defaults_service_worker_suffix_config_to_the_modcdp_worker(self) -> None: + cdp = ModCDPClient(injector={"injector_mode": "discover"}) - self.assertEqual(cdp.injector["injector_service_worker_url_suffixes"], ["/modcdp/service_worker.js"]) - self.assertEqual( - cdp._base_extension_injector_config(None).get("injector_service_worker_url_suffixes"), - ["/modcdp/service_worker.js"], - ) + self.assertIsNotNone(cdp.injector) + injector = cdp.injector + assert injector is not None + self.assertEqual(injector.config.injector_service_worker_url_suffixes, ["/modcdp/service_worker.js"]) - def test_preserves_explicit_none_server_config(self) -> None: - cdp = ModCDPClient(server=None) + def test_modcdpclient_preserves_explicit_null_server_config(self) -> None: + cdp = ModCDPClient(server_config=None) - self.assertIsNone(cdp.server) + self.assertIsNone(cdp.server_config) - def test_only_exposes_injector_attach_after_cdp_send_is_available(self) -> None: - cdp = ModCDPClient() - disconnected_config = cdp._base_extension_injector_config(None) - self.assertIsNone(disconnected_config.get("send")) - self.assertIsNone(disconnected_config.get("attachToTarget")) - - connected_config = cdp._base_extension_injector_config(lambda method, params=None, session_id=None: {}) - self.assertTrue(callable(connected_config.get("send"))) - self.assertTrue(callable(connected_config.get("attachToTarget"))) - - def test_defaults_launched_modcdp_server_upstreams_to_extension_auto(self) -> None: - for mode in ("nativemessaging", "reversews", "nats"): - launched = ModCDPClient(launcher={"launcher_mode": "local"}, upstream={"upstream_mode": mode}) - self.assertEqual(launched.launcher["launcher_mode"], "local") - self.assertEqual(launched.upstream_endpoint_kind, "modcdp_server") - self.assertEqual(launched.injector["injector_mode"], "auto") - - attach_only = ModCDPClient(upstream={"upstream_mode": mode}) - self.assertEqual(attach_only.launcher["launcher_mode"], "none") - self.assertEqual(attach_only.upstream_endpoint_kind, "modcdp_server") - self.assertEqual(attach_only.injector["injector_mode"], "none") - - def test_orders_local_auto_injection_as_launch_flag_then_load_unpacked_fallback(self) -> None: + def test_modcdpclient_selects_exactly_one_injector_from_explicit_injector_mode(self) -> None: cdp = ModCDPClient( launcher={"launcher_mode": "local"}, - injector={"injector_mode": "auto"}, + injector={"injector_mode": "cli"}, ) + self.assertEqual(type(cdp.injector).__name__, "CLIExtensionInjector") self.assertEqual( - [type(injector).__name__ for injector in cdp._extension_injectors_for_config()], - [ - "LocalBrowserLaunchExtensionInjector", - "ExtensionsLoadUnpackedInjector", - "DiscoveredExtensionInjector", - "BorrowedExtensionInjector", - ], + type(ModCDPClient(launcher={"launcher_mode": "remote"}, injector={"injector_mode": "cdp"}).injector).__name__, + "CDPExtensionInjector", ) - - def test_rejects_unknown_component_modes_at_their_owning_factory_boundary(self) -> None: - with self.assertRaisesRegex(RuntimeError, r"unknown upstream\.upstream_mode=bogus"): - ModCDPClient(upstream={"upstream_mode": "bogus"})._upstream_transport() - with self.assertRaisesRegex(RuntimeError, r"unknown launcher\.launcher_mode=bogus"): - ModCDPClient(launcher={"launcher_mode": "bogus"})._browser_launcher() - with self.assertRaisesRegex(RuntimeError, r"unknown injector\.injector_mode=bogus"): - ModCDPClient(injector={"injector_mode": "bogus"})._extension_injectors_for_config() - - def test_connects_with_local_launch_injector_chain(self) -> None: + self.assertEqual( + type(ModCDPClient(launcher={"launcher_mode": "bb"}, injector={"injector_mode": "bb"}).injector).__name__, + "BBExtensionInjector", + ) + self.assertEqual( + type(ModCDPClient(launcher={"launcher_mode": "remote"}, injector={"injector_mode": "discover"}).injector).__name__, + "DiscoverExtensionInjector", + ) + def test_modcdpclient_rejects_unknown_component_modes_at_their_owning_factory_boundary(self) -> None: + with self.assertRaisesRegex(Exception, r"unknown upstream_mode=bogus"): + ModCDPClient(upstream={"upstream_mode": "bogus"}) + with self.assertRaisesRegex(Exception, r"Input should be"): + ModCDPClient(launcher={"launcher_mode": "bogus"}) + with self.assertRaisesRegex(Exception, r"Input should be"): + ModCDPClient(injector={"injector_mode": "bogus"}) + + def test_modcdpclient_connects_with_nested_launch_upstream_extension_client_server_config(self) -> None: cdp = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True, "chrome_ready_timeout_ms": 60_000}}, + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_chrome_ready_timeout_ms": 60_000, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, "injector_service_worker_probe_timeout_ms": 30_000, }, - client={ + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + client_config={ + "client_hydrate_aliases": True, + "client_mirror_upstream_events": True, "client_cdp_send_timeout_ms": 30_000, "client_event_wait_timeout_ms": 30_000, }, + server_config={ + "client_config": {"client_cdp_send_timeout_ms": 30_000}, + "router": { + "router_routes": {"*.*": "loopback_cdp"}, + "loopback_execution_context_timeout_ms": 30_000, + }, + "upstream": {"upstream_ws_connect_error_settle_timeout_ms": 250}, + }, ) try: cdp.connect() self.assertIn( cdp.connect_timing.get("injector_source") if cdp.connect_timing else None, - ("discovered", "local_launch", "extensions_load_unpacked", "borrowed"), + ("discover", "cli", "cdp"), ) - self.assertEqual(cdp.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf") + self.assertEqual(cdp.launcher.config.launcher_mode, "local") + self.assertEqual(cdp.upstream.config.upstream_mode, "ws") + self.assertIsNotNone(cdp.injector) + assert cdp.injector is not None + self.assertEqual(cdp.injector.config.injector_mode, "cli") + self.assertEqual(cdp.router.config.router_routes["*.*"], "direct_cdp") + self.assertRegex(cdp.upstream.config.upstream_ws_cdp_url or "", r"^ws://") + self.assertEqual(cdp.injector.extension_id, "mdedooklbnfejodmnhmkdpkaedafkehf") self.assertEqual( cdp.Mod.evaluate(expression="chrome.runtime.getURL('modcdp/service_worker.js')"), "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/modcdp/service_worker.js", ) - contexts = cdp.Mod.evaluate( - expression=( - "chrome.runtime.getContexts({}).then((contexts) => contexts.map((context) => " - "({ type: context.contextType, url: context.documentUrl || context.origin || '' })))" - ) + self.assertEqual( + cdp.Mod.evaluate( + expression=( + "chrome.runtime.getContexts({}).then((contexts) => " + "contexts.some((context) => context.contextType === 'OFFSCREEN_DOCUMENT'))" + ) + ), + True, ) - self.assertTrue( - any( - isinstance(context, Mapping) - and context.get("type") == "OFFSCREEN_DOCUMENT" - and context.get("url") == "chrome-extension://mdedooklbnfejodmnhmkdpkaedafkehf/offscreen/keepalive.html" - for context in cast(list[Any], contexts) - ) + version = cdp.Browser.getVersion() + self.assertRegex(version.product, r"Chrome|Chromium") + self.assertIsInstance(version.protocolVersion, str) + runtime_evaluation = cdp.Runtime.evaluate(expression="1 + 1", returnByValue=True) + self.assertEqual(runtime_evaluation.result["type"], "number") + self.assertEqual(runtime_evaluation.result["value"], 2) + with self.assertRaisesRegex(Exception, "expression"): + cdp.send("Runtime.evaluate", {"returnByValue": True}) + with self.assertRaisesRegex(Exception, "number"): + cdp.Mod.ping(sent_at="bad") + self.assertEqual( + cdp.Mod.addMiddleware( + name=cdp.Mod.ping, + phase="response", + expression="async (payload, next) => next(payload)", + ), + {"name": "Mod.ping", "phase": "response", "registered": True}, ) + with self.assertRaisesRegex(Exception, "Invalid option|after"): + cdp.Mod.addMiddleware( + name="Mod.ping", + phase="after", + expression="async (payload, next) => next(payload)", + ) + created_target_id: Queue[str] = Queue() + + def on_target_created(payload: Mapping[str, Any]) -> None: + target_info = payload["targetInfo"] + if isinstance(target_info, Mapping) and target_info.get("url") == "about:blank#public-api-target-created": + created_target_id.put(str(target_info["targetId"])) + + cdp.on("Target.targetCreated", on_target_created) + created_via_alias = cdp.Target.createTarget(url="about:blank#public-api-target-created") + try: + self.assertEqual(created_target_id.get(timeout=10), created_via_alias.targetId) + finally: + cdp.off("Target.targetCreated", on_target_created) + cdp.Target.closeTarget(targetId=created_via_alias.targetId) + direct_target = cdp.send("Target.createTarget", {"url": "about:blank#direct-session-routing"}) + direct_session_target_id = str(direct_target["targetId"]) + try: + direct_session = cdp.send("Target.attachToTarget", {"targetId": direct_session_target_id, "flatten": True}) + direct_eval = cdp.send( + "Runtime.evaluate", + {"expression": "1 + 1", "returnByValue": True}, + str(direct_session["sessionId"]), + ) + self.assertEqual(direct_eval["result"]["value"], 2) + finally: + cdp.send("Target.closeTarget", {"targetId": direct_session_target_id}) sent_at = int(time.time() * 1000) pong: Queue[Mapping[str, Any]] = Queue() @@ -280,82 +336,80 @@ def muted_pong(payload: Mapping[str, Any]) -> None: finally: cdp.close() - def test_close_does_not_close_a_remote_browser_it_did_not_launch(self) -> None: + def test_modcdpclient_close_does_not_close_a_remote_browser_it_did_not_launch(self) -> None: chrome = LocalBrowserLauncher( { - "headless": True, - "chrome_ready_timeout_ms": 60_000, + "launcher_local_headless": True, + "launcher_local_chrome_ready_timeout_ms": 60_000, # This test manually supplies --load-extension, so it intentionally uses # the launch-flag browser path instead of relying on the client fallback. - "executable_path": reversews_test_browser_path(), - "extra_args": [f"--load-extension={EXTENSION_PATH}"], + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + "launcher_local_extra_args": [f"--load-extension={EXTENSION_PATH}"], } ).launch() - raw_ws = create_connection(cast(str, chrome["cdp_url"]), timeout=5) + cdp_url = chrome.cdp_url + if not isinstance(cdp_url, str): + self.fail(f"cdp_url = {cdp_url!r}") + transport = WSUpstreamTransport({"upstream_ws_cdp_url": cdp_url}) + transport.connect() cdp = ModCDPClient( - launcher={"launcher_mode": "remote"}, - upstream={"upstream_mode": "ws", "upstream_cdp_url": chrome["cdp_url"]}, + launcher={"launcher_mode": "remote", "launcher_remote_cdp_url": chrome.cdp_url}, + upstream={"upstream_mode": "ws", "upstream_ws_cdp_url": chrome.cdp_url}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "discover", + "injector_discover_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, "injector_service_worker_ready_timeout_ms": 30_000, "injector_service_worker_probe_timeout_ms": 30_000, }, - client={"client_routes": {"*.*": "direct_cdp"}}, + router={"router_routes": {"*.*": "direct_cdp"}}, ) try: cdp.connect() cdp.close() time.sleep(0.5) - raw_ws.send(json.dumps({"id": 1, "method": "Browser.getVersion", "params": {}})) - response = json.loads(raw_ws.recv()) - self.assertEqual(response["id"], 1) - self.assertRegex(response["result"]["product"], r"Chrome|Chromium") + response = transport.send("Browser.getVersion") + product = response.get("product") + if not isinstance(product, str): + self.fail(f"Browser.getVersion product = {product!r}") + self.assertRegex(product, r"Chrome|Chromium") finally: - raw_ws.close() + transport.close() cdp.close() - chrome["close"]() + chrome.close() - def test_close_keeps_injector_files_until_after_launched_browser_shutdown(self) -> None: + def test_modcdpclient_close_keeps_injector_files_until_after_launched_browser_shutdown(self) -> None: cdp = ModCDPClient( launcher={ "launcher_mode": "local", - "launcher_options": { - "headless": True, - # After explicit CHROME_PATH and CI /usr/bin/chromium, this test uses - # Chrome for Testing because Canary rejects --load-extension in this - # local launch injector path. - "executable_path": reversews_test_browser_path(), - }, + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, - server={"server_routes": {"*.*": "loopback_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, ) try: cdp.connect() - injector = next( - candidate - for candidate in cdp._extension_injectors - if type(candidate).__name__ == "LocalBrowserLaunchExtensionInjector" - ) + self.assertIsNotNone(cdp.injector) + injector = cdp.injector + assert injector is not None unpacked_extension_path = getattr(injector, "unpacked_extension_path") self.assertIsInstance(unpacked_extension_path, str) self.assertNotEqual(unpacked_extension_path, str(EXTENSION_PATH)) - launched = cdp._launched_browser + launched = cdp.launcher.launched if launched is None: self.fail("expected launched browser") - original_close = launched["close"] + original_close = launched.close browser_close_saw_extension = False def close_browser() -> None: @@ -363,7 +417,7 @@ def close_browser() -> None: browser_close_saw_extension = Path(unpacked_extension_path).exists() original_close() - launched["close"] = close_browser + launched.close = close_browser cdp.close() @@ -372,290 +426,135 @@ def close_browser() -> None: finally: cdp.close() - self.assertIsNone(cdp.transport) - self.assertIsNone(cdp._launched_browser) - self.assertEqual(cdp._extension_injectors, []) + self.assertIsNone(cdp.launcher.launched) - def test_close_clears_top_level_connection_state(self) -> None: + def test_modcdpclient_close_clears_top_level_connection_state(self) -> None: cdp = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, ) cdp.connect() - self.assertIsNotNone(cdp.transport) + self.assertIsNotNone(cdp.launcher.launched) cdp.close() - self.assertIsNone(cdp.transport) - with self.assertRaisesRegex(RuntimeError, "ModCDP upstream is not connected"): - cdp.sendRaw("Browser.getVersion") + self.assertIsNone(cdp.launcher.launched) - def test_generated_cdp_surface_exposes_direct_domain_commands(self) -> None: - client = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - client={"client_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, - server={"server_routes": {"*.*": "loopback_cdp"}}, - ) - target_ids: list[str] = [] - - client.connect() - try: - result = client.Target.createTarget(url="https://example.com") - raw_result = client.send("Target.createTarget", {"url": "https://example.org"}) - target_ids.append(str(result.targetId)) - target_ids.append(str(raw_result["targetId"])) - - self.assertRegex(str(result.targetId), r"^[A-F0-9]+$") - self.assertRegex(str(raw_result["targetId"]), r"^[A-F0-9]+$") - attached = client.Target.attachToTarget(targetId=result.targetId, flatten=True) - evaluated = client.Runtime.evaluate(expression="1 + 1", returnByValue=True, session_id=str(attached.sessionId)) - self.assertEqual(evaluated.result["value"], 2) - self.assertIsNotNone(client.last_command_timing) - timing = cast(Mapping[str, Any], client.last_command_timing) - self.assertEqual(timing["target"], "direct_cdp") - raw_version = cast(Mapping[str, Any], client.send("Browser.getVersion")) - self.assertIn("product", raw_version) - self.assertIsNotNone(client.last_command_timing) - timing = cast(Mapping[str, Any], client.last_command_timing) - self.assertEqual(timing["target"], "direct_cdp") - finally: - for target_id in target_ids: - try: - client.Target.closeTarget(targetId=target_id) - except Exception: - pass - client.close() - - with self.assertRaises(Exception): - client.Target._CreateTargetParams.model_validate({"url": "https://example.com", "unknown": True}) - - async def run_awaited_calls() -> None: - awaited_result = await client.Target.createTarget(url="https://example.com") - target_ids.append(str(awaited_result.targetId)) - self.assertRegex(str(awaited_result.targetId), r"^[A-F0-9]+$") - awaited_raw_result = await client.send("Target.createTarget", {"url": "https://example.net"}) - target_ids.append(str(awaited_raw_result["targetId"])) - self.assertRegex(str(awaited_raw_result["targetId"]), r"^[A-F0-9]+$") - - client = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - client={"client_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, - server={"server_routes": {"*.*": "loopback_cdp"}}, - ) - target_ids = [] - client.connect() - try: - asyncio.run(run_awaited_calls()) - finally: - for target_id in target_ids: - try: - client.Target.closeTarget(targetId=target_id) - except Exception: - pass - client.close() - - def test_generated_event_surface_supports_awaited_on_and_async_callbacks(self) -> None: + def test_modcdpclient_event_dispatch_snapshots_handlers_when_once_removes_itself(self) -> None: client = ModCDPClient() - seen: list[str] = [] + seen: Queue[str] = Queue() - async def callback(event: Any) -> None: - seen.append(event.targetId) + def persistent(_payload: Mapping[str, Any]) -> None: + seen.put("persistent") - async def register() -> None: - await client.on(client.Target.targetCreated, callback) + client.once("Target.targetCreated", lambda _payload: seen.put("once")) + client.on("Target.targetCreated", persistent) + client._on_recv( + { + "method": "Target.targetCreated", + "params": { + "targetInfo": { + "targetId": "target-1", + "type": "page", + "title": "about:blank", + "url": "about:blank", + "attached": False, + "canAccessOpener": False, + } + }, + } + ) + self.assertEqual([seen.get(timeout=1), seen.get(timeout=1)], ["once", "persistent"]) - asyncio.run(register()) - client._run_handler( - client._handlers["Target.targetCreated"][0], - {"targetInfo": {"targetId": "target-1", "type": "page", "url": "https://example.com"}}, - "Target.targetCreated", + client._on_recv( + { + "method": "Target.targetCreated", + "params": { + "targetInfo": { + "targetId": "target-2", + "type": "page", + "title": "about:blank", + "url": "about:blank", + "attached": False, + "canAccessOpener": False, + } + }, + } ) - self.assertEqual(seen, ["target-1"]) + self.assertEqual(seen.get(timeout=1), "persistent") + with self.assertRaises(Empty): + seen.get(timeout=0.1) - def test_event_dispatch_snapshots_handlers_when_once_removes_itself(self) -> None: + def test_modcdpclient_validates_native_command_params_before_sending(self) -> None: client = ModCDPClient() - client.ext_session_id = "ext-session" - modcdp_client_module = importlib.import_module("modcdp.client.ModCDPClient") - original_thread = modcdp_client_module.threading.Thread - seen: list[str] = [] - - class ImmediateThread: - def __init__(self, target, daemon=False): # noqa: ANN001 - self.target = target - def start(self) -> None: - self.target() + with self.assertRaisesRegex(Exception, "expression"): + client.send("Runtime.evaluate", {}) - def persistent(_payload: Mapping[str, Any]) -> None: - seen.append("persistent") - - try: - cast(Any, modcdp_client_module.threading).Thread = ImmediateThread - client.once("Target.targetCreated", lambda _payload: seen.append("once")) - client.on("Target.targetCreated", persistent) - client._on_recv( - { - "method": "Target.targetCreated", - "params": {"targetInfo": {"targetId": "target-1", "type": "page", "url": "about:blank"}}, - } - ) - self.assertEqual(seen, ["once", "persistent"]) - - seen.clear() - client._on_recv( - { - "method": "Target.targetCreated", - "params": {"targetInfo": {"targetId": "target-2", "type": "page", "url": "about:blank"}}, - } - ) - self.assertEqual(seen, ["persistent"]) - finally: - cast(Any, modcdp_client_module.threading).Thread = original_thread - - def test_root_events_dispatch_before_extension_session_is_attached(self) -> None: + def test_modcdpclient_validates_native_and_registered_custom_events_before_dispatch(self) -> None: client = ModCDPClient() - modcdp_client_module = importlib.import_module("modcdp.client.ModCDPClient") - original_thread = modcdp_client_module.threading.Thread - seen: list[str] = [] - - class ImmediateThread: - def __init__(self, target, daemon=False): # noqa: ANN001 - self.target = target - def start(self) -> None: - self.target() + with self.assertRaisesRegex(Exception, "targetInfo"): + client._on_recv({"method": "Target.targetCreated", "params": {}}) - try: - cast(Any, modcdp_client_module.threading).Thread = ImmediateThread - client.on("Target.targetCreated", lambda payload: seen.append(str(payload["targetInfo"]["targetId"]))) - client._on_recv( - { - "method": "Target.targetCreated", - "params": {"targetInfo": {"targetId": "target-1", "type": "page", "url": "about:blank"}}, - } - ) - self.assertEqual(seen, ["target-1"]) - finally: - cast(Any, modcdp_client_module.threading).Thread = original_thread + client.Mod.addCustomEvent( + "Custom.ready", + event_schema={ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + ) + with self.assertRaisesRegex(Exception, "boolean"): + client._on_recv({"method": "Custom.ready", "params": {"ok": "yes"}}) - def test_schema_only_custom_command_registers_without_websocket(self) -> None: + def test_modcdpclient_dispatches_root_events_before_extension_session_is_attached(self) -> None: client = ModCDPClient() + seen: Queue[str] = Queue() - result = client.send( - "Mod.addCustomCommand", + client.on("Target.targetCreated", lambda payload: seen.put(str(payload["targetInfo"]["targetId"]))) + client._on_recv( { - "name": "Custom.echo", - "params_schema": { - "type": "object", - "properties": {"text": {"type": "string", "minLength": 1}}, - "required": ["text"], - "additionalProperties": False, - }, - "result_schema": { - "type": "object", - "properties": {"text": {"type": "string"}}, - "required": ["text"], - "additionalProperties": False, + "method": "Target.targetCreated", + "params": { + "targetInfo": { + "targetId": "target-1", + "type": "page", + "title": "about:blank", + "url": "about:blank", + "attached": False, + "canAccessOpener": False, + } }, - }, - ) - - self.assertEqual(result, {"name": "Custom.echo", "registered": True}) - self.assertEqual(client._validate_command_params("Custom.echo", {"text": "ok"}), {"text": "ok"}) - with self.assertRaises(ValueError): - client._validate_command_params("Custom.echo", {"text": ""}) - with self.assertRaises(ValueError): - client._validate_command_params("Custom.echo", {"text": "ok", "extra": True}) - self.assertEqual(client._validate_command_result("Custom.echo", {"text": "ok"}), {"text": "ok"}) - with self.assertRaises(ValueError): - client._validate_command_result("Custom.echo", {"text": 123}) - - def test_constructor_custom_command_schemas_validate_nested_json(self) -> None: - client = ModCDPClient( - custom_commands=[ - { - "name": "Custom.collect", - "params_schema": { - "type": "object", - "properties": { - "items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "id": {"type": "string"}, - "count": {"type": "integer", "minimum": 1}, - }, - "required": ["id", "count"], - "additionalProperties": False, - }, - } - }, - "required": ["items"], - "additionalProperties": False, - }, - } - ] + } ) + self.assertEqual(seen.get(timeout=1), "target-1") - valid: dict[str, JsonValue] = {"items": [{"id": "a", "count": 1}]} - self.assertEqual(client._validate_command_params("Custom.collect", valid), valid) - with self.assertRaises(ValueError): - client._validate_command_params("Custom.collect", {"items": [{"id": "a", "count": 0}]}) - with self.assertRaises(ValueError): - client._validate_command_params("Custom.collect", {"items": []}) - - def test_custom_event_schema_validates_payload_before_handlers(self) -> None: - client = ModCDPClient( - custom_events=[ - { - "name": "Custom.ready", - "event_schema": { - "type": "object", - "properties": { - "url": {"type": "string", "pattern": "^https://"}, - "ready": {"type": "boolean"}, - }, - "required": ["url", "ready"], - "additionalProperties": False, - }, - } - ] - ) + def test_modcdpclient_uses_no_injector_unless_injector_mode_is_explicit(self) -> None: + launched = ModCDPClient(launcher={"launcher_mode": "local"}, upstream={"upstream_mode": "ws"}) + self.assertEqual(launched.launcher.config.launcher_mode, "local") + self.assertIsNone(launched.injector) - payload: dict[str, JsonValue] = {"url": "https://example.com", "ready": True} - self.assertEqual(client._validate_event_payload("Custom.ready", payload), payload) - with self.assertRaises(ValueError): - client._validate_event_payload("Custom.ready", {"url": "http://example.com", "ready": True}) - with self.assertRaises(ValueError): - client._validate_event_payload("Custom.ready", {"url": "https://example.com", "ready": True, "x": 1}) + attach_only = ModCDPClient(upstream={"upstream_mode": "ws"}) + self.assertEqual(attach_only.launcher.config.launcher_mode, "none") + self.assertIsNone(attach_only.injector) - def test_scalar_event_schema_validates_value_payloads(self) -> None: - client = ModCDPClient(custom_events=[{"name": "Custom.count", "event_schema": {"type": "integer", "minimum": 1}}]) - self.assertEqual(client._validate_event_payload("Custom.count", {"value": 3}), {"value": 3}) - with self.assertRaises(ValueError): - client._validate_event_payload("Custom.count", {"value": 0}) +def object_map(value: object) -> Mapping[str, object]: + if not isinstance(value, Mapping): + raise AssertionError(f"expected object mapping, got {value!r}") + return {str(key): raw_value for key, raw_value in value.items()} if __name__ == "__main__": diff --git a/python/tests/test_ModCDPClientCustomFlatNamespace.py b/python/tests/test_ModCDPClientCustomFlatNamespace.py index 18725503..9804b97c 100644 --- a/python/tests/test_ModCDPClientCustomFlatNamespace.py +++ b/python/tests/test_ModCDPClientCustomFlatNamespace.py @@ -1,7 +1,17 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.ModCDPClientCustomFlatNamespace.ts +# - ./go/modcdp/client/ModCDPClientCustomFlatNamespace_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations import asyncio +import glob +import os +import re from queue import Queue +import sys import unittest from pathlib import Path @@ -10,14 +20,50 @@ from modcdp import ModCDPClient +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep the setup semantics below 1:1 with translated tests; helpers here only select real browsers for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + + ROOT = Path(__file__).resolve().parents[2] EXTENSION_PATH = ROOT / "dist" / "extension" +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() class ModCDPClientCustomFlatNamespaceTests(unittest.TestCase): - def test_pydantic_custom_command_installs_flat_dynamic_method_through_real_service_worker(self) -> None: + def test_custom_commands_install_flat_namespace_methods_through_a_real_service_worker(self) -> None: class ParamsSchema(BaseModel): id: str + suffix: str = "" class ResultSchema(BaseModel): success: bool @@ -25,70 +71,93 @@ class ResultSchema(BaseModel): client = ModCDPClient( launcher={ "launcher_mode": "local", - "launcher_options": {"headless": True}, + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, - client={"client_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, - server={"server_routes": {"*.*": "loopback_cdp"}}, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, + types={ + "custom_commands": { + "Custom.doSomething": { + "params_schema": ParamsSchema, + "result_schema": ResultSchema, + "expression": "async ({ id, suffix = '' }) => ({ success: `${id}${suffix}` === 'abcmiddleware' })", + }, + "Custom.badResult": { + "params_schema": { + "type": "object", + "properties": {"id": {"type": "string"}}, + "required": ["id"], + "additionalProperties": False, + }, + "result_schema": ResultSchema, + "expression": "async () => ({ success: 'yes' })", + }, + }, + "custom_middlewares": [ + { + "name": "Custom.doSomething", + "phase": "request", + "expression": "async (payload, next) => next({ ...payload, suffix: 'middleware' })", + } + ], + }, ) async def run() -> None: client.connect() - registered = await client.Mod.addCustomCommand( - "Custom.doSomething", - params_schema=ParamsSchema, - result_schema=ResultSchema, - expression="async ({ id }) => ({ success: id === 'abc' })", - ) - self.assertEqual(registered, {"name": "Custom.doSomething", "registered": True}) - success: bool = await client.Custom.doSomething(id="abc") - raw_success: bool = bool(await client.send("Custom.doSomething", {"id": "abc"})) - self.assertIs(success, True) - self.assertIs(raw_success, True) + success = await client.Custom.doSomething(id="abc") + raw_success = await client.send("Custom.doSomething", {"id": "abc"}) + self.assertEqual(success, {"success": True}) + self.assertEqual(raw_success, {"success": True}) + with self.assertRaises(ValueError): + await client.Custom.doSomething(id=123) + with self.assertRaisesRegex(Exception, "boolean"): + await client.Custom.badResult(id="abc") try: asyncio.run(run()) - with self.assertRaises(ValueError): - client.Custom.doSomething(id=123) finally: client.close() - def test_pydantic_custom_event_schema_coerces_raw_string_handlers_through_real_service_worker(self) -> None: + def test_custom_events_validate_raw_string_handlers_through_a_real_service_worker(self) -> None: class EventSchema(BaseModel): data: str client = ModCDPClient( launcher={ "launcher_mode": "local", - "launcher_options": {"headless": True}, + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, - client={"client_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, - server={"server_routes": {"*.*": "loopback_cdp"}}, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, ) seen: Queue[str] = Queue() - async def callback(event: EventSchema) -> None: - seen.put(event.data) + async def callback(event: dict[str, str]) -> None: + seen.put(event["data"]) async def run() -> None: client.connect() await client.Mod.addCustomEvent("Custom.someEvent", event_schema=EventSchema) await client.on("Custom.someEvent", callback) await client.Mod.evaluate( - expression="async () => await globalThis.ModCDP.emit('Custom.someEvent', { data: 'ok' })" + expression="async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.someEvent', data: { data: 'ok' }, cdpSessionId: null }))" ) try: @@ -97,26 +166,425 @@ async def run() -> None: finally: client.close() - def test_schema_only_custom_event_registers_without_websocket(self) -> None: - client = ModCDPClient() + def test_dynamic_custom_command_event_and_middleware_registration_validates_through_a_real_service_worker(self) -> None: + client = ModCDPClient( + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, + ) + seen: Queue[str] = Queue() + + async def run() -> None: + client.connect() + self.assertEqual( + await client.Mod.addCustomCommand( + "Custom.dynamic", + params_schema={ + "type": "object", + "properties": {"text": {"type": "string", "minLength": 1}}, + "required": ["text"], + "additionalProperties": False, + }, + result_schema={ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + expression="async ({ text }) => ({ ok: text === 'live-dynamic' })", + ), + {"name": "Custom.dynamic", "registered": True}, + ) + self.assertEqual( + await client.Mod.addCustomCommand( + "Custom.dynamicBadResult", + params_schema={ + "type": "object", + "properties": {"text": {"type": "string"}}, + "required": ["text"], + "additionalProperties": False, + }, + result_schema={ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + expression="async () => ({ ok: 'yes' })", + ), + {"name": "Custom.dynamicBadResult", "registered": True}, + ) + self.assertEqual( + await client.Mod.addCustomEvent( + "Custom.dynamicReady", + event_schema={ + "type": "object", + "properties": {"id": {"type": "string", "format": "uuid"}}, + "required": ["id"], + "additionalProperties": False, + }, + ), + {"name": "Custom.dynamicReady", "registered": True}, + ) + self.assertEqual( + await client.Mod.addMiddleware( + name="Custom.dynamic", + phase="request", + expression="async (payload, next) => next({ ...payload, text: `${payload.text}-dynamic` })", + ), + {"name": "Custom.dynamic", "phase": "request", "registered": True}, + ) + + self.assertEqual(await client.send("Custom.dynamic", {"text": "live"}), {"ok": True}) + with self.assertRaises(ValueError): + await client.send("Custom.dynamic", {"text": ""}) + with self.assertRaisesRegex(Exception, "boolean"): + await client.send("Custom.dynamicBadResult", {"text": "live"}) + with self.assertRaises(ValueError): + await client.Mod.addMiddleware( + name="Custom.dynamic", + phase="after", + expression="async (payload, next) => next(payload)", + ) + + await client.on("Custom.dynamicReady", lambda _event: seen.put("ready")) + await client.Mod.evaluate( + expression="async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.dynamicReady', data: { id: '550e8400-e29b-41d4-a716-446655440000' }, cdpSessionId: null }))" + ) + + try: + asyncio.run(run()) + self.assertEqual(seen.get(timeout=10), "ready") + finally: + client.close() + + def test_assigned_type_registry_validates_updated_custom_command_event_and_middleware_schemas_through_a_real_service_worker(self) -> None: + client = ModCDPClient( + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, + ) + client.types = client.types.update( + custom_commands={ + "Custom.updated": { + "params_schema": { + "type": "object", + "properties": {"count": {"type": "integer", "minimum": 0}}, + "required": ["count"], + "additionalProperties": False, + }, + "result_schema": { + "type": "object", + "properties": {"done": {"type": "boolean"}}, + "required": ["done"], + "additionalProperties": False, + }, + "expression": "async ({ count }) => ({ done: count === 2 })", + }, + "Custom.updatedBadResult": { + "params_schema": { + "type": "object", + "properties": {"count": {"type": "number"}}, + "required": ["count"], + "additionalProperties": False, + }, + "result_schema": { + "type": "object", + "properties": {"done": {"type": "boolean"}}, + "required": ["done"], + "additionalProperties": False, + }, + "expression": "async () => ({ done: 'yes' })", + }, + }, + custom_events={ + "Custom.updatedReady": { + "event_schema": { + "type": "object", + "properties": {"ready": {"type": "boolean"}}, + "required": ["ready"], + "additionalProperties": False, + } + } + }, + custom_middlewares=[ + { + "name": "Custom.updated", + "phase": "request", + "expression": "async (payload, next) => next({ ...payload, count: payload.count + 1 })", + } + ], + ) + seen: Queue[bool] = Queue() + + async def run() -> None: + client.connect() + self.assertEqual(await client.send("Custom.updated", {"count": 1}), {"done": True}) + with self.assertRaises(ValueError): + await client.send("Custom.updated", {"count": -1}) + with self.assertRaisesRegex(Exception, "boolean"): + await client.send("Custom.updatedBadResult", {"count": 1}) + + await client.on("Custom.updatedReady", lambda _event: seen.put(True)) + await client.Mod.evaluate( + expression="async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.updatedReady', data: { ready: true }, cdpSessionId: null }))" + ) + + try: + asyncio.run(run()) + self.assertEqual(seen.get(timeout=10), True) + finally: + client.close() + + def test_service_worker_server_validates_registered_custom_command_and_event_schemas(self) -> None: + client = ModCDPClient( + launcher={ + "launcher_mode": "local", + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, + }, + upstream={"upstream_mode": "ws"}, + injector={ + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), + "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], + "injector_trust_service_worker_target": True, + }, + router={"router_routes": {"Mod.*": "service_worker", "Custom.*": "service_worker", "*.*": "direct_cdp"}}, + server_config={"router": {"router_routes": {"*.*": "loopback_cdp"}}}, + ) + seen: Queue[bool] = Queue() + + async def run() -> None: + client.connect() + self.assertEqual( + await client.Mod.addCustomCommand( + "Custom.double", + params_schema={ + "type": "object", + "properties": {"value": {"type": "number"}}, + "required": ["value"], + "additionalProperties": False, + }, + result_schema={ + "type": "object", + "properties": {"value": {"type": "number"}}, + "required": ["value"], + "additionalProperties": False, + }, + expression="async (params) => ({ value: params.value * 2 })", + ), + {"name": "Custom.double", "registered": True}, + ) + self.assertEqual(client.types.parseCommandParams("Custom.double", {"value": 2}), {"value": 2}) + self.assertEqual(client.types.parseCommandResult("Custom.double", {"value": 4}), {"value": 4}) + self.assertEqual(await client.send("Custom.double", {"value": 2}), {"value": 4}) + + self.assertEqual( + await client.Mod.addCustomCommand( + "Custom.badResult", + result_schema={ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + expression='async () => ({ ok: "yes" })', + ), + {"name": "Custom.badResult", "registered": True}, + ) + with self.assertRaisesRegex(Exception, "boolean"): + await client.send("Custom.badResult", {}) + + self.assertEqual( + await client.Mod.addCustomEvent( + "Custom.ready", + event_schema={ + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + ), + {"name": "Custom.ready", "registered": True}, + ) + self.assertEqual(client.types.parseEventPayload("Custom.ready", {"ok": True}), {"ok": True}) + with self.assertRaises(ValueError): + client.types.parseEventPayload("Custom.ready", {"ok": "yes"}) + await client.on("Custom.ready", lambda event: seen.put(bool(event["ok"]))) + await client.Mod.evaluate( + expression="async () => globalThis.__ModCDP_custom_event__(JSON.stringify({ event: 'Custom.ready', data: { ok: true }, cdpSessionId: null }))" + ) + + try: + asyncio.run(run()) + self.assertEqual(seen.get(timeout=10), True) + finally: + client.close() + + def test_schema_only_custom_commands_register_without_a_websocket(self) -> None: + client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + ) result = client.send( - "Mod.addCustomEvent", + "Mod.addCustomCommand", { - "name": "Custom.schemaOnly", - "event_schema": { + "name": "Custom.echo", + "params_schema": { + "type": "object", + "properties": {"text": {"type": "string", "minLength": 1}}, + "required": ["text"], + "additionalProperties": False, + }, + "result_schema": { "type": "object", - "properties": {"ok": {"type": "boolean"}}, - "required": ["ok"], + "properties": {"text": {"type": "string"}}, + "required": ["text"], "additionalProperties": False, }, }, ) - self.assertEqual(result, {"name": "Custom.schemaOnly", "registered": True}) - self.assertEqual(client._validate_event_payload("Custom.schemaOnly", {"ok": True}), {"ok": True}) + self.assertEqual(result, {"name": "Custom.echo", "registered": True}) + self.assertEqual(client.types.parseCommandParams("Custom.echo", {"text": "ok"}), {"text": "ok"}) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.echo", {"text": ""}) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.echo", {"text": "ok", "extra": True}) + self.assertEqual(client.types.parseCommandResult("Custom.echo", {"text": "ok"}), {"text": "ok"}) + with self.assertRaises(ValueError): + client.types.parseCommandResult("Custom.echo", {"text": 123}) + + def test_constructor_custom_command_and_event_schemas_validate_nested_payloads(self) -> None: + client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + types={ + "custom_commands": [ + { + "name": "Custom.collect", + "params_schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "count": {"type": "integer", "minimum": 1}, + }, + "required": ["id", "count"], + "additionalProperties": False, + }, + }, + }, + "required": ["items"], + "additionalProperties": False, + }, + } + ], + "custom_events": [ + { + "name": "Custom.ready", + "event_schema": { + "type": "object", + "properties": {"url": {"type": "string", "pattern": "^https://"}, "ready": {"type": "boolean"}}, + "required": ["url", "ready"], + "additionalProperties": False, + }, + }, + {"name": "Custom.count", "event_schema": {"type": "integer", "minimum": 1}}, + ], + }, + ) + + valid_params = {"items": [{"id": "a", "count": 1}]} + self.assertEqual(client.types.parseCommandParams("Custom.collect", valid_params), valid_params) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.collect", {"items": [{"id": "a", "count": 0}]}) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.collect", {"items": []}) + self.assertEqual( + client.types.parseEventPayload("Custom.ready", {"url": "https://example.com", "ready": True}), + {"url": "https://example.com", "ready": True}, + ) + with self.assertRaises(ValueError): + client.types.parseEventPayload("Custom.ready", {"url": "http://example.com", "ready": True}) + self.assertEqual(client.types.parseEventPayload("Custom.count", {"value": 3}), {"value": 3}) with self.assertRaises(ValueError): - client._validate_event_payload("Custom.schemaOnly", {"ok": True, "extra": True}) + client.types.parseEventPayload("Custom.count", {"value": 0}) + + def test_assigned_type_registry_updates_runtime_validation_and_aliases(self) -> None: + client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + ) + + client.types = client.types.update( + custom_commands={ + "Custom.later": { + "params_schema": { + "type": "object", + "properties": {"value": {"type": "number"}}, + "required": ["value"], + "additionalProperties": False, + }, + "result_schema": { + "type": "object", + "properties": {"ok": {"type": "boolean"}}, + "required": ["ok"], + "additionalProperties": False, + }, + } + }, + custom_events={ + "Custom.laterReady": { + "event_schema": { + "type": "object", + "properties": {"value": {"type": "string"}}, + "required": ["value"], + "additionalProperties": False, + } + } + }, + ) + + self.assertTrue(callable(client.Custom.later)) + self.assertEqual(client.types.parseCommandParams("Custom.later", {"value": 1}), {"value": 1}) + self.assertEqual(client.types.parseCommandResult("Custom.later", {"ok": True}), {"ok": True}) + self.assertEqual(client.types.parseEventPayload("Custom.laterReady", {"value": "ok"}), {"value": "ok"}) if __name__ == "__main__": diff --git a/python/tests/test_ModCDPClientRoutedDefaultOverrides.py b/python/tests/test_ModCDPClientRoutedDefaultOverrides.py index 24db6c3a..945a4c0b 100644 --- a/python/tests/test_ModCDPClientRoutedDefaultOverrides.py +++ b/python/tests/test_ModCDPClientRoutedDefaultOverrides.py @@ -1,20 +1,65 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.ModCDPClientRoutedDefaultOverrides.ts +# - ./go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations +import glob +import os +import re +import sys from pathlib import Path from queue import Empty, Queue -from typing import Any, cast +from typing import Any import unittest from modcdp import ModCDPClient +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep setup semantics 1:1 with TS; this only selects a real browser for real --load-extension runs. +def load_extension_test_browser_path() -> str: + for candidate in (os.environ.get("CHROME_PATH"), "/usr/bin/chromium" if sys.platform.startswith("linux") else None): + if candidate and Path(candidate).exists(): + return candidate + home = Path.home() + if sys.platform == "darwin": + patterns = [ + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), + str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), + ] + elif sys.platform.startswith("win"): + local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") + patterns = [ + str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), + str(home / ".cache/puppeteer/chrome/win*-*/chrome.exe"), + ] + else: + patterns = [ + str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), + "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", + str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), + ] + candidates = sorted( + dict.fromkeys(match for pattern in patterns for match in glob.glob(pattern)), + key=lambda path: (-max([int(part) for part in re.findall(r"\d+", path)] or [0]), -Path(path).stat().st_mtime, path), + ) + if candidates: + return candidates[0] + raise RuntimeError("No browser found for --load-extension tests. Install Chrome for Testing or set CHROME_PATH.") + + HERE = Path(__file__).resolve().parent EXTENSION_PATH = HERE.parents[1] / "dist" / "extension" +LOAD_EXTENSION_TEST_BROWSER_PATH = load_extension_test_browser_path() GET_TARGETS_OVERRIDE = r""" async (params) => { const [upstream, tabs] = await Promise.all([ - ModCDP.sendLoopback("Target.getTargets", params), + cdp.upstream.send("Target.getTargets", params), chrome.tabs.query({}), ]); @@ -37,9 +82,8 @@ TAB_ID_FROM_TARGET_ID_COMMAND = r""" async ({ targetId }) => { - const targets = await chrome.debugger.getTargets(); - const target = targets.find(target => target.id === targetId); - if (target?.tabId != null) return { tabId: target.tabId }; + const { targetInfos = [] } = await cdp.upstream.send("Target.getTargets", {}); + const target = targetInfos.find(target => target.targetId === targetId); const tabs = await chrome.tabs.query({}); const tab = tabs.find(tab => target?.url && (tab.url === target.url || tab.pendingUrl === target.url)); return { tabId: tab?.id ?? null }; @@ -52,7 +96,7 @@ const visit = async value => { if (!value || typeof value !== "object" || seen.has(value)) return; seen.add(value); - if (!Array.isArray(value) && typeof value.targetId === "string" && value.tabId == null) { + if (!Array.isArray(value) && typeof value.targetId === "string" && typeof value.type === "string" && value.tabId == null) { const { tabId } = await cdp.send("Custom.tabIdFromTargetId", { targetId: value.targetId }); if (tabId != null) value.tabId = tabId; } @@ -65,8 +109,15 @@ def target_infos_from_result(result: Any) -> list[dict[str, Any]]: - result_map = cast(dict[str, Any], result) - return cast(list[dict[str, Any]], result_map["targetInfos"]) + if not isinstance(result, dict): + raise AssertionError(f"result = {result!r}") + target_infos = result.get("targetInfos") + if not isinstance(target_infos, list): + raise AssertionError(f"targetInfos = {target_infos!r}") + for target_info in target_infos: + if not isinstance(target_info, dict): + raise AssertionError(f"targetInfo = {target_info!r}") + return target_infos class ModCDPClientRoutedDefaultOverridesTests(unittest.TestCase): @@ -74,49 +125,62 @@ def test_service_worker_routed_standard_cdp_commands_and_events_can_be_transform owner = ModCDPClient( launcher={ "launcher_mode": "local", - "launcher_options": {"headless": True}, + "launcher_local_headless": True, + "launcher_local_executable_path": LOAD_EXTENSION_TEST_BROWSER_PATH, }, upstream={"upstream_mode": "ws"}, injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), + "injector_mode": "cli", + "injector_cli_extension_path": str(EXTENSION_PATH), "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, ) owner.connect() cdp = ModCDPClient( - launcher={"launcher_mode": "remote"}, - upstream={"upstream_mode": "ws", "upstream_cdp_url": owner.cdp_url}, + launcher={"launcher_mode": "remote", "launcher_remote_cdp_url": owner.cdp_url}, + upstream={"upstream_mode": "ws", "upstream_ws_cdp_url": owner.cdp_url}, injector={ "injector_mode": "discover", "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], "injector_trust_service_worker_target": True, }, - client={ - "client_routes": { + router={"router_routes": { "Target.getTargets": "service_worker", "Target.createTarget": "service_worker", "Target.setDiscoverTargets": "service_worker", } }, - server={"server_loopback_cdp_url": owner.cdp_url, "server_routes": {"*.*": "loopback_cdp"}}, + server_config={"upstream": {"upstream_ws_cdp_url": owner.cdp_url}, "router": {"router_routes": {"*.*": "loopback_cdp"}}}, ) try: cdp.connect() self.assertEqual(cdp.cdp_url, owner.cdp_url) - self.assertIsNotNone(cdp.server) - server = cast(dict[str, Any], cdp.server) - self.assertEqual(server["server_loopback_cdp_url"], owner.cdp_url) + self.assertIsNotNone(cdp.server_config) + server_config = cdp.server_config + if server_config is None or server_config.upstream is None: + self.fail(f"server_config = {server_config!r}") + self.assertEqual(server_config.upstream.upstream_ws_cdp_url, owner.cdp_url) raw_targets = cdp.send("Target.getTargets") raw_target_infos = target_infos_from_result(raw_targets) self.assertTrue(raw_target_infos) self.assertFalse(any("tabId" in target_info for target_info in raw_target_infos)) - cdp.Mod.addCustomCommand("Custom.tabIdFromTargetId", expression=TAB_ID_FROM_TARGET_ID_COMMAND) - cdp.Mod.addMiddleware(name="*", phase="response", expression=ADD_TAB_ID_MIDDLEWARE) + cdp.Mod.addCustomCommand( + { + "name": "Custom.tabIdFromTargetId", + "expression": TAB_ID_FROM_TARGET_ID_COMMAND, + } + ) + cdp.Mod.addMiddleware( + { + "name": "*", + "phase": "response", + "expression": ADD_TAB_ID_MIDDLEWARE, + } + ) middleware_targets = cdp.send("Target.getTargets") self.assertTrue( any( @@ -125,8 +189,19 @@ def test_service_worker_routed_standard_cdp_commands_and_events_can_be_transform ) ) - cdp.Mod.addMiddleware(name="*", phase="event", expression=ADD_TAB_ID_MIDDLEWARE) - cdp.Mod.addCustomCommand("Target.getTargets", expression=GET_TARGETS_OVERRIDE) + cdp.Mod.addMiddleware( + { + "name": "*", + "phase": "event", + "expression": ADD_TAB_ID_MIDDLEWARE, + } + ) + cdp.Mod.addCustomCommand( + { + "name": "Target.getTargets", + "expression": GET_TARGETS_OVERRIDE, + } + ) enriched_targets = cdp.send("Target.getTargets") enriched_target_infos = target_infos_from_result(enriched_targets) @@ -139,7 +214,25 @@ def test_service_worker_routed_standard_cdp_commands_and_events_can_be_transform ) ) - cdp.Mod.addCustomEvent("Target.targetCreated") + topology = cdp.Mod.getTopology() + if not isinstance(topology, dict): + self.fail(f"topology = {topology!r}") + root_frame_id = topology.get("rootFrameId") + frames = topology.get("frames") + roots = topology.get("roots") + contexts = topology.get("contexts") + if not isinstance(frames, dict): + self.fail(f"frames = {frames!r}") + if not isinstance(roots, dict): + self.fail(f"roots = {roots!r}") + if not isinstance(contexts, dict): + self.fail(f"contexts = {contexts!r}") + self.assertIsInstance(root_frame_id, str) + self.assertIn(root_frame_id, frames) + self.assertTrue(any(root.get("kind") == "document" for root in roots.values())) + self.assertTrue(any(context.get("world") == "piercer" for context in contexts.values())) + + cdp.Mod.addCustomEvent({"name": "Target.targetCreated"}) transformed_events: Queue[dict] = Queue() def on_target_created(params): diff --git a/python/tests/test_ModCDPClientTypedEventInference.py b/python/tests/test_ModCDPClientTypedEventInference.py index e6f06f50..fea3b7eb 100644 --- a/python/tests/test_ModCDPClientTypedEventInference.py +++ b/python/tests/test_ModCDPClientTypedEventInference.py @@ -1,28 +1,52 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.ModCDPClientTypedEventInference.ts +# - ./go/modcdp/client/ModCDPClientTypedEventInference_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations import unittest -from typing import Any, cast +from collections.abc import Mapping +from typing import Any from modcdp import ModCDPClient -from modcdp.types import JsonValue class ModCDPClientTypedEventInferenceTests(unittest.TestCase): def test_typed_cdp_event_tokens_infer_callback_payloads_without_local_type_aliases(self) -> None: - client = ModCDPClient() - typed_events: list[Any] = [] - raw_events: list[dict[str, JsonValue]] = [] - - client.on(client.Target.targetCreated, typed_events.append) - client.on("Target.targetCreated", raw_events.append) - - payload = {"targetInfo": {"targetId": "target-1", "type": "page", "url": "https://example.com"}} - for handler in client._handlers["Target.targetCreated"]: - handler(payload) - - self.assertEqual(typed_events[0].targetId, "target-1") - target_info = cast(dict[str, JsonValue], raw_events[0]["targetInfo"]) - self.assertEqual(target_info["targetId"], "target-1") + client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + ) + seen: list[str] = [] + + def on_target_created(event: Any) -> None: + target_info = event.targetInfo + if not isinstance(target_info, Mapping): + raise AssertionError(f"targetInfo = {target_info!r}") + seen.append(str(target_info["targetId"])) + + client.on(client.Target.targetCreated, on_target_created) + client._on_recv( + { + "method": "Target.targetCreated", + "params": { + "targetInfo": { + "targetId": "target-1", + "type": "page", + "title": "Example", + "url": "https://example.com", + "attached": True, + "canAccessOpener": False, + } + }, + } + ) + + self.assertEqual(seen, ["target-1"]) if __name__ == "__main__": diff --git a/python/tests/test_ModCDPClient_protocol_validation.py b/python/tests/test_ModCDPClient_protocol_validation.py index b672ea44..37ea3574 100644 --- a/python/tests/test_ModCDPClient_protocol_validation.py +++ b/python/tests/test_ModCDPClient_protocol_validation.py @@ -1,125 +1,239 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/ModCDPClient_protocol_validation.test.ts +# - ./go/modcdp/client/ModCDPClient_protocol_validation_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations import unittest -from typing import TYPE_CHECKING, assert_type -from pydantic import BaseModel +from pydantic import BaseModel, Field from modcdp import ModCDPClient -from modcdp.client.ModCDPClient import AwaitableValue -from modcdp.types.generated.cdp import RuntimeDomain, TargetDomain -from modcdp.types.generated.cdp import AwaitableDict +from modcdp.types.CDPTypes import CDPTypes from modcdp.types.modcdp import JsonValue, ProtocolPayload -class CustomEchoParams(BaseModel): - text: str +class SumParams(BaseModel): + left: int + right: int -class CustomEchoResult(BaseModel): - ok: bool +class SumResult(BaseModel): + value: int + + +class FinishedEvent(BaseModel): + total: int + label: str + +class DynamicParams(BaseModel): + text: str = Field(min_length=1) -class CustomReadyEvent(BaseModel): + +class DynamicResult(BaseModel): ok: bool -class ModCDPClientProtocolValidationTests(unittest.TestCase): - def test_protocol_validation_covers_native_methods_native_events_custom_methods_custom_events_and_native_overrides(self) -> None: - client = ModCDPClient() - - if TYPE_CHECKING: - runtime_result = client.Runtime.evaluate(expression="1 + 1", returnByValue=True) - assert_type(runtime_result, RuntimeDomain._EvaluateResult) - - def on_target_created(event: TargetDomain._TargetCreatedEvent) -> None: - assert_type(event.targetId, str | None) - - assert_type(client.on(client.Target.targetCreated, on_target_created), ModCDPClient) - assert_type( - client.Mod.addCustomCommand("Custom.echo", params_schema=CustomEchoParams, result_schema=CustomEchoResult), - AwaitableDict | AwaitableValue, - ) - assert_type(client.Mod.addCustomEvent("Custom.ready", event_schema=CustomReadyEvent), AwaitableDict | AwaitableValue) - assert_type( - client.Mod.addMiddleware( - name="Target.getTargets", - phase="response", - expression="async (value, next) => next(value)", - ), - AwaitableDict | AwaitableValue, - ) +class UpdatedParams(BaseModel): + count: int = Field(gt=0) + + +class UpdatedResult(BaseModel): + done: bool + +class UpdatedReadyEvent(BaseModel): + ready: bool + + +class ModCDPClientProtocolValidationTests(unittest.TestCase): + def test_native_cdp_schemas_validate_method_params_return_values_and_event_payloads_statically_and_at_runtime(self) -> None: + types = CDPTypes() + client = ModCDPClient(launcher={"launcher_mode": "none"}, upstream={"upstream_mode": "ws"}, injector={"injector_mode": "none"}, server_config=None) runtime_params = {"expression": "1 + 1", "returnByValue": True} - runtime_result_payload = {"result": {"type": "number", "value": 2, "description": "2"}} - native_target_info: dict[str, JsonValue] = { - "targetId": "target-1", - "type": "page", - "title": "Example", - "url": "https://example.com", - "attached": False, - "canAccessOpener": False, + runtime_result = {"result": {"type": "number", "value": 2, "description": "2"}} + target_event: ProtocolPayload = { + "targetInfo": { + "targetId": "target-1", + "type": "page", + "title": "Example", + "url": "https://example.com", + "attached": False, + "canAccessOpener": False, + } } - native_event_payload: ProtocolPayload = {"targetInfo": native_target_info} - - self.assertEqual(client._validate_command_params("Runtime.evaluate", runtime_params), runtime_params) - self.assertEqual(client._validate_command_result("Runtime.evaluate", runtime_result_payload), runtime_result_payload) - self.assertEqual(client._validate_event_payload("Target.targetCreated", native_event_payload), native_event_payload) - with self.assertRaises(ValueError): - client._validate_command_params("Runtime.evaluate", {}) - with self.assertRaises(ValueError): - client._validate_command_result("Runtime.evaluate", {}) - with self.assertRaises(ValueError): - client._validate_event_payload("Target.targetCreated", {}) - - client.Mod.addCustomCommand("Custom.echo", params_schema=CustomEchoParams, result_schema=CustomEchoResult) - client.Mod.addCustomEvent("Custom.ready", event_schema=CustomReadyEvent) - - self.assertEqual(client._validate_command_params("Custom.echo", {"text": "ok"}), {"text": "ok"}) - self.assertEqual(client._validate_command_result("Custom.echo", {"ok": True}), True) - self.assertEqual(client._validate_event_payload("Custom.ready", {"ok": True}), CustomReadyEvent(ok=True)) - with self.assertRaises(ValueError): - client._validate_command_params("Custom.echo", {"text": 1}) - with self.assertRaises(ValueError): - client._validate_command_result("Custom.echo", {"ok": "yes"}) - with self.assertRaises(ValueError): - client._validate_event_payload("Custom.ready", {"ok": "yes"}) - - client.Mod.addCustomCommand( - "Target.getTargets", - result_schema={ - "type": "object", - "properties": { - "targetInfos": { - "type": "array", - "items": { - "type": "object", - "properties": { - "targetId": {"type": "string"}, - "type": {"type": "string"}, - "title": {"type": "string"}, - "url": {"type": "string"}, - "attached": {"type": "boolean"}, - "canAccessOpener": {"type": "boolean"}, - "tabId": {"type": "integer"}, - }, - "required": ["targetId", "type", "title", "url", "attached", "canAccessOpener"], - "additionalProperties": True, - }, + + self.assertTrue(callable(client.Runtime.evaluate)) + self.assertEqual(types.parseCommandParams("Runtime.evaluate", runtime_params), runtime_params) + self.assertEqual(types.parseCommandResult("Runtime.evaluate", runtime_result), runtime_result) + self.assertEqual(types.parseEventPayload("Target.targetCreated", target_event), target_event) + with self.assertRaises(ValueError): + types.parseCommandParams("Runtime.evaluate", {"returnByValue": True}) + with self.assertRaises(ValueError): + types.parseCommandResult("Runtime.evaluate", {}) + with self.assertRaises(ValueError): + types.parseEventPayload("Target.targetCreated", {}) + + def test_mod_schemas_validate_method_params_return_values_event_payloads_and_middleware_registrations_statically_and_at_runtime(self) -> None: + types = CDPTypes() + client = ModCDPClient(launcher={"launcher_mode": "none"}, upstream={"upstream_mode": "ws"}, injector={"injector_mode": "none"}, server_config=None) + ping_params = {"sent_at": 123} + ping_result = {"ok": True} + pong_event = {"sent_at": 123, "received_at": 124, "from": "extension-service-worker"} + middleware_params = { + "name": client.Target.getTargets, + "phase": "response", + "expression": "async (payload, next) => next(payload)", + } + middleware_result = {"name": "Target.getTargets", "phase": "response", "registered": True} + + self.assertEqual(types.parseCommandParams("Mod.ping", ping_params), ping_params) + self.assertEqual(types.parseCommandResult("Mod.ping", ping_result), ping_result) + self.assertEqual(types.parseEventPayload("Mod.pong", pong_event), pong_event) + self.assertEqual( + types.parseCommandParams("Mod.addMiddleware", middleware_params), + { + "name": "Target.getTargets", + "phase": "response", + "expression": "async (payload, next) => next(payload)", + }, + ) + self.assertEqual(types.parseCommandResult("Mod.addMiddleware", middleware_result), middleware_result) + with self.assertRaises(ValueError): + types.parseCommandParams("Mod.ping", {"sent_at": "123"}) + with self.assertRaises(ValueError): + types.parseCommandResult("Mod.ping", {"ok": "true"}) + with self.assertRaises(ValueError): + types.parseEventPayload("Mod.pong", {"sent_at": 123, "from": "extension-service-worker"}) + with self.assertRaises(ValueError): + types.parseCommandParams("Mod.addMiddleware", {"name": "Custom.any", "phase": "after", "expression": "async (payload, next) => next(payload)"}) + with self.assertRaises(ValueError): + types.parseCommandResult("Mod.addMiddleware", {"name": "Custom.any", "phase": "after", "registered": True}) + + def test_constructor_custom_schemas_validate_command_params_return_values_events_and_middleware_registrations_statically_and_at_runtime(self) -> None: + client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + types={ + "custom_commands": { + "Custom.sum": { + "params_schema": SumParams, + "result_schema": SumResult, + "expression": "async ({ left, right }) => ({ value: left + right })", } }, - "required": ["targetInfos"], - "additionalProperties": True, + "custom_events": {"Custom.finished": {"event_schema": FinishedEvent}}, + "custom_middlewares": [{"name": "Custom.sum", "phase": "response", "expression": "async (payload, next) => next(payload)"}], }, ) - client.Mod.addCustomEvent("Target.targetCreated") - extended_target_info = {**native_target_info, "tabId": 7} - self.assertEqual(client._validate_command_result("Target.getTargets", {"targetInfos": [extended_target_info]}), {"targetInfos": [extended_target_info]}) + self.assertEqual(client.types.parseCommandParams("Custom.sum", {"left": 1, "right": 2}), {"left": 1, "right": 2}) + self.assertEqual(client.types.parseCommandResult("Custom.sum", {"value": 3}), {"value": 3}) + self.assertEqual(client.types.parseEventPayload("Custom.finished", {"total": 3, "label": "ok"}), {"total": 3, "label": "ok"}) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.sum", {"left": "1", "right": 2}) + with self.assertRaises(ValueError): + client.types.parseCommandResult("Custom.sum", {"value": "3"}) + with self.assertRaises(ValueError): + client.types.parseEventPayload("Custom.finished", {"total": "3", "label": "ok"}) self.assertEqual( - client._validate_event_payload("Target.targetCreated", {"targetInfo": extended_target_info}), - {"targetInfo": extended_target_info}, + client.types.customMiddlewareWireRegistrations(), + [{"name": "Custom.sum", "phase": "response", "expression": "async (payload, next) => next(payload)"}], ) + with self.assertRaises(Exception): + CDPTypes(custom_middlewares=[{"name": "Custom.sum", "phase": "after", "expression": "async (payload, next) => next(payload)"}]) + + def test_dynamic_mod_registration_updates_custom_command_event_and_middleware_validation(self) -> None: + client = ModCDPClient(launcher={"launcher_mode": "none"}, upstream={"upstream_mode": "ws"}, injector={"injector_mode": "none"}, server_config=None) + + self.assertEqual( + client.Mod.addCustomCommand("Custom.dynamic", params_schema=DynamicParams, result_schema=DynamicResult), + {"name": "Custom.dynamic", "registered": True}, + ) + self.assertEqual( + client.Mod.addCustomEvent( + "Custom.dynamicReady", + event_schema={ + "type": "object", + "properties": {"id": {"type": "string", "pattern": "^[0-9a-f-]{36}$"}}, + "required": ["id"], + "additionalProperties": False, + }, + ), + {"name": "Custom.dynamicReady", "registered": True}, + ) + self.assertEqual( + client.Mod.addMiddleware( + name="Custom.dynamic", + phase="response", + expression="async (payload, next) => next(payload)", + ), + {"name": "Custom.dynamic", "phase": "response", "registered": True}, + ) + + self.assertTrue(callable(client.Custom.dynamic)) + self.assertEqual(client.types.parseCommandParams("Custom.dynamic", {"text": "ok"}), {"text": "ok"}) + self.assertEqual(client.types.parseCommandResult("Custom.dynamic", {"ok": True}), {"ok": True}) + self.assertEqual( + client.types.parseEventPayload("Custom.dynamicReady", {"id": "550e8400-e29b-41d4-a716-446655440000"}), + {"id": "550e8400-e29b-41d4-a716-446655440000"}, + ) + self.assertEqual( + client.types.customMiddlewareWireRegistrations(), + [{"name": "Custom.dynamic", "phase": "response", "expression": "async (payload, next) => next(payload)"}], + ) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.dynamic", {"text": ""}) + with self.assertRaises(ValueError): + client.types.parseCommandResult("Custom.dynamic", {"ok": "yes"}) + with self.assertRaises(ValueError): + client.types.parseEventPayload("Custom.dynamicReady", {"id": "nope"}) + with self.assertRaises(ValueError): + client.Mod.addMiddleware(name="Custom.dynamic", phase="after", expression="async (payload, next) => next(payload)") + + def test_client_types_update_replaces_the_registry_with_extended_runtime_validation_and_preserves_static_custom_aliases_on_typed_clients(self) -> None: + client = ModCDPClient(launcher={"launcher_mode": "none"}, upstream={"upstream_mode": "ws"}, injector={"injector_mode": "none"}, server_config=None) + updated_types = client.types.update( + { + "custom_commands": { + "Custom.updated": { + "params_schema": UpdatedParams, + "result_schema": UpdatedResult, + } + }, + "custom_events": {"Custom.updatedReady": {"event_schema": UpdatedReadyEvent}}, + "custom_middlewares": [{"name": "Custom.updated", "phase": "request", "expression": "async (payload, next) => next(payload)"}], + } + ) + typed_client = ModCDPClient( + launcher={"launcher_mode": "none"}, + upstream={"upstream_mode": "ws"}, + injector={"injector_mode": "none"}, + server_config=None, + types=updated_types, + ) + client.types = updated_types + + self.assertTrue(callable(typed_client.Custom.updated)) + self.assertTrue(callable(client.Custom.updated)) + self.assertEqual(client.types.parseCommandParams("Custom.updated", {"count": 1}), {"count": 1}) + self.assertEqual(client.types.parseCommandResult("Custom.updated", {"done": True}), {"done": True}) + self.assertEqual(client.types.parseEventPayload("Custom.updatedReady", {"ready": True}), {"ready": True}) + self.assertEqual( + client.types.customMiddlewareWireRegistrations(), + [{"name": "Custom.updated", "phase": "request", "expression": "async (payload, next) => next(payload)"}], + ) + with self.assertRaises(ValueError): + client.types.parseCommandParams("Custom.updated", {"count": 0}) + with self.assertRaises(ValueError): + client.types.parseCommandResult("Custom.updated", {"done": "true"}) + with self.assertRaises(ValueError): + client.types.parseEventPayload("Custom.updatedReady", {"ready": "true"}) if __name__ == "__main__": diff --git a/python/tests/test_ModCDPPayloadSchemaNormalization.py b/python/tests/test_ModCDPPayloadSchemaNormalization.py deleted file mode 100644 index 29576ab2..00000000 --- a/python/tests/test_ModCDPPayloadSchemaNormalization.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -import unittest - -from pydantic_core import to_jsonable_python - -from modcdp import ModCDPClient - - -class ModCDPPayloadSchemaNormalizationTests(unittest.TestCase): - def test_payload_schema_normalization_accepts_empty_json_schema_objects(self) -> None: - adapter, schema, _ = ModCDPClient()._adapter_from_optional_schema({}, "params_schema") - - assert adapter is not None - self.assertEqual(schema, {}) - self.assertEqual(adapter.validate_python({"value": 1}), {"value": 1}) - - def test_payload_schema_normalization_rejects_unsupported_schema_specs(self) -> None: - with self.assertRaises(TypeError): - ModCDPClient()._adapter_from_optional_schema("not-a-schema", "params_schema") - - def test_payload_schema_normalization_accepts_non_empty_json_schema_objects(self) -> None: - adapter, schema, _ = ModCDPClient()._adapter_from_optional_schema( - { - "type": "object", - "properties": {"value": {"type": "string"}}, - "required": ["value"], - }, - "params_schema", - ) - - assert adapter is not None - assert schema is not None - self.assertEqual(schema["type"], "object") - self.assertEqual(to_jsonable_python(adapter.validate_python({"value": "ok", "extra": True})), {"value": "ok", "extra": True}) - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_NativeMessagingUpstreamTransport.py b/python/tests/test_NativeMessagingUpstreamTransport.py deleted file mode 100644 index f11bdc91..00000000 --- a/python/tests/test_NativeMessagingUpstreamTransport.py +++ /dev/null @@ -1,268 +0,0 @@ -from __future__ import annotations - -import threading -import time -import unittest -import glob -import os -import re -import socket -import sys -import tempfile -from pathlib import Path -from queue import Queue - -from modcdp import ModCDPClient -from modcdp.transport.NativeMessagingUpstreamTransport import NativeMessagingUpstreamTransport - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" -NATIVE_MESSAGING_TEST_BROWSER_PATH: str | None = None - - -class NativeMessagingUpstreamTransportTests(unittest.TestCase): - def test_config_owns_manifest_host_wait_timeout_loopback_and_injector_config(self) -> None: - transport = NativeMessagingUpstreamTransport( - { - "upstream_nativemessaging_manifest": "/tmp/modcdp-native-host.json", - "upstream_nativemessaging_manifests": ["/tmp/modcdp-native-host-extra.json"], - "upstream_nativemessaging_host_name": "com.modcdp.test", - "injector_extension_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "upstream_nativemessaging_wait_timeout_ms": 10, - } - ) - self.assertEqual(transport.getInjectorConfig(), {"upstream_nativemessaging_host_name": "com.modcdp.test"}) - self.assertEqual(transport.getServerConfig(), {}) - self.assertIs( - transport.update( - { - "cdp_url": "ws://127.0.0.1:9222/devtools/browser/test", - "upstream_nativemessaging_manifests": [], - "upstream_nativemessaging_host_name": "com.modcdp.updated", - "upstream_nativemessaging_wait_timeout_ms": 5, - } - ), - transport, - ) - self.assertEqual( - transport.getServerConfig(), - {"server_loopback_cdp_url": "ws://127.0.0.1:9222/devtools/browser/test"}, - ) - self.assertEqual(transport.getInjectorConfig(), {"upstream_nativemessaging_host_name": "com.modcdp.updated"}) - self.assertFalse(transport.include_default_manifest_paths) - transport.update({"upstream_nativemessaging_manifest": None}) - self.assertTrue(transport.include_default_manifest_paths) - transport.update({"user_data_dir": "/tmp/modcdp-profile-one"}) - transport.update({"user_data_dir": "/tmp/modcdp-profile-one"}) - transport.update({"user_data_dir": "/tmp/modcdp-profile-two"}) - self.assertEqual( - transport.upstream_nativemessaging_manifests, - [ - str(Path("/tmp/modcdp-profile-two") / "NativeMessagingHosts" / "com.modcdp.updated.json"), - str(Path("/tmp/modcdp-profile-two") / "Default" / "NativeMessagingHosts" / "com.modcdp.updated.json"), - ], - ) - with self.assertRaisesRegex(RuntimeError, r"Timed out waiting 5ms for native messaging host com\.modcdp\.updated"): - transport.waitForPeer() - - def test_close_resets_peer_wait_state(self) -> None: - upstream_nativemessaging_host_name = f"com.modcdp.close.reset.python.{os.getpid()}" - transport = NativeMessagingUpstreamTransport({"upstream_nativemessaging_host_name": upstream_nativemessaging_host_name, "upstream_nativemessaging_wait_timeout_ms": 5}) - transport.connect() - bound_port = transport.bound_port - if bound_port is None: - self.fail("native messaging transport did not bind a port") - peer = socket.create_connection(("127.0.0.1", bound_port), timeout=10) - - try: - transport.waitForPeer() - transport.close() - native_host_name_pattern = upstream_nativemessaging_host_name.replace(".", r"\.") - with self.assertRaisesRegex( - RuntimeError, - rf"Timed out waiting 5ms for native messaging host {native_host_name_pattern}", - ): - transport.waitForPeer() - finally: - peer.close() - transport.close() - - def test_waits_again_after_peer_disconnects(self) -> None: - upstream_nativemessaging_host_name = f"com.modcdp.disconnect.reset.python.{os.getpid()}" - transport = NativeMessagingUpstreamTransport({"upstream_nativemessaging_host_name": upstream_nativemessaging_host_name, "upstream_nativemessaging_wait_timeout_ms": 5}) - transport.connect() - bound_port = transport.bound_port - if bound_port is None: - self.fail("native messaging transport did not bind a port") - peer = socket.create_connection(("127.0.0.1", bound_port), timeout=10) - - try: - transport.waitForPeer() - peer.close() - _wait_until(lambda: transport.socket is None) - native_host_name_pattern = upstream_nativemessaging_host_name.replace(".", r"\.") - with self.assertRaisesRegex( - RuntimeError, - rf"Timed out waiting 5ms for native messaging host {native_host_name_pattern}", - ): - transport.waitForPeer() - finally: - peer.close() - transport.close() - - def test_accepts_replacement_peer_after_disconnect(self) -> None: - upstream_nativemessaging_host_name = f"com.modcdp.replacement.python.{os.getpid()}" - transport = NativeMessagingUpstreamTransport({"upstream_nativemessaging_host_name": upstream_nativemessaging_host_name, "upstream_nativemessaging_wait_timeout_ms": 500}) - transport.connect() - bound_port = transport.bound_port - if bound_port is None: - self.fail("native messaging transport did not bind a port") - first_peer = socket.create_connection(("127.0.0.1", bound_port), timeout=10) - - try: - transport.waitForPeer() - first_peer.close() - _wait_until(lambda: transport.socket is None) - - second_peer = socket.create_connection(("127.0.0.1", bound_port), timeout=10) - try: - transport.waitForPeer() - finally: - second_peer.close() - finally: - first_peer.close() - transport.close() - - def test_close_rejects_pending_peer_waits(self) -> None: - transport = NativeMessagingUpstreamTransport( - { - "upstream_nativemessaging_host_name": "com.modcdp.close", - "upstream_nativemessaging_wait_timeout_ms": 5_000, - } - ) - result: Queue[BaseException | None] = Queue() - - def wait_for_peer() -> None: - try: - transport.waitForPeer() - except BaseException as error: - result.put(error) - return - result.put(None) - - thread = threading.Thread(target=wait_for_peer, daemon=True) - thread.start() - time.sleep(0.05) - transport.close() - thread.join(timeout=1) - - error = result.get(timeout=1) - self.assertIsInstance(error, RuntimeError) - self.assertRegex( - str(error), - r"Native messaging transport for com\.modcdp\.close closed before a peer connected", - ) - - def test_installs_launch_profile_native_host_manifest_and_connects_to_real_extension(self) -> None: - upstream_nativemessaging_host_name = "com.modcdp.bridge" - temp_profile_dir = tempfile.TemporaryDirectory(prefix="modcdp.native.") - cdp = ModCDPClient( - launcher={ - "launcher_mode": "local", - "launcher_options": { - "headless": True, - "user_data_dir": temp_profile_dir.name, - "cleanup_user_data_dir": True, - # Native messaging is browser -> client only. After explicit CHROME_PATH - # and CI /usr/bin/chromium, this test uses Chrome for Testing because - # Canary rejects --load-extension in this local test path. - "executable_path": extension_launch_flag_test_browser_path(), - }, - }, - upstream={"upstream_mode": "nativemessaging", "upstream_nativemessaging_host_name": upstream_nativemessaging_host_name}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - server={"server_routes": {"*.*": "loopback_cdp"}}, - ) - - try: - cdp.connect() - self.assertEqual(cdp.transport.mode if cdp.transport else None, "nativemessaging") - self.assertEqual(cdp.upstream_endpoint_kind, "modcdp_server") - transport_url = cdp.transport.url if cdp.transport and cdp.transport.url else "" - self.assertRegex(transport_url, rf"^native://{upstream_nativemessaging_host_name}@127\.0\.0\.1:\d+$") - launched_profile_dir = cdp._launched_browser.get("profile_dir") if cdp._launched_browser else "" - self.assertTrue((Path(launched_profile_dir) / "NativeMessagingHosts" / f"{upstream_nativemessaging_host_name}.json").exists()) - version = cdp.send("Browser.getVersion") - self.assertIsInstance(version["product"], str) - time.sleep(1.5) - second_version = cdp.send("Browser.getVersion") - self.assertIsInstance(second_version["product"], str) - finally: - cdp.close() - temp_profile_dir.cleanup() - - -def _wait_until(predicate, timeout_s: float = 2.0) -> None: - deadline = time.monotonic() + timeout_s - while time.monotonic() < deadline: - if predicate(): - return - time.sleep(0.02) - raise AssertionError("timed out waiting for condition") - - -def extension_launch_flag_test_browser_path() -> str: - global NATIVE_MESSAGING_TEST_BROWSER_PATH - if NATIVE_MESSAGING_TEST_BROWSER_PATH is not None: - return NATIVE_MESSAGING_TEST_BROWSER_PATH - explicit_candidates = [ - os.environ.get("CHROME_PATH"), - "/usr/bin/chromium" if sys.platform.startswith("linux") else None, - ] - for candidate in explicit_candidates: - if candidate and Path(candidate).exists(): - NATIVE_MESSAGING_TEST_BROWSER_PATH = candidate - return candidate - - home = Path.home() - if sys.platform == "darwin": - patterns = [ - str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), - str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), - str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), - ] - elif sys.platform.startswith("win"): - local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") - patterns = [ - str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), - str(home / ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), - ] - else: - patterns = [ - str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), - "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", - str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), - ] - candidates = sorted( - {candidate for pattern in patterns for candidate in glob.glob(pattern)}, - key=lambda candidate: (_path_version(candidate), Path(candidate).stat().st_mtime), - reverse=True, - ) - if candidates: - NATIVE_MESSAGING_TEST_BROWSER_PATH = candidates[0] - return candidates[0] - raise RuntimeError("Native messaging tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing.") - - -def _path_version(candidate: str) -> int: - numbers = [int(value) for value in re.findall(r"\d+", candidate)] - return max(numbers) if numbers else 0 - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_NatsUpstreamTransport.py b/python/tests/test_NatsUpstreamTransport.py deleted file mode 100644 index 99a67642..00000000 --- a/python/tests/test_NatsUpstreamTransport.py +++ /dev/null @@ -1,183 +0,0 @@ -from __future__ import annotations - -import socket -import subprocess -import tempfile -import threading -import time -import unittest -from pathlib import Path -from queue import Queue - -from websocket import create_connection - -from modcdp.transport.NatsUpstreamTransport import NatsUpstreamTransport - - -ROOT = Path(__file__).resolve().parents[2] - - -class NatsUpstreamTransportTests(unittest.TestCase): - def test_config_owns_url_nats_subject_prefix_wait_timeout_and_injector_config(self) -> None: - transport = NatsUpstreamTransport({"upstream_nats_url": "ws://127.0.0.1:4223", "upstream_nats_subject_prefix": "modcdp.one"}) - self.assertEqual(transport.url, "ws://127.0.0.1:4223/") - self.assertEqual(transport.upstream_nats_subject_prefix, "modcdp.one") - self.assertEqual( - transport.getInjectorConfig(), - {"upstream_nats_url": "ws://127.0.0.1:4223/", "upstream_nats_subject_prefix": "modcdp.one"}, - ) - self.assertIs( - transport.update( - { - "upstream_nats_url": "nats://127.0.0.1:4222", - "upstream_nats_subject_prefix": "modcdp.two", - "upstream_nats_role": "browser", - "upstream_nats_wait_timeout_ms": 5, - } - ), - transport, - ) - self.assertEqual(transport.url, "nats://127.0.0.1:4222") - self.assertEqual(transport.upstream_nats_subject_prefix, "modcdp.two") - with self.assertRaisesRegex(RuntimeError, "Timed out waiting 5ms for NATS ModCDP peer"): - transport.waitForPeer() - - def test_close_rejects_pending_peer_waits(self) -> None: - transport = NatsUpstreamTransport( - { - "upstream_nats_url": "ws://127.0.0.1:4223", - "upstream_nats_subject_prefix": "modcdp.close", - "upstream_nats_wait_timeout_ms": 5_000, - } - ) - result: Queue[BaseException | None] = Queue() - - def wait_for_peer() -> None: - try: - transport.waitForPeer() - except BaseException as error: - result.put(error) - return - result.put(None) - - thread = threading.Thread(target=wait_for_peer, daemon=True) - thread.start() - time.sleep(0.05) - transport.close() - thread.join(timeout=1) - - error = result.get(timeout=1) - self.assertIsInstance(error, RuntimeError) - self.assertRegex(str(error), r"NATS transport for modcdp\.close closed before a peer connected") - - def test_close_resets_peer_wait_state(self) -> None: - transport = NatsUpstreamTransport({"upstream_nats_wait_timeout_ms": 5}) - transport._handle_payload('{"type":"modcdp.nats.hello","role":"browser","version":1}') - - transport.waitForPeer() - transport.close() - - with self.assertRaisesRegex(RuntimeError, "Timed out waiting 5ms for NATS ModCDP peer"): - transport.waitForPeer() - self.assertTrue(transport.closed) - - def test_reconnect_after_close_resets_closed_state_with_real_nats_server(self) -> None: - nats = _start_nats_server() - transport = NatsUpstreamTransport({"upstream_nats_url": nats["url"], "upstream_nats_subject_prefix": f"modcdp.reconnect.{int(time.time() * 1000)}"}) - - try: - transport.connect() - self.assertTrue(transport.connected) - transport.close() - self.assertFalse(transport.connected) - self.assertTrue(transport.closed) - transport.connect() - self.assertTrue(transport.connected) - self.assertFalse(transport.closed) - finally: - transport.close() - nats["close"]() - -def _start_nats_server(): - websocket_port = _free_port() - client_port = _free_port() - temp_dir = tempfile.TemporaryDirectory(prefix="modcdp-nats-") - config_path = Path(temp_dir.name) / "nats.conf" - config_path.write_text( - "\n".join( - [ - 'host: "127.0.0.1"', - f"port: {client_port}", - "websocket {", - ' host: "127.0.0.1"', - f" port: {websocket_port}", - " no_tls: true", - "}", - "", - ] - ) - ) - binary_path = subprocess.check_output( - [ - "pnpm", - "exec", - "node", - "--input-type=module", - "-e", - "import { getBinaryPath } from '@eplightning/nats-server'; console.log(await getBinaryPath())", - ], - cwd=ROOT, - text=True, - ).strip() - proc = subprocess.Popen([binary_path, "-c", str(config_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - url = f"ws://127.0.0.1:{websocket_port}" - try: - _wait_for_websocket(url) - except Exception: - _close_process(proc) - temp_dir.cleanup() - raise - - def close() -> None: - _close_process(proc) - temp_dir.cleanup() - - return {"url": url, "close": close} - - -def _wait_for_websocket(url: str, timeout_s: float = 10) -> None: - deadline = time.time() + timeout_s - last_error: Exception | None = None - while time.time() < deadline: - try: - ws = create_connection(url, timeout=1) - ws.close() - return - except Exception as error: - last_error = error - time.sleep(0.05) - raise last_error or TimeoutError(f"Timed out waiting for {url}") - - -def _close_process(proc: subprocess.Popen) -> None: - if proc.poll() is not None: - return - proc.terminate() - try: - proc.wait(timeout=2) - except subprocess.TimeoutExpired: - proc.kill() - proc.wait(timeout=2) - - -def _free_port() -> int: - sock = socket.socket() - sock.bind(("127.0.0.1", 0)) - try: - return int(sock.getsockname()[1]) - finally: - sock.close() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_NoneBrowserLauncher.py b/python/tests/test_NoneBrowserLauncher.py new file mode 100644 index 00000000..62668647 --- /dev/null +++ b/python/tests/test_NoneBrowserLauncher.py @@ -0,0 +1,26 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.NoneBrowserLauncher.ts +# - ./go/modcdp/launcher/NoneBrowserLauncher_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import unittest + +from modcdp.launcher.NoneBrowserLauncher import NoneBrowserLauncher + + +class NoneBrowserLauncherTests(unittest.TestCase): + def test_nonebrowserlauncher_records_an_empty_launched_browser(self) -> None: + launcher = NoneBrowserLauncher() + + launched = launcher.launch() + + self.assertIsNone(launched.cdp_url) + self.assertIs(launcher.launched, launched) + launched.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_NoopBrowserLauncher.py b/python/tests/test_NoopBrowserLauncher.py deleted file mode 100644 index a0fb556a..00000000 --- a/python/tests/test_NoopBrowserLauncher.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -import unittest - -from modcdp.launcher.NoopBrowserLauncher import NoopBrowserLauncher - - -class NoopBrowserLauncherTests(unittest.TestCase): - def test_constructor_launch_and_config_match_ts_shape(self) -> None: - launcher = NoopBrowserLauncher({"cdp_url": "ws://127.0.0.1:9222/devtools/browser/initial"}) - self.assertEqual(launcher.options.get("cdp_url"), "ws://127.0.0.1:9222/devtools/browser/initial") - self.assertEqual( - launcher.getTransportConfig().get("cdp_url"), - "ws://127.0.0.1:9222/devtools/browser/initial", - ) - - launched = launcher.launch({"cdp_url": "ws://127.0.0.1:9222/devtools/browser/call"}) - self.assertIs(launcher.launched, launched) - self.assertIsNone(launched["cdp_url"]) - self.assertEqual(launcher.getServerConfig(), {}) - launched["close"]() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_PipeUpstreamTransport.py b/python/tests/test_PipeUpstreamTransport.py deleted file mode 100644 index 62219841..00000000 --- a/python/tests/test_PipeUpstreamTransport.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -import os -import unittest -from pathlib import Path - -from modcdp import ModCDPClient -from modcdp.transport.PipeUpstreamTransport import PipeUpstreamTransport - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" - - -class PipeUpstreamTransportTests(unittest.TestCase): - def test_constructor_update_launcher_config_and_unconnected_errors_match_transport_surface(self) -> None: - transport = PipeUpstreamTransport({"cdp_url": "pipe://constructor"}) - self.assertEqual(transport.mode, "pipe") - self.assertEqual(transport.endpoint_kind, "raw_cdp") - self.assertEqual(transport.url, "pipe://constructor") - self.assertEqual(transport.getLauncherConfig(), {"remote_debugging": "pipe"}) - self.assertIs(transport.update({"cdp_url": "pipe://1234"}), transport) - self.assertEqual(transport.url, "pipe://1234") - with self.assertRaisesRegex(RuntimeError, r"upstream\.upstream_mode=pipe requires"): - transport.connect() - with self.assertRaisesRegex(RuntimeError, "CDP pipe is not connected"): - transport.send({"id": 1, "method": "Runtime.evaluate"}) - - def test_resets_connection_state_after_pipe_closes(self) -> None: - read_fd, read_writer_fd = os.pipe() - write_reader_fd, write_fd = os.pipe() - pipe_read = os.fdopen(read_fd, "rb", buffering=0) - pipe_read_writer = os.fdopen(read_writer_fd, "wb", buffering=0) - pipe_write_reader = os.fdopen(write_reader_fd, "rb", buffering=0) - pipe_write = os.fdopen(write_fd, "wb", buffering=0) - transport = PipeUpstreamTransport({"pipe_read": pipe_read, "pipe_write": pipe_write, "cdp_url": "pipe://test"}) - closed: list[Exception] = [] - transport.onClose(lambda error: closed.append(error)) - - try: - transport.connect() - transport.send({"id": 1, "method": "Runtime.evaluate", "params": {"expression": "1"}}) - pipe_read_writer.close() - self.assertTrue(_wait_for(lambda: len(closed) == 1)) - with self.assertRaisesRegex(RuntimeError, "CDP pipe is not connected"): - transport.send({"id": 2, "method": "Runtime.evaluate", "params": {"expression": "1"}}) - finally: - transport.close() - pipe_write_reader.close() - - def test_launches_real_browser_and_uses_pid_scoped_pipe_url(self) -> None: - cdp = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "pipe"}, - injector={ - "injector_mode": "inject", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - server={"server_routes": {"*.*": "chrome_debugger"}}, - ) - - try: - cdp.connect() - self.assertEqual(cdp.transport.mode if cdp.transport else None, "pipe") - self.assertEqual(cdp.upstream_endpoint_kind, "raw_cdp") - self.assertRegex(cdp.cdp_url or "", r"^pipe://\d+$") - self.assertEqual(cdp.transport.url if cdp.transport else None, cdp.cdp_url) - cdp.Mod.addCustomCommand( - "Custom.runtimeReadyState", - expression="async () => await cdp.send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true })", - ) - runtime = cdp.send("Custom.runtimeReadyState") - self.assertEqual(runtime["result"]["value"], "complete") - finally: - cdp.close() - -def _wait_for(fn, timeout_s: float = 2) -> bool: - import time - - deadline = time.monotonic() + timeout_s - while time.monotonic() < deadline: - if fn(): - return True - time.sleep(0.02) - return False - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_RemoteBrowserLauncher.py b/python/tests/test_RemoteBrowserLauncher.py index e4d60a58..5a80db01 100644 --- a/python/tests/test_RemoteBrowserLauncher.py +++ b/python/tests/test_RemoteBrowserLauncher.py @@ -1,68 +1,86 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.RemoteBrowserLauncher.ts +# - ./go/modcdp/launcher/RemoteBrowserLauncher_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import json import unittest -from websocket import create_connection - from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher from modcdp.launcher.RemoteBrowserLauncher import RemoteBrowserLauncher +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport class RemoteBrowserLauncherTests(unittest.TestCase): - def test_requires_upstream_cdp_url(self) -> None: - with self.assertRaisesRegex(RuntimeError, "launcher.launcher_mode=remote requires upstream.upstream_cdp_url"): + def test_requires_launcher_remote_cdp_url(self) -> None: + with self.assertRaisesRegex(RuntimeError, "launcher_mode=remote requires launcher_remote_cdp_url"): RemoteBrowserLauncher().launch() - def test_connects_to_real_browser_from_http_and_websocket_cdp_endpoints(self) -> None: + def test_connects_to_a_real_browser_from_both_http_discovery_and_websocket_cdp_endpoints(self) -> None: port = LocalBrowserLauncher.freePort() local = LocalBrowserLauncher().launch( - {"port": port, "headless": True, "chrome_ready_timeout_ms": 45_000} + {"launcher_local_cdp_listen_port": port, "launcher_local_headless": True, "launcher_local_chrome_ready_timeout_ms": 45_000} ) - ws = None + transport = None try: - from_http = RemoteBrowserLauncher(cdp_url=f"http://127.0.0.1:{port}").launch() - self.assertEqual(from_http["cdp_url"], local["cdp_url"]) - from_http_cdp_url = from_http.get("cdp_url") + from_http = RemoteBrowserLauncher({"launcher_remote_cdp_url": f"http://127.0.0.1:{port}"}).launch() + self.assertEqual(from_http.cdp_url, local.cdp_url) + from_http_cdp_url = from_http.cdp_url if not isinstance(from_http_cdp_url, str): self.fail(f"cdp_url = {from_http_cdp_url!r}") - ws = create_connection(from_http_cdp_url, timeout=10) - _expect_cdp_browser_surface(ws) - from_http["close"]() - - from_host_port = RemoteBrowserLauncher(cdp_url=f"127.0.0.1:{port}").launch() - self.assertEqual(from_host_port["cdp_url"], local["cdp_url"]) - from_host_port["close"]() - - from_options = RemoteBrowserLauncher({"cdp_url": local["cdp_url"]}).launch() - self.assertEqual(from_options["cdp_url"], local["cdp_url"]) - from_options["close"]() - - from_override = RemoteBrowserLauncher({"cdp_url": "http://127.0.0.1:1"}).launch({"cdp_url": f"127.0.0.1:{port}"}) - self.assertEqual(from_override["cdp_url"], local["cdp_url"]) - from_override["close"]() - - from_ws = RemoteBrowserLauncher().launch({"cdp_url": local["cdp_url"]}) - self.assertEqual(from_ws["cdp_url"], local["cdp_url"]) - _expect_cdp_browser_surface(ws) - from_ws["close"]() + transport = WSUpstreamTransport({"upstream_ws_cdp_url": from_http_cdp_url}) + transport.connect() + expect_cdp_browser_surface(transport) + from_http.close() + + from_host_port = RemoteBrowserLauncher({"launcher_remote_cdp_url": f"127.0.0.1:{port}"}).launch() + self.assertEqual(from_host_port.cdp_url, local.cdp_url) + from_host_port.close() + + from_config = RemoteBrowserLauncher({"launcher_remote_cdp_url": local.cdp_url}).launch() + self.assertEqual(from_config.cdp_url, local.cdp_url) + from_config.close() + + from_ws = RemoteBrowserLauncher().launch({"launcher_remote_cdp_url": local.cdp_url}) + self.assertEqual(from_ws.cdp_url, local.cdp_url) + expect_cdp_browser_surface(transport) + from_ws.close() finally: - if ws is not None: - ws.close() - local["close"]() + if transport is not None: + transport.close() + local.close() - def test_accepts_wss_cdp_endpoint_without_http_discovery(self) -> None: - launched = RemoteBrowserLauncher(cdp_url="wss://example.test/devtools/browser/test").launch() + def test_lets_launch_config_override_constructor_cdp_url(self) -> None: + first = LocalBrowserLauncher().launch( + {"launcher_local_cdp_listen_port": LocalBrowserLauncher.freePort(), "launcher_local_headless": True} + ) + second = LocalBrowserLauncher().launch( + {"launcher_local_cdp_listen_port": LocalBrowserLauncher.freePort(), "launcher_local_headless": True} + ) - self.assertEqual(launched["cdp_url"], "wss://example.test/devtools/browser/test") - launched["close"]() + try: + second_cdp_listen_port = second.cdp_listen_port + if not isinstance(second_cdp_listen_port, int): + raise AssertionError(f"second cdp_listen_port = {second_cdp_listen_port!r}") + launched = RemoteBrowserLauncher({"launcher_remote_cdp_url": first.cdp_url}).launch( + {"launcher_remote_cdp_url": f"127.0.0.1:{second_cdp_listen_port}"} + ) + self.assertEqual(launched.cdp_url, second.cdp_url) + launched.close() + finally: + first.close() + second.close() -def _expect_cdp_browser_surface(ws) -> None: - ws.send(json.dumps({"id": 1, "method": "Browser.getVersion", "params": {}})) - message = json.loads(ws.recv()) - if not isinstance(message.get("result", {}).get("product"), str): - raise AssertionError(f"Browser.getVersion result = {message!r}") +# MODCDP_TEST_SUPPORT: LANGUAGE-SPECIFIC TEST SUPPORT ONLY. +# Keep the setup semantics above 1:1 with translated tests; helpers here only send real CDP messages to real browser endpoints. +def expect_cdp_browser_surface(transport: WSUpstreamTransport) -> None: + result = transport.send("Browser.getVersion") + product = result.get("product") + if not isinstance(product, str) or ("Chrome" not in product and "Chromium" not in product): + raise AssertionError(f"Browser.getVersion result = {result!r}") if __name__ == "__main__": diff --git a/python/tests/test_ReverseWebSocketUpstreamTransport.py b/python/tests/test_ReverseWebSocketUpstreamTransport.py deleted file mode 100644 index 734ad29e..00000000 --- a/python/tests/test_ReverseWebSocketUpstreamTransport.py +++ /dev/null @@ -1,257 +0,0 @@ -from __future__ import annotations - -import json -import glob -import os -import re -import socket -import sys -import threading -import time -import unittest -from pathlib import Path -from queue import Queue -from typing import cast - -from websocket import create_connection - -from modcdp import ModCDPClient -from modcdp.transport.ReverseWebSocketUpstreamTransport import ReverseWebSocketUpstreamTransport - -ROOT = Path(__file__).resolve().parents[2] -EXTENSION_PATH = ROOT / "dist" / "extension" -REVERSEWS_TEST_BROWSER_PATH = None - - -class ReverseWebSocketUpstreamTransportTests(unittest.TestCase): - def test_config_owns_bind_updates_and_wait_timeout(self) -> None: - transport = ReverseWebSocketUpstreamTransport({ - "upstream_reversews_bind": "127.0.0.1:29292", - "upstream_reversews_wait_timeout_ms": 10, - }) - self.assertEqual(transport.url, "ws://127.0.0.1:29292") - self.assertEqual(transport.getInjectorConfig(), {}) - self.assertIs( - transport.update({ - "upstream_reversews_bind": "127.0.0.1:29293", - "upstream_reversews_wait_timeout_ms": 5, - }), - transport, - ) - self.assertEqual(transport.url, "ws://127.0.0.1:29293") - self.assertEqual(transport.getInjectorConfig(), {}) - with self.assertRaisesRegex(RuntimeError, "Timed out waiting 5ms"): - transport.waitForPeer() - - def test_close_rejects_pending_peer_waits(self) -> None: - reverse_port = _free_port() - transport = ReverseWebSocketUpstreamTransport({"upstream_reversews_bind": f"127.0.0.1:{reverse_port}", "upstream_reversews_wait_timeout_ms": 5_000}) - result: Queue[BaseException | None] = Queue() - - def wait_for_peer() -> None: - try: - transport.waitForPeer() - except BaseException as error: - result.put(error) - return - result.put(None) - - thread = threading.Thread(target=wait_for_peer, daemon=True) - thread.start() - time.sleep(0.05) - transport.close() - thread.join(timeout=1) - - error = result.get(timeout=1) - self.assertIsInstance(error, RuntimeError) - self.assertRegex( - str(error), - rf"Reverse websocket transport at ws://127\.0\.0\.1:{reverse_port} closed before a peer connected", - ) - - def test_close_resets_peer_wait_state(self) -> None: - reverse_port = _free_port() - transport = ReverseWebSocketUpstreamTransport({"upstream_reversews_bind": f"127.0.0.1:{reverse_port}", "upstream_reversews_wait_timeout_ms": 5}) - transport.connect() - url = transport.url - if url is None: - self.fail("reverse transport url was not set") - peer = create_connection(url, timeout=10) - peer.send(json.dumps({"type": "modcdp.reverse.hello", "role": "test-peer", "version": 1})) - - try: - transport.waitForPeer() - self.assertEqual( - transport.peer_info, - {"type": "modcdp.reverse.hello", "role": "test-peer", "version": 1}, - ) - transport.close() - - with self.assertRaisesRegex(RuntimeError, "Timed out waiting 5ms"): - transport.waitForPeer() - self.assertIsNone(transport.peer_info) - finally: - peer.close() - transport.close() - - def test_waits_again_after_peer_disconnects(self) -> None: - reverse_port = _free_port() - transport = ReverseWebSocketUpstreamTransport({"upstream_reversews_bind": f"127.0.0.1:{reverse_port}", "upstream_reversews_wait_timeout_ms": 5}) - transport.connect() - url = transport.url - if url is None: - self.fail("reverse transport url was not set") - peer = create_connection(url, timeout=10) - peer.send(json.dumps({"type": "modcdp.reverse.hello", "role": "test-peer", "version": 1})) - - try: - transport.waitForPeer() - peer.close() - _wait_until(lambda: transport.socket is None) - - with self.assertRaisesRegex(RuntimeError, "Timed out waiting 5ms"): - transport.waitForPeer() - finally: - peer.close() - transport.close() - - def test_accepts_replacement_peer_after_disconnect(self) -> None: - reverse_port = _free_port() - transport = ReverseWebSocketUpstreamTransport({"upstream_reversews_bind": f"127.0.0.1:{reverse_port}", "upstream_reversews_wait_timeout_ms": 500}) - transport.connect() - url = transport.url - if url is None: - self.fail("reverse transport url was not set") - first_peer = create_connection(url, timeout=10) - first_peer.send(json.dumps({"type": "modcdp.reverse.hello", "role": "first-peer", "version": 1})) - - try: - transport.waitForPeer() - first_peer.close() - _wait_until(lambda: transport.socket is None) - - second_peer = create_connection(url, timeout=10) - second_peer.send(json.dumps({"type": "modcdp.reverse.hello", "role": "second-peer", "version": 1})) - try: - transport.waitForPeer() - self.assertEqual((transport.peer_info or {}).get("role"), "second-peer") - finally: - second_peer.close() - finally: - first_peer.close() - transport.close() - - def test_accepts_real_extension_reverse_connection_and_routes_cdp_through_chrome_debugger(self) -> None: - cdp = ModCDPClient( - launcher={ - "launcher_mode": "local", - "launcher_options": { - "headless": sys.platform.startswith("linux") and not os.environ.get("DISPLAY"), - # Reversews is browser -> client only. After explicit CHROME_PATH and - # CI /usr/bin/chromium, these tests use Chrome for Testing because - # Canary rejects --load-extension in this local test path. - "executable_path": reversews_test_browser_path(), - }, - }, - upstream={"upstream_mode": "reversews"}, - injector={ - "injector_mode": "auto", - "injector_extension_path": str(EXTENSION_PATH), - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - "injector_service_worker_probe_timeout_ms": 1_000, - }, - ) - - try: - cdp.connect() - self.assertEqual(cdp.transport.mode if cdp.transport else None, "reversews") - self.assertEqual(cdp.upstream_endpoint_kind, "modcdp_server") - self.assertIsInstance(cdp.transport, ReverseWebSocketUpstreamTransport) - transport = cast(ReverseWebSocketUpstreamTransport, cdp.transport) - self.assertEqual(transport.url, "ws://127.0.0.1:29292") - self.assertEqual( - transport.peer_info.get("extension_id") if transport.peer_info else None, - "mdedooklbnfejodmnhmkdpkaedafkehf", - ) - evaluated = cdp.send("Runtime.evaluate", {"expression": "location.href", "returnByValue": True}) - self.assertEqual(evaluated["result"]["value"], "about:blank") - time.sleep(1.5) - second_evaluated = cdp.send("Runtime.evaluate", {"expression": "document.readyState", "returnByValue": True}) - self.assertEqual(second_evaluated["result"]["value"], "complete") - finally: - cdp.close() - - -def _free_port() -> int: - sock = socket.socket() - sock.bind(("127.0.0.1", 0)) - try: - return int(sock.getsockname()[1]) - finally: - sock.close() - - -def _wait_until(predicate, timeout_s: float = 2.0) -> None: - deadline = time.monotonic() + timeout_s - while time.monotonic() < deadline: - if predicate(): - return - time.sleep(0.02) - raise AssertionError("timed out waiting for condition") - - -def reversews_test_browser_path() -> str: - global REVERSEWS_TEST_BROWSER_PATH - if REVERSEWS_TEST_BROWSER_PATH is not None: - return REVERSEWS_TEST_BROWSER_PATH - explicit_candidates = [ - os.environ.get("CHROME_PATH"), - "/usr/bin/chromium" if sys.platform.startswith("linux") else None, - ] - for candidate in explicit_candidates: - if candidate and Path(candidate).exists(): - REVERSEWS_TEST_BROWSER_PATH = candidate - return candidate - home = Path.home() - if sys.platform == "darwin": - patterns = [ - str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), - str(home / "Library/Caches/ms-playwright/chromium-*/chrome-mac*/Chromium.app/Contents/MacOS/Chromium"), - str(home / "Library/Caches/puppeteer/chrome/mac*-*/chrome-mac*/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"), - ] - elif sys.platform.startswith("win"): - local_app_data = Path(os.environ.get("LOCALAPPDATA") or home / "AppData/Local") - patterns = [ - str(local_app_data / "ms-playwright/chromium-*/chrome-win*/chrome.exe"), - str(home / ".cache/puppeteer/chrome/win*-*/chrome-win*/chrome.exe"), - ] - else: - patterns = [ - str(home / ".cache/ms-playwright/chromium-*/chrome-linux*/chrome"), - "/opt/pw-browsers/chromium-*/chrome-linux*/chrome", - str(home / ".cache/puppeteer/chrome/linux-*/chrome-linux*/chrome"), - ] - candidates = newest_first([match for pattern in patterns for match in glob.glob(pattern)]) - if candidates: - REVERSEWS_TEST_BROWSER_PATH = candidates[0] - return candidates[0] - raise RuntimeError("Reversews tests require CHROME_PATH, /usr/bin/chromium, or Chrome for Testing.") - - -def newest_first(candidates: list[str]) -> list[str]: - return sorted(dict.fromkeys(candidates), key=path_score) - - -def path_score(candidate: str) -> tuple[int, float, str]: - numbers = [int(part) for part in re.findall(r"\d+", candidate)] - version = max(numbers) if numbers else 0 - try: - mtime = Path(candidate).stat().st_mtime - except OSError: - mtime = 0.0 - return (-version, -mtime, candidate) - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_UpstreamTransport.py b/python/tests/test_UpstreamTransport.py index 900ae326..a14a875b 100644 --- a/python/tests/test_UpstreamTransport.py +++ b/python/tests/test_UpstreamTransport.py @@ -1,57 +1,61 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.UpstreamTransport.ts +# - ./go/modcdp/transport/UpstreamTransport_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations import unittest -from modcdp.transport.UpstreamTransport import UpstreamTransport, endpoint_kind_for_upstream +from modcdp.transport.UpstreamTransport import UpstreamTransport +from modcdp.types.generated.cdp import RuntimeDomain, TargetDomain class TestTransport(UpstreamTransport): - mode = "ws" - endpoint_kind = "raw_cdp" + upstream_mode = "ws" def emit(self, value: str) -> None: self._parse_and_emit_recv(value) class UpstreamTransportTests(unittest.TestCase): - def test_shared_transport_config_endpoint_classification_and_recv_callbacks(self) -> None: + def test_owns_shared_transport_config_and_recv_callbacks(self) -> None: transport = UpstreamTransport() received = [] stop = transport.onRecv(lambda message: received.append(message)) - self.assertEqual(endpoint_kind_for_upstream("ws"), "raw_cdp") - self.assertEqual(endpoint_kind_for_upstream("pipe"), "raw_cdp") - self.assertEqual(endpoint_kind_for_upstream("nativemessaging"), "modcdp_server") - self.assertEqual(endpoint_kind_for_upstream("reversews"), "modcdp_server") - self.assertEqual(endpoint_kind_for_upstream("nats"), "modcdp_server") self.assertIs(transport.update(), transport) - self.assertEqual(transport.getLauncherConfig(), {}) - self.assertEqual(transport.getInjectorConfig(), {}) - self.assertEqual(transport.getServerConfig(), {}) - self.assertIsNone(transport.close()) parsed = [] test_transport = TestTransport() test_transport.onRecv(lambda message: parsed.append(message)) test_transport.emit('{"id":1,"result":{"ok":true}}') + test_transport.emit('{"id":2,"result":true}') + test_transport.emit('{"id":3,"result":0}') test_transport.emit('{"method":"Runtime.executionContextCreated","params":{}}') self.assertEqual( parsed, [ {"id": 1, "result": {"ok": True}}, + {"id": 2, "result": True}, + {"id": 3, "result": 0}, {"method": "Runtime.executionContextCreated", "params": {}}, ], ) - stop() + typed_events = [] + test_transport.on(TargetDomain.targetCreated, lambda event, _target_id, _session_id: typed_events.append(event)) + test_transport.emit( + '{"method":"Target.targetCreated","params":{"targetInfo":{"targetId":"target-1","type":"page","title":"Example","url":"https://example.com","attached":false,"canAccessOpener":false}}}' + ) + self.assertEqual(typed_events[0]["targetInfo"]["targetId"], "target-1") + with self.assertRaises(ValueError): + test_transport.on(RuntimeDomain.executionContextDestroyed, lambda _event, _target_id, _session_id: None) + test_transport.emit('{"method":"Runtime.executionContextDestroyed","params":{"executionContextId":1}}') + stop() self.assertEqual(received, []) - close_errors = [] - stop_close = transport.onClose(lambda error: close_errors.append(error)) - stop_close() - stop_close() - transport._emit_close(RuntimeError("closed")) - self.assertEqual(close_errors, []) with self.assertRaisesRegex(NotImplementedError, "UpstreamTransport.connect is not implemented"): transport.connect() with self.assertRaisesRegex(NotImplementedError, "UpstreamTransport.send is not implemented"): diff --git a/python/tests/test_WSUpstreamTransport.py b/python/tests/test_WSUpstreamTransport.py new file mode 100644 index 00000000..ec63f3c0 --- /dev/null +++ b/python/tests/test_WSUpstreamTransport.py @@ -0,0 +1,161 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.WSUpstreamTransport.ts +# - ./go/modcdp/transport/WSUpstreamTransport_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. +from __future__ import annotations + +import unittest +import threading +import time +from collections.abc import Mapping +from queue import Queue + +from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher +from modcdp.types.modcdp import LaunchedBrowser +from modcdp.transport.WSUpstreamTransport import WSUpstreamTransport + + +def _close_chrome(chrome: LaunchedBrowser) -> None: + chrome.close() + + +class WSUpstreamTransportTests(unittest.TestCase): + def test_ws_upstream_constructor_update_server_config_and_unconnected_errors_match_the_transport_surface(self) -> None: + transport = WSUpstreamTransport() + self.assertEqual(transport.url, "") + self.assertIs(transport.update({"upstream_ws_cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}), transport) + self.assertEqual(transport.url, "ws://127.0.0.1:1/devtools/browser/test") + unconfigured = WSUpstreamTransport() + with self.assertRaisesRegex(RuntimeError, "WSUpstreamTransport requires"): + unconfigured.connect() + with self.assertRaisesRegex(RuntimeError, "CDP websocket is not connected"): + unconfigured.send({"id": 1, "method": "Browser.getVersion"}) + state = transport.toJSON()["state"] + if not isinstance(state, dict): + raise AssertionError(f"state = {state!r}") + connected = None + for key, value in state.items(): + if key == "connected": + connected = value + self.assertIs(connected, False) + + def test_ws_upstream_launches_a_real_browser_and_speaks_raw_cdp(self) -> None: + chrome = LocalBrowserLauncher({"launcher_local_headless": True}).launch() + transport = WSUpstreamTransport({"upstream_ws_cdp_url": chrome.cdp_url}) + received: Queue[dict] = Queue() + transport.onRecv(lambda message: received.put(message)) + try: + transport.connect() + self.assertRegex(transport.url or "", r"^ws://") + transport.send({"id": 1, "method": "Browser.getVersion", "params": {}}) + response = received.get(timeout=5) + self.assertEqual(response["id"], 1) + self.assertIsInstance(response["result"]["product"], str) + finally: + transport.close() + chrome.close() + + def test_ws_upstream_resolves_a_bare_host_port_cdp_endpoint_to_the_browser_websocket(self) -> None: + port = LocalBrowserLauncher.freePort() + chrome = LocalBrowserLauncher({"launcher_local_cdp_listen_port": port, "launcher_local_headless": True}).launch() + transport = WSUpstreamTransport({"upstream_ws_cdp_url": f"127.0.0.1:{port}"}) + received: Queue[dict] = Queue() + transport.onRecv(lambda message: received.put(message)) + try: + transport.connect() + self.assertEqual(transport.url, chrome["cdp_url"]) + transport.send({"id": 1, "method": "Browser.getVersion", "params": {}}) + response = received.get(timeout=5) + self.assertEqual(response["id"], 1) + self.assertIsInstance(response["result"]["product"], str) + finally: + transport.close() + chrome.close() + + def test_ws_upstream_close_clears_connection_state(self) -> None: + chrome = LocalBrowserLauncher({"launcher_local_headless": True}).launch() + transport = WSUpstreamTransport({"upstream_ws_cdp_url": chrome.cdp_url}) + + try: + transport.connect() + self.assertIsNotNone(transport.ws) + state = transport.toJSON()["state"] + if not isinstance(state, dict): + raise AssertionError(f"state = {state!r}") + connected = None + for key, value in state.items(): + if key == "connected": + connected = value + self.assertIs(connected, True) + transport.close() + self.assertIsNone(transport.ws) + state = transport.toJSON()["state"] + if not isinstance(state, dict): + raise AssertionError(f"state = {state!r}") + connected = None + for key, value in state.items(): + if key == "connected": + connected = value + self.assertIs(connected, False) + with self.assertRaisesRegex(RuntimeError, "CDP websocket is not connected"): + transport.send({"id": 1, "method": "Browser.getVersion"}) + finally: + transport.close() + chrome.close() + + def test_ws_upstream_close_rejects_pending_commands(self) -> None: + chrome = LocalBrowserLauncher({"launcher_local_headless": True}).launch() + transport = WSUpstreamTransport({"upstream_ws_cdp_url": chrome.cdp_url, "upstream_cdp_send_timeout_ms": 60_000}) + result: Queue[object] = Queue() + + try: + transport.connect() + target_id = transport.createTarget("about:blank#modcdp-pending-close") + session_id = transport.attachToTarget(target_id) + if not isinstance(session_id, str): + raise AssertionError(f"session_id = {session_id!r}") + + def send_pending() -> None: + try: + result.put( + transport.send( + "Runtime.evaluate", + {"expression": "new Promise(() => {})", "awaitPromise": True}, + session_id, + ) + ) + except BaseException as error: + result.put(error) + + thread = threading.Thread(target=send_pending, daemon=True) + thread.start() + deadline = time.time() + 5 + while time.time() < deadline: + state = object_map(transport.toJSON().get("state")) + pending = state.get("pending") + if pending == 1: + break + time.sleep(0.05) + else: + raise AssertionError("pending Runtime.evaluate was not recorded") + + transport.close() + error = result.get(timeout=5) + self.assertIsInstance(error, RuntimeError) + self.assertIn("CDP websocket closed", str(error)) + thread.join(timeout=1) + finally: + transport.close() + _close_chrome(chrome) + + +def object_map(value: object) -> Mapping[str, object]: + if not isinstance(value, Mapping): + raise AssertionError(f"expected object mapping, got {value!r}") + return {str(key): raw_value for key, raw_value in value.items()} + + +if __name__ == "__main__": + unittest.main() diff --git a/python/tests/test_WebSocketUpstreamTransport.py b/python/tests/test_WebSocketUpstreamTransport.py deleted file mode 100644 index f576956f..00000000 --- a/python/tests/test_WebSocketUpstreamTransport.py +++ /dev/null @@ -1,165 +0,0 @@ -from __future__ import annotations - -import time -import threading -import unittest -from queue import Queue -from typing import Any, cast -from unittest.mock import patch - -from modcdp import ModCDPClient -from modcdp.launcher.LocalBrowserLauncher import LocalBrowserLauncher -from modcdp.transport.WebSocketUpstreamTransport import WebSocketUpstreamTransport - - -class WebSocketUpstreamTransportTests(unittest.TestCase): - def test_reconnect_closes_old_socket_and_ignores_stale_reader_errors(self) -> None: - class FakeWebSocket: - def __init__(self, name: str, generation: Any) -> None: - self.name = name - self.generation = generation - self.closed = False - self.generation_at_close: int | None = None - self.entered = threading.Event() - self.release = threading.Event() - - def recv(self) -> str: - self.entered.set() - self.release.wait(timeout=1) - raise RuntimeError(f"{self.name} stale read") - - def send(self, _message: str) -> None: - return None - - def close(self) -> None: - self.generation_at_close = self.generation() - self.closed = True - self.release.set() - - closes: list[Exception] = [] - transport = WebSocketUpstreamTransport({"cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}) - first = FakeWebSocket("first", lambda: transport._generation) - second = FakeWebSocket("second", lambda: transport._generation) - transport.onClose(lambda error: closes.append(error)) - - with patch("modcdp.transport.WebSocketUpstreamTransport.create_connection", side_effect=[first, second]): - transport.connect() - self.assertTrue(first.entered.wait(timeout=1)) - transport.connect() - self.assertTrue(first.closed) - self.assertEqual(first.generation_at_close, 2) - first.release.set() - time.sleep(0.05) - self.assertEqual(closes, []) - transport.close() - - def test_constructor_update_and_server_config_match_ts_shape(self) -> None: - transport = WebSocketUpstreamTransport() - self.assertEqual(transport.url, "") - self.assertEqual(transport.getServerConfig(), {}) - self.assertIs(transport.update({"cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}), transport) - self.assertEqual(transport.url, "ws://127.0.0.1:1/devtools/browser/test") - self.assertEqual(transport.getServerConfig(), {"server_loopback_cdp_url": "ws://127.0.0.1:1/devtools/browser/test"}) - unconfigured = WebSocketUpstreamTransport() - with self.assertRaisesRegex(RuntimeError, r"upstream\.upstream_mode=ws requires"): - unconfigured.connect() - with self.assertRaisesRegex(RuntimeError, "CDP websocket is not connected"): - unconfigured.send({"id": 1, "method": "Browser.getVersion"}) - - def test_launches_real_browser_and_speaks_raw_cdp(self) -> None: - cdp = ModCDPClient( - launcher={"launcher_mode": "local", "launcher_options": {"headless": True}}, - upstream={"upstream_mode": "ws"}, - injector={ - "injector_mode": "auto", - "injector_service_worker_url_suffixes": ["/modcdp/service_worker.js"], - "injector_trust_service_worker_target": True, - }, - ) - try: - cdp.connect() - self.assertEqual(cdp.transport.mode if cdp.transport else None, "ws") - self.assertEqual(cdp.upstream_endpoint_kind, "raw_cdp") - timing = cdp.connect_timing - self.assertIsNotNone(timing) - if timing is None: - raise AssertionError("expected connect timing") - self.assertEqual(timing["upstream_mode"], "ws") - self.assertEqual(timing["upstream_endpoint_kind"], "raw_cdp") - self.assertGreaterEqual(timing["transport_connected_at"], timing["transport_started_at"]) - self.assertEqual( - timing["transport_duration_ms"], - timing["transport_connected_at"] - timing["transport_started_at"], - ) - self.assertRegex(cdp.cdp_url or "", r"^ws://") - version = cdp.sendRaw("Browser.getVersion") - self.assertIsInstance(version["product"], str) - time.sleep(1.5) - raw_target_infos = cdp.sendRaw("Target.getTargets").get("targetInfos", []) - target_infos = cast(list[dict[str, Any]], raw_target_infos if isinstance(raw_target_infos, list) else []) - self.assertTrue( - any( - target.get("type") == "service_worker" - and str(target.get("url", "")).endswith("/modcdp/service_worker.js") - for target in target_infos - ) - ) - self.assertTrue( - cdp.Mod.evaluate( - expression="Boolean(globalThis.ModCDP?.handleCommand && chrome.runtime.getURL('modcdp/service_worker.js'))" - ) - ) - finally: - cdp.close() - - def test_resolves_real_http_cdp_endpoint_to_browser_websocket(self) -> None: - chrome = LocalBrowserLauncher({"headless": True}).launch() - transport = WebSocketUpstreamTransport({"cdp_url": chrome["cdp_url"]}) - received: Queue[dict] = Queue() - transport.onRecv(lambda message: received.put(message)) - try: - transport.connect() - self.assertRegex(transport.url or "", r"^ws://") - transport.send({"id": 1, "method": "Browser.getVersion", "params": {}}) - response = received.get(timeout=5) - self.assertEqual(response["id"], 1) - self.assertIsInstance(response["result"]["product"], str) - finally: - transport.close() - chrome["close"]() - - def test_resolves_real_host_port_cdp_endpoint_to_browser_websocket(self) -> None: - port = LocalBrowserLauncher.freePort() - chrome = LocalBrowserLauncher({"port": port, "headless": True}).launch() - transport = WebSocketUpstreamTransport({"cdp_url": f"127.0.0.1:{port}"}) - received: Queue[dict] = Queue() - transport.onRecv(lambda message: received.put(message)) - try: - transport.connect() - self.assertEqual(transport.url, chrome["cdp_url"]) - transport.send({"id": 1, "method": "Browser.getVersion", "params": {}}) - response = received.get(timeout=5) - self.assertEqual(response["id"], 1) - self.assertIsInstance(response["result"]["product"], str) - finally: - transport.close() - chrome["close"]() - - def test_close_clears_connection_state(self) -> None: - chrome = LocalBrowserLauncher({"headless": True}).launch() - transport = WebSocketUpstreamTransport({"cdp_url": chrome["cdp_url"]}) - - try: - transport.connect() - self.assertIsNotNone(transport.ws) - transport.close() - self.assertIsNone(transport.ws) - with self.assertRaisesRegex(RuntimeError, "CDP websocket is not connected"): - transport.send({"id": 1, "method": "Browser.getVersion"}) - finally: - transport.close() - chrome["close"]() - - -if __name__ == "__main__": - unittest.main() diff --git a/python/tests/test_translate.py b/python/tests/test_translate.py index fd9ff1cd..89d8286d 100644 --- a/python/tests/test_translate.py +++ b/python/tests/test_translate.py @@ -1,63 +1,126 @@ +# MODCDP_TRANSLATE_TEST: KEEP THIS TEST FILE TRANSLATED ACROSS TYPESCRIPT, PYTHON, AND GO. +# All test cases, descriptions, covered edge cases, and setup should be kept perfectly 1:1 in sync between: +# - ./js/test/test.translate.ts +# - ./go/modcdp/translate/translate_test.go +# NO MOCKING, NO MONKEY PATCHING, NO SIMULATING, NO FAKING, NO SKIPPING ALLOWED. +# USE REAL USER-FACING CODE PATHS WITH REAL BROWSERS, REAL CLASSES, REAL URLS, etc. Hard fail if keys or other env requirements are missing. from __future__ import annotations -import unittest +from collections.abc import Mapping import json -from typing import cast +import unittest from modcdp.translate import ( CUSTOM_EVENT_BINDING_NAME, + encode_binding_payload, route_for, unwrap_event_if_needed, unwrap_response_if_needed, wrap_command_if_needed, ) +from modcdp.types.modcdp import ModCDPBindingPayload class TranslateTests(unittest.TestCase): - def test_routes_wraps_and_unwraps_modcdp_protocol_messages_deterministically(self) -> None: + def test_translate_routes_wraps_and_unwraps_modcdp_protocol_messages_deterministically(self) -> None: self.assertEqual(route_for("Browser.getVersion", {"Browser.*": "direct_cdp", "*.*": "service_worker"}), "direct_cdp") self.assertEqual(route_for("Target.getTargets", {"Browser.*": "direct_cdp", "*.*": "service_worker"}), "service_worker") + self.assertEqual(route_for("Browser.getVersion"), "direct_cdp") direct = wrap_command_if_needed("Browser.getVersion", {}, routes={"*.*": "direct_cdp"}) - self.assertEqual(direct["target"], "direct_cdp") - self.assertEqual(direct["steps"], [{"method": "Browser.getVersion", "params": {}}]) + self.assertEqual(direct.target, "direct_cdp") + self.assertEqual(direct.steps, [{"method": "Browser.getVersion", "params": {}}]) wrapped = wrap_command_if_needed( "Mod.evaluate", {"expression": "({ ok: true })", "params": {"value": 1}}, cdp_session_id="session-1", ) - self.assertEqual(wrapped["target"], "service_worker") - self.assertEqual(wrapped["steps"][0]["method"], "Runtime.callFunctionOn") - self.assertIn('attachToSession("session-1")', str(wrapped["steps"][0].get("params", {}).get("functionDeclaration"))) - self.assertEqual(wrapped["steps"][0].get("unwrap"), "runtime") + self.assertEqual(wrapped.target, "service_worker") + self.assertEqual(wrapped.steps[0].method, "Runtime.callFunctionOn") + wrapped_step_params = wrapped.steps[0].params + self.assertIsNotNone(wrapped_step_params) + assert wrapped_step_params is not None + self.assertIn("globalThis.ModCDP.handleCommand", str(wrapped_step_params.get("functionDeclaration"))) + wrapped_arguments = wrapped_step_params["arguments"] + self.assertIsInstance(wrapped_arguments, list) + assert isinstance(wrapped_arguments, list) + self.assertIsInstance(wrapped_arguments[1], Mapping) + self.assertIsInstance(wrapped_arguments[2], Mapping) + assert isinstance(wrapped_arguments[1], Mapping) + assert isinstance(wrapped_arguments[2], Mapping) + self.assertEqual(len(wrapped_arguments[1]), 1) + self.assertEqual(len(wrapped_arguments[2]), 1) + self.assertEqual(json.loads(str(next(iter(wrapped_arguments[1].values())))), {"expression": "({ ok: true })", "params": {"value": 1}}) + self.assertEqual(next(iter(wrapped_arguments[2].values())), "session-1") + self.assertEqual(wrapped.steps[0].unwrap, "runtime_json") configured = wrap_command_if_needed( "Mod.configure", - {"server": {"server_routes": {"*.*": "loopback_cdp"}}}, + {"router": {"router_routes": {"*.*": "loopback_cdp"}}}, cdp_session_id="session-1", ) - self.assertEqual(configured["steps"][0].get("unwrap"), "runtime_json") + self.assertEqual(configured.steps[0].unwrap, "runtime_json") + + ping = wrap_command_if_needed("Mod.ping", {}) + ping_step_params = ping.steps[0].params + self.assertIsNotNone(ping_step_params) + assert ping_step_params is not None + ping_arguments = ping_step_params["arguments"] + self.assertIsInstance(ping_arguments, list) + assert isinstance(ping_arguments, list) + self.assertIsInstance(ping_arguments[1], Mapping) + assert isinstance(ping_arguments[1], Mapping) + self.assertEqual(len(ping_arguments[1]), 1) + self.assertEqual(json.loads(str(next(iter(ping_arguments[1].values())))), {}) custom = wrap_command_if_needed( "Custom.echo", {"secret": "x" * 100, "nested": {"ok": True}}, cdp_session_id="session-1", ) - custom_step_params = custom["steps"][0].get("params", {}) + custom_step_params = custom.steps[0].params + self.assertIsNotNone(custom_step_params) + assert custom_step_params is not None self.assertIn("JSON.parse(paramsJson)", str(custom_step_params.get("functionDeclaration"))) self.assertNotIn("xxxxxxxxxx", str(custom_step_params.get("functionDeclaration"))) - custom_arguments = cast("list[dict[str, object]]", custom_step_params.get("arguments", [])) - self.assertEqual(custom_arguments[0].get("value"), "Custom.echo") - self.assertEqual(json.loads(str(custom_arguments[1].get("value"))), {"secret": "x" * 100, "nested": {"ok": True}}) - self.assertEqual(custom_arguments[2].get("value"), "session-1") + custom_arguments = custom_step_params["arguments"] + self.assertIsInstance(custom_arguments, list) + assert isinstance(custom_arguments, list) + self.assertIsInstance(custom_arguments[0], Mapping) + self.assertIsInstance(custom_arguments[1], Mapping) + self.assertIsInstance(custom_arguments[2], Mapping) + assert isinstance(custom_arguments[0], Mapping) + assert isinstance(custom_arguments[1], Mapping) + assert isinstance(custom_arguments[2], Mapping) + self.assertEqual(len(custom_arguments[0]), 1) + self.assertEqual(len(custom_arguments[1]), 1) + self.assertEqual(len(custom_arguments[2]), 1) + self.assertEqual(next(iter(custom_arguments[0].values())), "Custom.echo") + self.assertEqual(json.loads(str(next(iter(custom_arguments[1].values())))), {"secret": "x" * 100, "nested": {"ok": True}}) + self.assertEqual(next(iter(custom_arguments[2].values())), "session-1") + + custom_with_session = wrap_command_if_needed( + "Custom.echo", + {"secret": "targeted"}, + cdp_session_id="target-session-1", + ) + custom_with_session_params = custom_with_session.steps[0].params + self.assertIsNotNone(custom_with_session_params) + assert custom_with_session_params is not None + custom_with_session_arguments = custom_with_session_params["arguments"] + self.assertIsInstance(custom_with_session_arguments, list) + assert isinstance(custom_with_session_arguments, list) + self.assertIsInstance(custom_with_session_arguments[2], Mapping) + assert isinstance(custom_with_session_arguments[2], Mapping) + self.assertEqual(len(custom_with_session_arguments[2]), 1) + self.assertEqual(next(iter(custom_with_session_arguments[2].values())), "target-session-1") self.assertEqual(unwrap_response_if_needed({"result": {"type": "object", "value": {"ok": True}}}, "runtime"), {"ok": True}) self.assertEqual(unwrap_response_if_needed({"product": "Chrome/1"}, None), {"product": "Chrome/1"}) - payload = json.dumps( - {"event": "Custom.ready", "data": {"ready": True}, "cdpSessionId": "session-2"}, - separators=(",", ":"), + payload = encode_binding_payload( + ModCDPBindingPayload(event="Custom.ready", data={"ready": True}, cdpSessionId="session-2") ) self.assertEqual( unwrap_event_if_needed(