@agentrhq/webcmd 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -24
- package/dist/src/adapter-shadow.d.ts +11 -0
- package/dist/src/adapter-shadow.js +72 -0
- package/dist/src/adapter-shadow.test.d.ts +1 -0
- package/dist/src/adapter-shadow.test.js +49 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.d.ts +1 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/brand.d.ts +9 -0
- package/dist/src/brand.js +9 -0
- package/dist/src/brand.test.d.ts +1 -0
- package/dist/src/brand.test.js +15 -0
- package/dist/src/browser/analyze.d.ts +118 -0
- package/dist/src/browser/analyze.js +405 -0
- package/dist/src/browser/analyze.test.d.ts +1 -0
- package/dist/src/browser/analyze.test.js +209 -0
- package/dist/src/browser/article-extract.d.ts +57 -0
- package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
- package/dist/src/browser/article-extract.e2e.test.js +105 -0
- package/dist/src/browser/article-extract.js +169 -0
- package/dist/src/browser/article-extract.test.d.ts +1 -0
- package/dist/src/browser/article-extract.test.js +94 -0
- package/dist/src/browser/ax-snapshot.d.ts +37 -0
- package/dist/src/browser/ax-snapshot.js +217 -0
- package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
- package/dist/src/browser/ax-snapshot.test.js +91 -0
- package/dist/src/browser/base-page.d.ts +151 -0
- package/dist/src/browser/base-page.js +1060 -0
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +803 -0
- package/dist/src/browser/bridge-readiness.d.ts +14 -0
- package/dist/src/browser/bridge-readiness.js +24 -0
- package/dist/src/browser/bridge-readiness.test.d.ts +1 -0
- package/dist/src/browser/bridge-readiness.test.js +58 -0
- package/dist/src/browser/bridge.d.ts +26 -0
- package/dist/src/browser/bridge.js +58 -0
- package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
- package/dist/src/browser/cdp-click-fixture.test.js +87 -0
- package/dist/src/browser/cdp.d.ts +46 -0
- package/dist/src/browser/cdp.js +536 -0
- package/dist/src/browser/cdp.test.d.ts +1 -0
- package/dist/src/browser/cdp.test.js +79 -0
- package/dist/src/browser/compound.d.ts +59 -0
- package/dist/src/browser/compound.js +112 -0
- package/dist/src/browser/compound.test.d.ts +1 -0
- package/dist/src/browser/compound.test.js +175 -0
- package/dist/src/browser/config.d.ts +2 -0
- package/dist/src/browser/config.js +14 -0
- package/dist/src/browser/daemon-client.d.ts +32 -0
- package/dist/src/browser/daemon-client.js +135 -0
- package/dist/src/browser/daemon-client.test.d.ts +1 -0
- package/dist/src/browser/daemon-client.test.js +326 -0
- package/dist/src/browser/daemon-lifecycle.d.ts +37 -0
- package/dist/src/browser/daemon-lifecycle.js +147 -0
- package/dist/src/browser/daemon-transport.d.ts +54 -0
- package/dist/src/browser/daemon-transport.js +68 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -0
- package/dist/src/browser/dom-helpers.d.ts +41 -0
- package/dist/src/browser/dom-helpers.js +239 -0
- package/dist/src/browser/dom-helpers.test.d.ts +1 -0
- package/dist/src/browser/dom-helpers.test.js +106 -0
- package/dist/src/browser/dom-snapshot.d.ts +93 -0
- package/dist/src/browser/dom-snapshot.js +894 -0
- package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/src/browser/dom-snapshot.test.js +313 -0
- package/dist/src/browser/errors.d.ts +38 -0
- package/dist/src/browser/errors.js +79 -0
- package/dist/src/browser/errors.test.d.ts +1 -0
- package/dist/src/browser/errors.test.js +60 -0
- package/dist/src/browser/extract.d.ts +69 -0
- package/dist/src/browser/extract.js +132 -0
- package/dist/src/browser/extract.test.d.ts +1 -0
- package/dist/src/browser/extract.test.js +129 -0
- package/dist/src/browser/find.d.ts +84 -0
- package/dist/src/browser/find.js +398 -0
- package/dist/src/browser/find.test.d.ts +1 -0
- package/dist/src/browser/find.test.js +180 -0
- package/dist/src/browser/html-tree.d.ts +75 -0
- package/dist/src/browser/html-tree.js +112 -0
- package/dist/src/browser/html-tree.test.d.ts +1 -0
- package/dist/src/browser/html-tree.test.js +181 -0
- package/dist/src/browser/index.d.ts +14 -0
- package/dist/src/browser/index.js +12 -0
- package/dist/src/browser/network-cache.d.ts +49 -0
- package/dist/src/browser/network-cache.js +78 -0
- package/dist/src/browser/network-cache.test.d.ts +1 -0
- package/dist/src/browser/network-cache.test.js +75 -0
- package/dist/src/browser/network-interceptor.d.ts +11 -0
- package/dist/src/browser/network-interceptor.js +11 -0
- package/dist/src/browser/network-key.d.ts +22 -0
- package/dist/src/browser/network-key.js +66 -0
- package/dist/src/browser/network-key.test.d.ts +1 -0
- package/dist/src/browser/network-key.test.js +49 -0
- package/dist/src/browser/page.d.ts +83 -0
- package/dist/src/browser/page.js +453 -0
- package/dist/src/browser/page.test.d.ts +1 -0
- package/dist/src/browser/page.test.js +406 -0
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +86 -0
- package/dist/src/browser/protocol.d.ts +63 -0
- package/dist/src/browser/protocol.js +1 -0
- package/dist/src/browser/runtime/local-cloak/actions.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +240 -0
- package/dist/src/browser/runtime/local-cloak/downloads.d.ts +3 -0
- package/dist/src/browser/runtime/local-cloak/downloads.js +28 -0
- package/dist/src/browser/runtime/local-cloak/network.d.ts +28 -0
- package/dist/src/browser/runtime/local-cloak/network.js +120 -0
- package/dist/src/browser/runtime/local-cloak/network.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/network.test.js +124 -0
- package/dist/src/browser/runtime/local-cloak/profiles.d.ts +6 -0
- package/dist/src/browser/runtime/local-cloak/profiles.js +17 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/profiles.test.js +19 -0
- package/dist/src/browser/runtime/local-cloak/provider.d.ts +15 -0
- package/dist/src/browser/runtime/local-cloak/provider.js +27 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/provider.test.js +244 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +77 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +280 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.d.ts +1 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +92 -0
- package/dist/src/browser/runtime/provider.d.ts +9 -0
- package/dist/src/browser/runtime/provider.js +1 -0
- package/dist/src/browser/runtime/provider.test.d.ts +2 -0
- package/dist/src/browser/runtime/provider.test.js +49 -0
- package/dist/src/browser/shape-filter.d.ts +52 -0
- package/dist/src/browser/shape-filter.js +101 -0
- package/dist/src/browser/shape-filter.test.d.ts +1 -0
- package/dist/src/browser/shape-filter.test.js +101 -0
- package/dist/src/browser/shape.d.ts +23 -0
- package/dist/src/browser/shape.js +95 -0
- package/dist/src/browser/shape.test.d.ts +1 -0
- package/dist/src/browser/shape.test.js +82 -0
- package/dist/src/browser/stealth.d.ts +11 -0
- package/dist/src/browser/stealth.js +359 -0
- package/dist/src/browser/stealth.test.d.ts +1 -0
- package/dist/src/browser/stealth.test.js +134 -0
- package/dist/src/browser/tabs.d.ts +13 -0
- package/dist/src/browser/tabs.js +70 -0
- package/dist/src/browser/target-errors.d.ts +39 -0
- package/dist/src/browser/target-errors.js +45 -0
- package/dist/src/browser/target-errors.test.d.ts +1 -0
- package/dist/src/browser/target-errors.test.js +94 -0
- package/dist/src/browser/target-resolver.d.ts +158 -0
- package/dist/src/browser/target-resolver.js +570 -0
- package/dist/src/browser/target-resolver.test.d.ts +1 -0
- package/dist/src/browser/target-resolver.test.js +118 -0
- package/dist/src/browser/utils.d.ts +20 -0
- package/dist/src/browser/utils.js +64 -0
- package/dist/src/browser/utils.test.d.ts +1 -0
- package/dist/src/browser/utils.test.js +29 -0
- package/dist/src/browser/verify-fixture.d.ts +65 -0
- package/dist/src/browser/verify-fixture.js +318 -0
- package/dist/src/browser/verify-fixture.test.d.ts +1 -0
- package/dist/src/browser/verify-fixture.test.js +219 -0
- package/dist/src/browser/visual-refs.d.ts +11 -0
- package/dist/src/browser/visual-refs.js +108 -0
- package/dist/src/browser.test.d.ts +1 -0
- package/dist/src/browser.test.js +297 -0
- package/dist/src/build-manifest.d.ts +103 -0
- package/dist/src/build-manifest.js +363 -0
- package/dist/src/build-manifest.test.d.ts +1 -0
- package/dist/src/build-manifest.test.js +341 -0
- package/dist/src/capabilityRouting.d.ts +19 -0
- package/dist/src/capabilityRouting.js +56 -0
- package/dist/src/capabilityRouting.test.d.ts +1 -0
- package/dist/src/capabilityRouting.test.js +63 -0
- package/dist/src/cli-argv-preprocess.d.ts +63 -0
- package/dist/src/cli-argv-preprocess.js +303 -0
- package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
- package/dist/src/cli-argv-preprocess.test.js +304 -0
- package/dist/src/cli.d.ts +79 -0
- package/dist/src/cli.js +3350 -0
- package/dist/src/cli.test.d.ts +1 -0
- package/dist/src/cli.test.js +3041 -0
- package/dist/src/commanderAdapter.d.ts +21 -0
- package/dist/src/commanderAdapter.js +208 -0
- package/dist/src/commanderAdapter.test.d.ts +1 -0
- package/dist/src/commanderAdapter.test.js +352 -0
- package/dist/src/commands/auth.d.ts +40 -0
- package/dist/src/commands/auth.js +434 -0
- package/dist/src/commands/auth.test.d.ts +1 -0
- package/dist/src/commands/auth.test.js +252 -0
- package/dist/src/commands/daemon.d.ts +9 -0
- package/dist/src/commands/daemon.js +91 -0
- package/dist/src/commands/daemon.test.d.ts +1 -0
- package/dist/src/commands/daemon.test.js +293 -0
- package/dist/src/completion-fast.d.ts +22 -0
- package/dist/src/completion-fast.js +88 -0
- package/dist/src/completion-shared.d.ts +13 -0
- package/dist/src/completion-shared.js +61 -0
- package/dist/src/completion.d.ts +21 -0
- package/dist/src/completion.js +68 -0
- package/dist/src/completion.test.d.ts +1 -0
- package/dist/src/completion.test.js +24 -0
- package/dist/src/constants.d.ts +18 -0
- package/dist/src/constants.js +41 -0
- package/dist/src/convention-audit.d.ts +50 -0
- package/dist/src/convention-audit.js +553 -0
- package/dist/src/convention-audit.test.d.ts +1 -0
- package/dist/src/convention-audit.test.js +255 -0
- package/dist/src/daemon/server.d.ts +13 -0
- package/dist/src/daemon/server.js +185 -0
- package/dist/src/daemon/server.test.d.ts +1 -0
- package/dist/src/daemon/server.test.js +132 -0
- package/dist/src/daemon-utils.d.ts +18 -0
- package/dist/src/daemon-utils.js +37 -0
- package/dist/src/daemon.d.ts +1 -0
- package/dist/src/daemon.js +27 -0
- package/dist/src/daemon.test.d.ts +1 -0
- package/dist/src/daemon.test.js +60 -0
- package/dist/src/discovery.d.ts +46 -0
- package/dist/src/discovery.js +341 -0
- package/dist/src/doctor.d.ts +39 -0
- package/dist/src/doctor.js +168 -0
- package/dist/src/doctor.test.d.ts +1 -0
- package/dist/src/doctor.test.js +302 -0
- package/dist/src/download/article-download.d.ts +72 -0
- package/dist/src/download/article-download.js +305 -0
- package/dist/src/download/article-download.test.d.ts +1 -0
- package/dist/src/download/article-download.test.js +235 -0
- package/dist/src/download/index.d.ts +71 -0
- package/dist/src/download/index.js +363 -0
- package/dist/src/download/index.test.d.ts +1 -0
- package/dist/src/download/index.test.js +174 -0
- package/dist/src/download/media-download.d.ts +49 -0
- package/dist/src/download/media-download.js +127 -0
- package/dist/src/download/media-download.test.d.ts +1 -0
- package/dist/src/download/media-download.test.js +112 -0
- package/dist/src/download/progress.d.ts +36 -0
- package/dist/src/download/progress.js +120 -0
- package/dist/src/download/progress.test.d.ts +1 -0
- package/dist/src/download/progress.test.js +36 -0
- package/dist/src/electron-apps.d.ts +31 -0
- package/dist/src/electron-apps.js +91 -0
- package/dist/src/electron-apps.test.d.ts +1 -0
- package/dist/src/electron-apps.test.js +76 -0
- package/dist/src/engine.test.d.ts +1 -0
- package/dist/src/engine.test.js +312 -0
- package/dist/src/errors.d.ts +114 -0
- package/dist/src/errors.js +174 -0
- package/dist/src/errors.test.d.ts +1 -0
- package/dist/src/errors.test.js +109 -0
- package/dist/src/execution.d.ts +24 -0
- package/dist/src/execution.js +509 -0
- package/dist/src/execution.test.d.ts +1 -0
- package/dist/src/execution.test.js +645 -0
- package/dist/src/external.d.ts +49 -0
- package/dist/src/external.js +211 -0
- package/dist/src/external.test.d.ts +1 -0
- package/dist/src/external.test.js +110 -0
- package/dist/src/help.d.ts +88 -0
- package/dist/src/help.js +556 -0
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +58 -0
- package/dist/src/hooks.d.ts +46 -0
- package/dist/src/hooks.js +58 -0
- package/dist/src/hooks.test.d.ts +4 -0
- package/dist/src/hooks.test.js +92 -0
- package/dist/src/interceptor.d.ts +44 -0
- package/dist/src/interceptor.js +183 -0
- package/dist/src/interceptor.test.d.ts +4 -0
- package/dist/src/interceptor.test.js +81 -0
- package/dist/src/launcher.d.ts +41 -0
- package/dist/src/launcher.js +226 -0
- package/dist/src/launcher.test.d.ts +1 -0
- package/dist/src/launcher.test.js +153 -0
- package/dist/src/logger.d.ts +26 -0
- package/dist/src/logger.js +49 -0
- package/dist/src/main.d.ts +5 -0
- package/dist/src/main.js +177 -0
- package/dist/src/manifest-types.d.ts +43 -0
- package/dist/src/manifest-types.js +9 -0
- package/dist/src/node-network.d.ts +10 -0
- package/dist/src/node-network.js +174 -0
- package/dist/src/node-network.test.d.ts +1 -0
- package/dist/src/node-network.test.js +55 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/output.d.ts +14 -0
- package/dist/src/output.js +152 -0
- package/dist/src/output.test.d.ts +1 -0
- package/dist/src/output.test.js +53 -0
- package/dist/src/package-exports.test.d.ts +1 -0
- package/dist/src/package-exports.test.js +112 -0
- package/dist/src/package-paths.d.ts +8 -0
- package/dist/src/package-paths.js +41 -0
- package/dist/src/pipeline/executor.d.ts +11 -0
- package/dist/src/pipeline/executor.js +93 -0
- package/dist/src/pipeline/executor.test.d.ts +4 -0
- package/dist/src/pipeline/executor.test.js +182 -0
- package/dist/src/pipeline/index.d.ts +5 -0
- package/dist/src/pipeline/index.js +5 -0
- package/dist/src/pipeline/registry.d.ts +28 -0
- package/dist/src/pipeline/registry.js +55 -0
- package/dist/src/pipeline/steps/browser.d.ts +13 -0
- package/dist/src/pipeline/steps/browser.js +86 -0
- package/dist/src/pipeline/steps/download.d.ts +18 -0
- package/dist/src/pipeline/steps/download.js +252 -0
- package/dist/src/pipeline/steps/download.test.d.ts +1 -0
- package/dist/src/pipeline/steps/download.test.js +102 -0
- package/dist/src/pipeline/steps/fetch.d.ts +5 -0
- package/dist/src/pipeline/steps/fetch.js +122 -0
- package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/src/pipeline/steps/fetch.test.js +117 -0
- package/dist/src/pipeline/steps/intercept.d.ts +5 -0
- package/dist/src/pipeline/steps/intercept.js +50 -0
- package/dist/src/pipeline/steps/tap.d.ts +12 -0
- package/dist/src/pipeline/steps/tap.js +93 -0
- package/dist/src/pipeline/steps/transform.d.ts +9 -0
- package/dist/src/pipeline/steps/transform.js +70 -0
- package/dist/src/pipeline/template.d.ts +17 -0
- package/dist/src/pipeline/template.js +344 -0
- package/dist/src/pipeline/template.test.d.ts +4 -0
- package/dist/src/pipeline/template.test.js +197 -0
- package/dist/src/pipeline/transform.test.d.ts +4 -0
- package/dist/src/pipeline/transform.test.js +133 -0
- package/dist/src/plugin-manifest.d.ts +70 -0
- package/dist/src/plugin-manifest.js +160 -0
- package/dist/src/plugin-manifest.test.d.ts +4 -0
- package/dist/src/plugin-manifest.test.js +179 -0
- package/dist/src/plugin-scaffold.d.ts +28 -0
- package/dist/src/plugin-scaffold.js +143 -0
- package/dist/src/plugin-scaffold.test.d.ts +4 -0
- package/dist/src/plugin-scaffold.test.js +83 -0
- package/dist/src/plugin.d.ts +146 -0
- package/dist/src/plugin.js +1303 -0
- package/dist/src/plugin.test.d.ts +4 -0
- package/dist/src/plugin.test.js +1363 -0
- package/dist/src/registry-api.d.ts +13 -0
- package/dist/src/registry-api.js +10 -0
- package/dist/src/registry.d.ts +123 -0
- package/dist/src/registry.js +125 -0
- package/dist/src/registry.test.d.ts +4 -0
- package/dist/src/registry.test.js +209 -0
- package/dist/src/runtime-copy.test.d.ts +1 -0
- package/dist/src/runtime-copy.test.js +29 -0
- package/dist/src/runtime-detect.d.ts +31 -0
- package/dist/src/runtime-detect.js +47 -0
- package/dist/src/runtime-detect.test.d.ts +1 -0
- package/dist/src/runtime-detect.test.js +39 -0
- package/dist/src/runtime-identity.test.d.ts +1 -0
- package/dist/src/runtime-identity.test.js +18 -0
- package/dist/src/runtime.d.ts +47 -0
- package/dist/src/runtime.js +54 -0
- package/dist/src/scripts/framework.d.ts +7 -0
- package/dist/src/scripts/framework.js +25 -0
- package/dist/src/scripts/interact.d.ts +4 -0
- package/dist/src/scripts/interact.js +20 -0
- package/dist/src/scripts/store.d.ts +13 -0
- package/dist/src/scripts/store.js +48 -0
- package/dist/src/serialization.d.ts +42 -0
- package/dist/src/serialization.js +103 -0
- package/dist/src/serialization.test.d.ts +1 -0
- package/dist/src/serialization.test.js +78 -0
- package/dist/src/skills.d.ts +14 -0
- package/dist/src/skills.js +127 -0
- package/dist/src/skills.test.d.ts +1 -0
- package/dist/src/skills.test.js +71 -0
- package/dist/src/snapshotFormatter.d.ts +11 -0
- package/dist/src/snapshotFormatter.js +338 -0
- package/dist/src/snapshotFormatter.test.d.ts +7 -0
- package/dist/src/snapshotFormatter.test.js +521 -0
- package/dist/src/tui.d.ts +28 -0
- package/dist/src/tui.js +179 -0
- package/dist/src/tui.test.d.ts +1 -0
- package/dist/src/tui.test.js +19 -0
- package/dist/src/types.d.ts +236 -0
- package/dist/src/types.js +7 -0
- package/dist/src/update-check.d.ts +66 -0
- package/dist/src/update-check.js +206 -0
- package/dist/src/update-check.test.d.ts +1 -0
- package/dist/src/update-check.test.js +117 -0
- package/dist/src/utils.d.ts +57 -0
- package/dist/src/utils.js +152 -0
- package/dist/src/utils.test.d.ts +1 -0
- package/dist/src/utils.test.js +155 -0
- package/dist/src/validate.d.ts +21 -0
- package/dist/src/validate.js +130 -0
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/dist/src/verify.d.ts +26 -0
- package/dist/src/verify.js +67 -0
- package/dist/src/version.d.ts +4 -0
- package/dist/src/version.js +22 -0
- package/dist/src/weixin-download.test.d.ts +1 -0
- package/dist/src/weixin-download.test.js +45 -0
- package/package.json +4 -3
- package/scripts/check-package-bin.mjs +95 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP client — implements IPage by connecting directly to a Chrome/Electron CDP WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Fixes applied:
|
|
5
|
+
* - send() now has a 30s timeout guard (P0 #4)
|
|
6
|
+
* - goto() waits for Page.loadEventFired instead of hardcoded 1s sleep (P1 #3)
|
|
7
|
+
* - Implemented scroll, autoScroll, screenshot, networkRequests (P1 #2)
|
|
8
|
+
* - Shared DOM helper methods extracted to reduce duplication with Page (P1 #5)
|
|
9
|
+
*/
|
|
10
|
+
import { WebSocket } from 'ws';
|
|
11
|
+
import { request as httpRequest } from 'node:http';
|
|
12
|
+
import { request as httpsRequest } from 'node:https';
|
|
13
|
+
import { buildEvaluateExpression } from './utils.js';
|
|
14
|
+
import { generateStealthJs } from './stealth.js';
|
|
15
|
+
import { waitForDomStableJs } from './dom-helpers.js';
|
|
16
|
+
import { isRecord, saveBase64ToFile } from '../utils.js';
|
|
17
|
+
import { getAllElectronApps } from '../electron-apps.js';
|
|
18
|
+
import { BasePage } from './base-page.js';
|
|
19
|
+
const CDP_SEND_TIMEOUT = 30_000;
|
|
20
|
+
// Memory guard for in-process capture. The 4k cap we used to apply everywhere
|
|
21
|
+
// silently truncated JSON so `JSON.parse` failed or gave partial objects — the
|
|
22
|
+
// primary agent-facing bug. Now we keep the full body up to a large cap and
|
|
23
|
+
// surface `responseBodyFullSize` + `responseBodyTruncated` so downstream layers
|
|
24
|
+
// can tell the agent what happened instead of lying about the payload.
|
|
25
|
+
export const CDP_RESPONSE_BODY_CAPTURE_LIMIT = 8 * 1024 * 1024;
|
|
26
|
+
export class CDPBridge {
|
|
27
|
+
_ws = null;
|
|
28
|
+
_idCounter = 0;
|
|
29
|
+
_pending = new Map();
|
|
30
|
+
_eventListeners = new Map();
|
|
31
|
+
async connect(opts) {
|
|
32
|
+
if (this._ws)
|
|
33
|
+
throw new Error('CDPBridge is already connected. Call close() before reconnecting.');
|
|
34
|
+
const endpoint = opts?.cdpEndpoint ?? process.env.WEBCMD_CDP_ENDPOINT;
|
|
35
|
+
if (!endpoint)
|
|
36
|
+
throw new Error('CDP endpoint not provided (pass cdpEndpoint or set WEBCMD_CDP_ENDPOINT)');
|
|
37
|
+
let wsUrl = endpoint;
|
|
38
|
+
if (endpoint.startsWith('http')) {
|
|
39
|
+
const targets = await fetchJsonDirect(`${endpoint.replace(/\/$/, '')}/json`);
|
|
40
|
+
const target = selectCDPTarget(targets);
|
|
41
|
+
if (!target || !target.webSocketDebuggerUrl) {
|
|
42
|
+
throw new Error('No inspectable targets found at CDP endpoint');
|
|
43
|
+
}
|
|
44
|
+
wsUrl = target.webSocketDebuggerUrl;
|
|
45
|
+
}
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const ws = new WebSocket(wsUrl);
|
|
48
|
+
const timeoutMs = (opts?.timeout ?? 10) * 1000;
|
|
49
|
+
const timeout = setTimeout(() => {
|
|
50
|
+
this._ws = null;
|
|
51
|
+
ws.close();
|
|
52
|
+
reject(new Error('CDP connect timeout'));
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
ws.on('open', async () => {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
this._ws = ws;
|
|
57
|
+
try {
|
|
58
|
+
await this.send('Page.enable');
|
|
59
|
+
await this.send('Page.addScriptToEvaluateOnNewDocument', { source: generateStealthJs() });
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
ws.close();
|
|
63
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve(new CDPPage(this));
|
|
67
|
+
});
|
|
68
|
+
ws.on('error', (err) => {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
reject(err);
|
|
71
|
+
});
|
|
72
|
+
ws.on('message', (data) => {
|
|
73
|
+
try {
|
|
74
|
+
const msg = JSON.parse(data.toString());
|
|
75
|
+
if (msg.id && this._pending.has(msg.id)) {
|
|
76
|
+
const entry = this._pending.get(msg.id);
|
|
77
|
+
clearTimeout(entry.timer);
|
|
78
|
+
this._pending.delete(msg.id);
|
|
79
|
+
if (msg.error) {
|
|
80
|
+
entry.reject(new Error(msg.error.message));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
entry.resolve(msg.result);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (msg.method) {
|
|
87
|
+
const listeners = this._eventListeners.get(msg.method);
|
|
88
|
+
if (listeners) {
|
|
89
|
+
for (const fn of listeners)
|
|
90
|
+
fn(msg.params);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
if (process.env.WEBCMD_VERBOSE) {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.error('[cdp] Failed to parse WebSocket message:', err instanceof Error ? err.message : err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async close() {
|
|
104
|
+
if (this._ws) {
|
|
105
|
+
this._ws.close();
|
|
106
|
+
this._ws = null;
|
|
107
|
+
}
|
|
108
|
+
for (const p of this._pending.values()) {
|
|
109
|
+
clearTimeout(p.timer);
|
|
110
|
+
p.reject(new Error('CDP connection closed'));
|
|
111
|
+
}
|
|
112
|
+
this._pending.clear();
|
|
113
|
+
this._eventListeners.clear();
|
|
114
|
+
}
|
|
115
|
+
async send(method, params = {}, timeoutMs = CDP_SEND_TIMEOUT) {
|
|
116
|
+
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
|
|
117
|
+
throw new Error('CDP connection is not open');
|
|
118
|
+
}
|
|
119
|
+
const id = ++this._idCounter;
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
this._pending.delete(id);
|
|
123
|
+
reject(new Error(`CDP command '${method}' timed out after ${timeoutMs / 1000}s`));
|
|
124
|
+
}, timeoutMs);
|
|
125
|
+
this._pending.set(id, { resolve, reject, timer });
|
|
126
|
+
this._ws.send(JSON.stringify({ id, method, params }));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
on(event, handler) {
|
|
130
|
+
let set = this._eventListeners.get(event);
|
|
131
|
+
if (!set) {
|
|
132
|
+
set = new Set();
|
|
133
|
+
this._eventListeners.set(event, set);
|
|
134
|
+
}
|
|
135
|
+
set.add(handler);
|
|
136
|
+
}
|
|
137
|
+
off(event, handler) {
|
|
138
|
+
this._eventListeners.get(event)?.delete(handler);
|
|
139
|
+
}
|
|
140
|
+
waitForEvent(event, timeoutMs = 15_000) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
this.off(event, handler);
|
|
144
|
+
reject(new Error(`Timed out waiting for CDP event '${event}'`));
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
const handler = (params) => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
this.off(event, handler);
|
|
149
|
+
resolve(params);
|
|
150
|
+
};
|
|
151
|
+
this.on(event, handler);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
class CDPPage extends BasePage {
|
|
156
|
+
bridge;
|
|
157
|
+
_pageEnabled = false;
|
|
158
|
+
// Network capture state (mirrors extension/src/cdp.ts NetworkCaptureEntry shape)
|
|
159
|
+
_networkCapturing = false;
|
|
160
|
+
_networkCapturePattern = '';
|
|
161
|
+
_networkEntries = [];
|
|
162
|
+
_pendingRequests = new Map(); // requestId → index in _networkEntries
|
|
163
|
+
_pendingBodyFetches = new Set(); // track in-flight getResponseBody calls
|
|
164
|
+
_consoleMessages = [];
|
|
165
|
+
_consoleCapturing = false;
|
|
166
|
+
constructor(bridge) {
|
|
167
|
+
super();
|
|
168
|
+
this.bridge = bridge;
|
|
169
|
+
}
|
|
170
|
+
async goto(url, options) {
|
|
171
|
+
if (!this._pageEnabled) {
|
|
172
|
+
await this.bridge.send('Page.enable');
|
|
173
|
+
this._pageEnabled = true;
|
|
174
|
+
}
|
|
175
|
+
const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000).catch(() => { });
|
|
176
|
+
await this.bridge.send('Page.navigate', { url });
|
|
177
|
+
await loadPromise;
|
|
178
|
+
this._lastUrl = url;
|
|
179
|
+
if (options?.waitUntil !== 'none') {
|
|
180
|
+
const maxMs = options?.settleMs ?? 1000;
|
|
181
|
+
await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async evaluate(input, ...args) {
|
|
185
|
+
const expression = buildEvaluateExpression(input, args);
|
|
186
|
+
const result = await this.bridge.send('Runtime.evaluate', {
|
|
187
|
+
expression,
|
|
188
|
+
returnByValue: true,
|
|
189
|
+
awaitPromise: true,
|
|
190
|
+
});
|
|
191
|
+
if (result.exceptionDetails) {
|
|
192
|
+
throw new Error('Evaluate error: ' + (result.exceptionDetails.exception?.description || 'Unknown exception'));
|
|
193
|
+
}
|
|
194
|
+
return result.result?.value;
|
|
195
|
+
}
|
|
196
|
+
async getCookies(opts = {}) {
|
|
197
|
+
const result = await this.bridge.send('Network.getCookies', opts.url ? { urls: [opts.url] } : {});
|
|
198
|
+
const cookies = isRecord(result) && Array.isArray(result.cookies) ? result.cookies : [];
|
|
199
|
+
const domain = opts.domain;
|
|
200
|
+
return domain
|
|
201
|
+
? cookies.filter((cookie) => isCookie(cookie) && matchesCookieDomain(cookie.domain, domain))
|
|
202
|
+
: cookies;
|
|
203
|
+
}
|
|
204
|
+
async screenshot(options = {}) {
|
|
205
|
+
const fullPage = options.fullPage === true;
|
|
206
|
+
const overrideWidth = options.width && options.width > 0 ? Math.ceil(options.width) : undefined;
|
|
207
|
+
// height is ignored under fullPage so the captureBeyondViewport path stays unchanged for users who pass --height alongside --full-page.
|
|
208
|
+
const overrideHeight = !fullPage && options.height && options.height > 0 ? Math.ceil(options.height) : undefined;
|
|
209
|
+
const needsOverride = overrideWidth !== undefined || overrideHeight !== undefined;
|
|
210
|
+
if (needsOverride) {
|
|
211
|
+
if (overrideWidth !== undefined && fullPage) {
|
|
212
|
+
await this.bridge.send('Emulation.setDeviceMetricsOverride', {
|
|
213
|
+
mobile: false,
|
|
214
|
+
width: overrideWidth,
|
|
215
|
+
height: 0,
|
|
216
|
+
deviceScaleFactor: 1,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
let finalWidth = overrideWidth ?? 0;
|
|
220
|
+
let finalHeight = overrideHeight ?? 0;
|
|
221
|
+
if (fullPage) {
|
|
222
|
+
const metrics = await this.bridge.send('Page.getLayoutMetrics');
|
|
223
|
+
const m = isRecord(metrics) ? metrics : {};
|
|
224
|
+
const css = isRecord(m.cssContentSize) ? m.cssContentSize : undefined;
|
|
225
|
+
const fb = isRecord(m.contentSize) ? m.contentSize : undefined;
|
|
226
|
+
const size = css ?? fb;
|
|
227
|
+
if (size && typeof size.width === 'number' && typeof size.height === 'number') {
|
|
228
|
+
if (finalWidth === 0)
|
|
229
|
+
finalWidth = Math.ceil(size.width);
|
|
230
|
+
finalHeight = Math.ceil(size.height);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
await this.bridge.send('Emulation.setDeviceMetricsOverride', {
|
|
234
|
+
mobile: false,
|
|
235
|
+
width: finalWidth,
|
|
236
|
+
height: finalHeight,
|
|
237
|
+
deviceScaleFactor: 1,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const result = await this.bridge.send('Page.captureScreenshot', {
|
|
242
|
+
format: options.format ?? 'png',
|
|
243
|
+
quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
|
|
244
|
+
captureBeyondViewport: !needsOverride && fullPage,
|
|
245
|
+
});
|
|
246
|
+
const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
|
|
247
|
+
if (options.path) {
|
|
248
|
+
await saveBase64ToFile(base64, options.path);
|
|
249
|
+
}
|
|
250
|
+
return base64;
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (needsOverride) {
|
|
254
|
+
await this.bridge.send('Emulation.clearDeviceMetricsOverride').catch(() => { });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async startNetworkCapture(pattern = '') {
|
|
259
|
+
// Always update the filter pattern
|
|
260
|
+
this._networkCapturePattern = pattern;
|
|
261
|
+
// Reset state only on first start; avoid wiping entries if already capturing
|
|
262
|
+
if (!this._networkCapturing) {
|
|
263
|
+
this._networkEntries = [];
|
|
264
|
+
this._pendingRequests.clear();
|
|
265
|
+
this._pendingBodyFetches.clear();
|
|
266
|
+
await this.bridge.send('Network.enable');
|
|
267
|
+
// Step 1: Record request method/url on requestWillBeSent
|
|
268
|
+
this.bridge.on('Network.requestWillBeSent', (params) => {
|
|
269
|
+
const p = params;
|
|
270
|
+
if (!this._networkCapturePattern || p.request.url.includes(this._networkCapturePattern)) {
|
|
271
|
+
const idx = this._networkEntries.push({
|
|
272
|
+
url: p.request.url,
|
|
273
|
+
method: p.request.method,
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
}) - 1;
|
|
276
|
+
this._pendingRequests.set(p.requestId, idx);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
// Step 2: Fill in response metadata on responseReceived
|
|
280
|
+
this.bridge.on('Network.responseReceived', (params) => {
|
|
281
|
+
const p = params;
|
|
282
|
+
const idx = this._pendingRequests.get(p.requestId);
|
|
283
|
+
if (idx !== undefined) {
|
|
284
|
+
this._networkEntries[idx].responseStatus = p.response.status;
|
|
285
|
+
this._networkEntries[idx].responseContentType = p.response.mimeType || '';
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
// Step 3: Fetch body on loadingFinished (body is only reliably available after this)
|
|
289
|
+
this.bridge.on('Network.loadingFinished', (params) => {
|
|
290
|
+
const p = params;
|
|
291
|
+
const idx = this._pendingRequests.get(p.requestId);
|
|
292
|
+
if (idx !== undefined) {
|
|
293
|
+
const bodyFetch = this.bridge.send('Network.getResponseBody', { requestId: p.requestId }).then((result) => {
|
|
294
|
+
const r = result;
|
|
295
|
+
if (typeof r?.body === 'string') {
|
|
296
|
+
const fullSize = r.body.length;
|
|
297
|
+
const truncated = fullSize > CDP_RESPONSE_BODY_CAPTURE_LIMIT;
|
|
298
|
+
const body = truncated ? r.body.slice(0, CDP_RESPONSE_BODY_CAPTURE_LIMIT) : r.body;
|
|
299
|
+
this._networkEntries[idx].responsePreview = r.base64Encoded ? `base64:${body}` : body;
|
|
300
|
+
this._networkEntries[idx].responseBodyFullSize = fullSize;
|
|
301
|
+
this._networkEntries[idx].responseBodyTruncated = truncated;
|
|
302
|
+
}
|
|
303
|
+
}).catch((err) => {
|
|
304
|
+
// Body unavailable for some requests (e.g. uploads) — non-fatal
|
|
305
|
+
if (process.env.WEBCMD_VERBOSE) {
|
|
306
|
+
// eslint-disable-next-line no-console
|
|
307
|
+
console.error(`[cdp] getResponseBody failed for ${p.requestId}:`, err instanceof Error ? err.message : err);
|
|
308
|
+
}
|
|
309
|
+
}).finally(() => {
|
|
310
|
+
this._pendingBodyFetches.delete(bodyFetch);
|
|
311
|
+
});
|
|
312
|
+
this._pendingBodyFetches.add(bodyFetch);
|
|
313
|
+
this._pendingRequests.delete(p.requestId);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this._networkCapturing = true;
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
async readNetworkCapture() {
|
|
321
|
+
// Await all in-flight body fetches so entries have responsePreview populated
|
|
322
|
+
if (this._pendingBodyFetches.size > 0) {
|
|
323
|
+
await Promise.all([...this._pendingBodyFetches]);
|
|
324
|
+
}
|
|
325
|
+
const entries = [...this._networkEntries];
|
|
326
|
+
this._networkEntries = [];
|
|
327
|
+
return entries;
|
|
328
|
+
}
|
|
329
|
+
async consoleMessages(level = 'all') {
|
|
330
|
+
if (!this._consoleCapturing) {
|
|
331
|
+
await this.bridge.send('Runtime.enable');
|
|
332
|
+
this.bridge.on('Runtime.consoleAPICalled', (params) => {
|
|
333
|
+
const p = params;
|
|
334
|
+
const text = (p.args || []).map(a => a.value !== undefined ? String(a.value) : (a.description || '')).join(' ');
|
|
335
|
+
this._consoleMessages.push({ type: p.type, text, timestamp: Date.now() });
|
|
336
|
+
if (this._consoleMessages.length > 500)
|
|
337
|
+
this._consoleMessages.shift();
|
|
338
|
+
});
|
|
339
|
+
// Capture uncaught exceptions as error-level messages
|
|
340
|
+
this.bridge.on('Runtime.exceptionThrown', (params) => {
|
|
341
|
+
const p = params;
|
|
342
|
+
const desc = p.exceptionDetails?.exception?.description || p.exceptionDetails?.text || 'Unknown exception';
|
|
343
|
+
this._consoleMessages.push({ type: 'error', text: desc, timestamp: Date.now() });
|
|
344
|
+
if (this._consoleMessages.length > 500)
|
|
345
|
+
this._consoleMessages.shift();
|
|
346
|
+
});
|
|
347
|
+
this._consoleCapturing = true;
|
|
348
|
+
}
|
|
349
|
+
if (level === 'all')
|
|
350
|
+
return [...this._consoleMessages];
|
|
351
|
+
// 'error' level includes both console.error() and uncaught exceptions
|
|
352
|
+
if (level === 'error')
|
|
353
|
+
return this._consoleMessages.filter(m => m.type === 'error' || m.type === 'warning');
|
|
354
|
+
return this._consoleMessages.filter(m => m.type === level);
|
|
355
|
+
}
|
|
356
|
+
async tabs() {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
async selectTab(_target) {
|
|
360
|
+
// Not supported in direct CDP mode
|
|
361
|
+
}
|
|
362
|
+
async cdp(method, params = {}) {
|
|
363
|
+
return this.bridge.send(method, params);
|
|
364
|
+
}
|
|
365
|
+
async handleJavaScriptDialog(accept, promptText) {
|
|
366
|
+
await this.cdp('Page.handleJavaScriptDialog', {
|
|
367
|
+
accept,
|
|
368
|
+
...(promptText !== undefined && { promptText }),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async nativeClick(x, y) {
|
|
372
|
+
await this.cdp('Input.dispatchMouseEvent', {
|
|
373
|
+
type: 'mouseMoved',
|
|
374
|
+
x,
|
|
375
|
+
y,
|
|
376
|
+
});
|
|
377
|
+
await this.cdp('Input.dispatchMouseEvent', {
|
|
378
|
+
type: 'mousePressed',
|
|
379
|
+
x,
|
|
380
|
+
y,
|
|
381
|
+
button: 'left',
|
|
382
|
+
clickCount: 1,
|
|
383
|
+
});
|
|
384
|
+
await this.cdp('Input.dispatchMouseEvent', {
|
|
385
|
+
type: 'mouseReleased',
|
|
386
|
+
x,
|
|
387
|
+
y,
|
|
388
|
+
button: 'left',
|
|
389
|
+
clickCount: 1,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
async nativeType(text) {
|
|
393
|
+
await this.cdp('Input.insertText', { text });
|
|
394
|
+
}
|
|
395
|
+
async insertText(text) {
|
|
396
|
+
await this.nativeType(text);
|
|
397
|
+
}
|
|
398
|
+
async nativeKeyPress(key, modifiers = []) {
|
|
399
|
+
let modifierFlags = 0;
|
|
400
|
+
for (const mod of modifiers) {
|
|
401
|
+
if (mod === 'Alt')
|
|
402
|
+
modifierFlags |= 1;
|
|
403
|
+
if (mod === 'Ctrl' || mod === 'Control')
|
|
404
|
+
modifierFlags |= 2;
|
|
405
|
+
if (mod === 'Meta')
|
|
406
|
+
modifierFlags |= 4;
|
|
407
|
+
if (mod === 'Shift')
|
|
408
|
+
modifierFlags |= 8;
|
|
409
|
+
}
|
|
410
|
+
await this.cdp('Input.dispatchKeyEvent', {
|
|
411
|
+
type: 'keyDown',
|
|
412
|
+
key,
|
|
413
|
+
modifiers: modifierFlags,
|
|
414
|
+
});
|
|
415
|
+
await this.cdp('Input.dispatchKeyEvent', {
|
|
416
|
+
type: 'keyUp',
|
|
417
|
+
key,
|
|
418
|
+
modifiers: modifierFlags,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function isCookie(value) {
|
|
423
|
+
return isRecord(value)
|
|
424
|
+
&& typeof value.name === 'string'
|
|
425
|
+
&& typeof value.value === 'string'
|
|
426
|
+
&& typeof value.domain === 'string';
|
|
427
|
+
}
|
|
428
|
+
function matchesCookieDomain(cookieDomain, targetDomain) {
|
|
429
|
+
const normalizedCookieDomain = cookieDomain.replace(/^\./, '').toLowerCase();
|
|
430
|
+
const normalizedTargetDomain = targetDomain.replace(/^\./, '').toLowerCase();
|
|
431
|
+
return normalizedTargetDomain === normalizedCookieDomain
|
|
432
|
+
|| normalizedTargetDomain.endsWith(`.${normalizedCookieDomain}`);
|
|
433
|
+
}
|
|
434
|
+
function selectCDPTarget(targets) {
|
|
435
|
+
const preferredPattern = compilePreferredPattern(process.env.WEBCMD_CDP_TARGET);
|
|
436
|
+
const ranked = targets
|
|
437
|
+
.map((target, index) => ({ target, index, score: scoreCDPTarget(target, preferredPattern) }))
|
|
438
|
+
.filter(({ score }) => Number.isFinite(score))
|
|
439
|
+
.sort((a, b) => {
|
|
440
|
+
if (b.score !== a.score)
|
|
441
|
+
return b.score - a.score;
|
|
442
|
+
return a.index - b.index;
|
|
443
|
+
});
|
|
444
|
+
return ranked[0]?.target;
|
|
445
|
+
}
|
|
446
|
+
function scoreCDPTarget(target, preferredPattern) {
|
|
447
|
+
if (!target.webSocketDebuggerUrl)
|
|
448
|
+
return Number.NEGATIVE_INFINITY;
|
|
449
|
+
const type = (target.type ?? '').toLowerCase();
|
|
450
|
+
const url = (target.url ?? '').toLowerCase();
|
|
451
|
+
const title = (target.title ?? '').toLowerCase();
|
|
452
|
+
const haystack = `${title} ${url}`;
|
|
453
|
+
if (!haystack.trim() && !type)
|
|
454
|
+
return Number.NEGATIVE_INFINITY;
|
|
455
|
+
if (haystack.includes('devtools'))
|
|
456
|
+
return Number.NEGATIVE_INFINITY;
|
|
457
|
+
if (type === 'background_page' || type === 'service_worker')
|
|
458
|
+
return Number.NEGATIVE_INFINITY;
|
|
459
|
+
let score = 0;
|
|
460
|
+
if (preferredPattern && preferredPattern.test(haystack))
|
|
461
|
+
score += 1000;
|
|
462
|
+
if (type === 'app')
|
|
463
|
+
score += 120;
|
|
464
|
+
else if (type === 'webview')
|
|
465
|
+
score += 100;
|
|
466
|
+
else if (type === 'page')
|
|
467
|
+
score += 80;
|
|
468
|
+
else if (type === 'iframe')
|
|
469
|
+
score += 20;
|
|
470
|
+
if (url.startsWith('http://localhost') || url.startsWith('https://localhost'))
|
|
471
|
+
score += 90;
|
|
472
|
+
if (url.startsWith('file://'))
|
|
473
|
+
score += 60;
|
|
474
|
+
if (url.startsWith('http://127.0.0.1') || url.startsWith('https://127.0.0.1'))
|
|
475
|
+
score += 50;
|
|
476
|
+
if (url.startsWith('about:blank'))
|
|
477
|
+
score -= 120;
|
|
478
|
+
if (url === '' || url === 'about:blank')
|
|
479
|
+
score -= 40;
|
|
480
|
+
if (title && title !== 'devtools')
|
|
481
|
+
score += 25;
|
|
482
|
+
// Boost score for known Electron app names from the registry (builtin + user-defined)
|
|
483
|
+
const appNames = Object.values(getAllElectronApps()).map(a => (a.displayName ?? a.processName).toLowerCase());
|
|
484
|
+
for (const name of appNames) {
|
|
485
|
+
if (title.includes(name)) {
|
|
486
|
+
score += 120;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
for (const name of appNames) {
|
|
491
|
+
if (url.includes(name)) {
|
|
492
|
+
score += 100;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return score;
|
|
497
|
+
}
|
|
498
|
+
function compilePreferredPattern(raw) {
|
|
499
|
+
const value = raw?.trim();
|
|
500
|
+
if (!value)
|
|
501
|
+
return undefined;
|
|
502
|
+
return new RegExp(escapeRegExp(value.toLowerCase()));
|
|
503
|
+
}
|
|
504
|
+
function escapeRegExp(value) {
|
|
505
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
506
|
+
}
|
|
507
|
+
export const __test__ = {
|
|
508
|
+
selectCDPTarget,
|
|
509
|
+
scoreCDPTarget,
|
|
510
|
+
};
|
|
511
|
+
function fetchJsonDirect(url) {
|
|
512
|
+
return new Promise((resolve, reject) => {
|
|
513
|
+
const parsed = new URL(url);
|
|
514
|
+
const request = (parsed.protocol === 'https:' ? httpsRequest : httpRequest)(parsed, (res) => {
|
|
515
|
+
const statusCode = res.statusCode ?? 0;
|
|
516
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
517
|
+
res.resume();
|
|
518
|
+
reject(new Error(`Failed to fetch CDP targets: HTTP ${statusCode}`));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const chunks = [];
|
|
522
|
+
res.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
523
|
+
res.on('end', () => {
|
|
524
|
+
try {
|
|
525
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
request.on('error', reject);
|
|
533
|
+
request.setTimeout(10_000, () => request.destroy(new Error('Timed out fetching CDP targets')));
|
|
534
|
+
request.end();
|
|
535
|
+
});
|
|
536
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const { MockWebSocket } = vi.hoisted(() => {
|
|
3
|
+
class MockWebSocket {
|
|
4
|
+
static OPEN = 1;
|
|
5
|
+
readyState = 1;
|
|
6
|
+
handlers = new Map();
|
|
7
|
+
constructor(_url) {
|
|
8
|
+
queueMicrotask(() => this.emit('open'));
|
|
9
|
+
}
|
|
10
|
+
on(event, handler) {
|
|
11
|
+
const handlers = this.handlers.get(event) ?? [];
|
|
12
|
+
handlers.push(handler);
|
|
13
|
+
this.handlers.set(event, handlers);
|
|
14
|
+
}
|
|
15
|
+
send(_message) { }
|
|
16
|
+
close() {
|
|
17
|
+
this.readyState = 3;
|
|
18
|
+
}
|
|
19
|
+
emit(event, ...args) {
|
|
20
|
+
for (const handler of this.handlers.get(event) ?? []) {
|
|
21
|
+
handler(...args);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { MockWebSocket };
|
|
26
|
+
});
|
|
27
|
+
vi.mock('ws', () => ({
|
|
28
|
+
WebSocket: MockWebSocket,
|
|
29
|
+
}));
|
|
30
|
+
import { CDPBridge } from './cdp.js';
|
|
31
|
+
describe('CDPBridge cookies', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.unstubAllEnvs();
|
|
34
|
+
});
|
|
35
|
+
it('filters cookies by actual domain match instead of substring match', async () => {
|
|
36
|
+
vi.stubEnv('WEBCMD_CDP_ENDPOINT', 'ws://127.0.0.1:9222/devtools/page/1');
|
|
37
|
+
const bridge = new CDPBridge();
|
|
38
|
+
vi.spyOn(bridge, 'send').mockResolvedValue({
|
|
39
|
+
cookies: [
|
|
40
|
+
{ name: 'good', value: '1', domain: '.example.com' },
|
|
41
|
+
{ name: 'exact', value: '2', domain: 'example.com' },
|
|
42
|
+
{ name: 'bad', value: '3', domain: 'notexample.com' },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
const page = await bridge.connect();
|
|
46
|
+
const cookies = await page.getCookies({ domain: 'example.com' });
|
|
47
|
+
expect(cookies).toEqual([
|
|
48
|
+
{ name: 'good', value: '1', domain: '.example.com' },
|
|
49
|
+
{ name: 'exact', value: '2', domain: 'example.com' },
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
it('exposes native input helpers on direct CDP pages', async () => {
|
|
53
|
+
vi.stubEnv('WEBCMD_CDP_ENDPOINT', 'ws://127.0.0.1:9222/devtools/page/1');
|
|
54
|
+
const bridge = new CDPBridge();
|
|
55
|
+
const send = vi.spyOn(bridge, 'send').mockResolvedValue({});
|
|
56
|
+
const page = await bridge.connect();
|
|
57
|
+
send.mockClear();
|
|
58
|
+
expect(page.nativeType).toBeTypeOf('function');
|
|
59
|
+
expect(page.nativeKeyPress).toBeTypeOf('function');
|
|
60
|
+
expect(page.nativeClick).toBeTypeOf('function');
|
|
61
|
+
expect(page.handleJavaScriptDialog).toBeTypeOf('function');
|
|
62
|
+
expect(page.cdp).toBeTypeOf('function');
|
|
63
|
+
await page.nativeType('hello');
|
|
64
|
+
await page.nativeKeyPress('a', ['Ctrl']);
|
|
65
|
+
await page.nativeClick(10, 20);
|
|
66
|
+
await page.handleJavaScriptDialog(true, 'ok');
|
|
67
|
+
await page.cdp('Page.getLayoutMetrics', {});
|
|
68
|
+
expect(send.mock.calls).toEqual([
|
|
69
|
+
['Input.insertText', { text: 'hello' }],
|
|
70
|
+
['Input.dispatchKeyEvent', { type: 'keyDown', key: 'a', modifiers: 2 }],
|
|
71
|
+
['Input.dispatchKeyEvent', { type: 'keyUp', key: 'a', modifiers: 2 }],
|
|
72
|
+
['Input.dispatchMouseEvent', { type: 'mouseMoved', x: 10, y: 20 }],
|
|
73
|
+
['Input.dispatchMouseEvent', { type: 'mousePressed', x: 10, y: 20, button: 'left', clickCount: 1 }],
|
|
74
|
+
['Input.dispatchMouseEvent', { type: 'mouseReleased', x: 10, y: 20, button: 'left', clickCount: 1 }],
|
|
75
|
+
['Page.handleJavaScriptDialog', { accept: true, promptText: 'ok' }],
|
|
76
|
+
['Page.getLayoutMetrics', {}],
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compound-component expansion for high-agent-failure form controls.
|
|
3
|
+
*
|
|
4
|
+
* Agents burn turns on three recurring input categories because the raw
|
|
5
|
+
* attribute dump from `browser state` under-specifies them:
|
|
6
|
+
*
|
|
7
|
+
* - date / time / datetime-local / month / week — agents type
|
|
8
|
+
* free-form strings and the browser silently ignores mismatched formats.
|
|
9
|
+
* - select — the snapshot caps visible options at ~6; agents don't know
|
|
10
|
+
* the full option set, can't match by label, and waste turns clicking
|
|
11
|
+
* to open the dropdown just to read options.
|
|
12
|
+
* - file — the snapshot shows current filenames but not `accept` or
|
|
13
|
+
* `multiple`; agents re-upload or pick unsupported MIME types.
|
|
14
|
+
*
|
|
15
|
+
* `compoundInfoOf(el)` returns a structured JSON summary agents can rely
|
|
16
|
+
* on. Included in `browser find --css` envelope so the agent gets the
|
|
17
|
+
* rich view without extra round-trips.
|
|
18
|
+
*
|
|
19
|
+
* Emitted as a JS source string (`COMPOUND_INFO_JS`) so it can be inlined
|
|
20
|
+
* into the generated evaluate scripts under find / snapshot / eval.
|
|
21
|
+
*/
|
|
22
|
+
export type DateLikeControl = 'date' | 'time' | 'datetime-local' | 'month' | 'week';
|
|
23
|
+
export interface DateCompound {
|
|
24
|
+
control: DateLikeControl;
|
|
25
|
+
format: string;
|
|
26
|
+
current: string;
|
|
27
|
+
min?: string;
|
|
28
|
+
max?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface SelectOption {
|
|
31
|
+
label: string;
|
|
32
|
+
value: string;
|
|
33
|
+
selected: boolean;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface SelectCompound {
|
|
37
|
+
control: 'select';
|
|
38
|
+
multiple: boolean;
|
|
39
|
+
current: string | string[];
|
|
40
|
+
options: SelectOption[];
|
|
41
|
+
options_total: number;
|
|
42
|
+
}
|
|
43
|
+
export interface FileCompound {
|
|
44
|
+
control: 'file';
|
|
45
|
+
multiple: boolean;
|
|
46
|
+
current: string[];
|
|
47
|
+
accept?: string;
|
|
48
|
+
}
|
|
49
|
+
export type CompoundInfo = DateCompound | SelectCompound | FileCompound;
|
|
50
|
+
/** Max options included in a SelectCompound.options[]. Above this, `options_total` still reflects the true count. */
|
|
51
|
+
export declare const COMPOUND_SELECT_OPTIONS_CAP = 50;
|
|
52
|
+
/** Max characters per option label / file name. */
|
|
53
|
+
export declare const COMPOUND_LABEL_CAP = 80;
|
|
54
|
+
/**
|
|
55
|
+
* JavaScript source declaring `compoundInfoOf(el)`. Inlined into the JS
|
|
56
|
+
* emitted by `buildFindJs` (and any other evaluate script that needs the
|
|
57
|
+
* rich compound view). Returns a `CompoundInfo` object or `null`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const COMPOUND_INFO_JS = "\nfunction compoundInfoOf(el) {\n if (!el || !el.tagName) return null;\n const tag = el.tagName;\n const LABEL_CAP = 80;\n const OPTS_CAP = 50;\n if (tag === 'INPUT') {\n const type = (el.getAttribute('type') || 'text').toLowerCase();\n const FORMATS = {\n 'date': 'YYYY-MM-DD',\n 'time': 'HH:MM',\n 'datetime-local': 'YYYY-MM-DDTHH:MM',\n 'month': 'YYYY-MM',\n 'week': 'YYYY-W##',\n };\n if (FORMATS[type]) {\n const info = {\n control: type,\n format: FORMATS[type],\n current: (el.value == null ? '' : String(el.value)),\n };\n const min = el.getAttribute('min');\n if (min) info.min = min;\n const max = el.getAttribute('max');\n if (max) info.max = max;\n return info;\n }\n if (type === 'file') {\n const info = {\n control: 'file',\n multiple: !!el.multiple,\n current: [],\n };\n const accept = el.getAttribute('accept');\n if (accept) info.accept = accept;\n try {\n if (el.files && el.files.length) {\n for (let i = 0; i < el.files.length; i++) {\n const name = (el.files[i].name || '').slice(0, LABEL_CAP);\n info.current.push(name);\n }\n }\n } catch (_) {}\n return info;\n }\n return null;\n }\n if (tag === 'SELECT') {\n const multiple = !!el.multiple;\n const options = [];\n const selectedLabels = [];\n let total = 0;\n try {\n const opts = el.options || [];\n total = opts.length;\n // Walk ALL options so `current` reflects selections that sit beyond the\n // serialization cap. Only the first OPTS_CAP entries get pushed into\n // options[]; anything past the cap still contributes to selectedLabels\n // so agents see the true current state of big dropdowns.\n for (let i = 0; i < opts.length; i++) {\n const o = opts[i];\n const labelRaw = (o.label != null && o.label !== '') ? o.label : (o.text || '');\n const label = String(labelRaw).trim().slice(0, LABEL_CAP);\n if (i < OPTS_CAP) {\n const entry = { label: label, value: o.value, selected: !!o.selected };\n if (o.disabled) entry.disabled = true;\n options.push(entry);\n }\n if (o.selected) selectedLabels.push(label);\n }\n } catch (_) {}\n return {\n control: 'select',\n multiple: multiple,\n current: multiple ? selectedLabels : (selectedLabels[0] || ''),\n options: options,\n options_total: total,\n };\n }\n return null;\n}\n";
|