browxai 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/THIRD_PARTY_NOTICES.md +45 -0
- package/dist/cli/chrome.d.ts +1 -0
- package/dist/cli/chrome.js +130 -0
- package/dist/cli/command-registry.d.ts +15 -0
- package/dist/cli/command-registry.js +35 -0
- package/dist/cli/doctor-plugins.d.ts +18 -0
- package/dist/cli/doctor-plugins.js +338 -0
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.js +407 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +200 -0
- package/dist/cli/register-commands.d.ts +1 -0
- package/dist/cli/register-commands.js +22 -0
- package/dist/cli/serve.d.ts +14 -0
- package/dist/cli/serve.js +151 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +129 -0
- package/dist/engine/adapters/adb.d.ts +72 -0
- package/dist/engine/adapters/adb.js +200 -0
- package/dist/engine/adapters/android-cdp.d.ts +54 -0
- package/dist/engine/adapters/android-cdp.js +110 -0
- package/dist/engine/adapters/android.engine.d.ts +1 -0
- package/dist/engine/adapters/android.engine.js +31 -0
- package/dist/engine/adapters/chromium.engine.d.ts +1 -0
- package/dist/engine/adapters/chromium.engine.js +44 -0
- package/dist/engine/adapters/firefox.engine.d.ts +1 -0
- package/dist/engine/adapters/firefox.engine.js +43 -0
- package/dist/engine/adapters/playwright-chromium.d.ts +43 -0
- package/dist/engine/adapters/playwright-chromium.js +56 -0
- package/dist/engine/adapters/playwright-firefox.d.ts +52 -0
- package/dist/engine/adapters/playwright-firefox.js +97 -0
- package/dist/engine/adapters/playwright-webkit.d.ts +40 -0
- package/dist/engine/adapters/playwright-webkit.js +79 -0
- package/dist/engine/adapters/safari/bidi-client.d.ts +46 -0
- package/dist/engine/adapters/safari/bidi-client.js +130 -0
- package/dist/engine/adapters/safari/launch.d.ts +56 -0
- package/dist/engine/adapters/safari/launch.js +104 -0
- package/dist/engine/adapters/safari/webdriver-client.d.ts +102 -0
- package/dist/engine/adapters/safari/webdriver-client.js +175 -0
- package/dist/engine/adapters/safari.engine.d.ts +1 -0
- package/dist/engine/adapters/safari.engine.js +52 -0
- package/dist/engine/adapters/safaridriver-hybrid.d.ts +56 -0
- package/dist/engine/adapters/safaridriver-hybrid.js +127 -0
- package/dist/engine/adapters/webkit.engine.d.ts +1 -0
- package/dist/engine/adapters/webkit.engine.js +47 -0
- package/dist/engine/capabilities.d.ts +53 -0
- package/dist/engine/capabilities.js +122 -0
- package/dist/engine/capability-registry.d.ts +9 -0
- package/dist/engine/capability-registry.js +20 -0
- package/dist/engine/index.d.ts +18 -0
- package/dist/engine/index.js +14 -0
- package/dist/engine/register-engines.d.ts +5 -0
- package/dist/engine/register-engines.js +16 -0
- package/dist/engine/registry.d.ts +145 -0
- package/dist/engine/registry.js +67 -0
- package/dist/engine/select.d.ts +48 -0
- package/dist/engine/select.js +128 -0
- package/dist/engine/session-cdp.d.ts +13 -0
- package/dist/engine/session-cdp.js +22 -0
- package/dist/engine/tool-gate.d.ts +19 -0
- package/dist/engine/tool-gate.js +226 -0
- package/dist/engine/types.d.ts +71 -0
- package/dist/engine/types.js +16 -0
- package/dist/helper/bridge.d.ts +48 -0
- package/dist/helper/bridge.js +200 -0
- package/dist/helper/browx-page.d.ts +1 -0
- package/dist/helper/browx-page.js +47 -0
- package/dist/helper/overlay-hide.d.ts +9 -0
- package/dist/helper/overlay-hide.js +49 -0
- package/dist/helper/stealth.d.ts +10 -0
- package/dist/helper/stealth.js +88 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +15 -0
- package/dist/page/a11y.d.ts +81 -0
- package/dist/page/a11y.js +219 -0
- package/dist/page/action-substrate.d.ts +64 -0
- package/dist/page/action-substrate.js +118 -0
- package/dist/page/actionresult-blocks.d.ts +99 -0
- package/dist/page/actionresult-blocks.js +144 -0
- package/dist/page/actionresult-shape.d.ts +48 -0
- package/dist/page/actionresult-shape.js +155 -0
- package/dist/page/actionresult-types.d.ts +368 -0
- package/dist/page/actionresult-types.js +4 -0
- package/dist/page/actionresult.d.ts +4 -0
- package/dist/page/actionresult.js +299 -0
- package/dist/page/actions-probe.d.ts +32 -0
- package/dist/page/actions-probe.js +294 -0
- package/dist/page/actions-scroll.d.ts +40 -0
- package/dist/page/actions-scroll.js +53 -0
- package/dist/page/actions.d.ts +132 -0
- package/dist/page/actions.js +453 -0
- package/dist/page/archive-assets.d.ts +39 -0
- package/dist/page/archive-assets.js +187 -0
- package/dist/page/archive.d.ts +47 -0
- package/dist/page/archive.js +349 -0
- package/dist/page/asset-export.d.ts +122 -0
- package/dist/page/asset-export.js +376 -0
- package/dist/page/await_network.d.ts +16 -0
- package/dist/page/await_network.js +23 -0
- package/dist/page/bbox.d.ts +37 -0
- package/dist/page/bbox.js +115 -0
- package/dist/page/canvas-capture.d.ts +82 -0
- package/dist/page/canvas-capture.js +257 -0
- package/dist/page/canvas-diff.d.ts +51 -0
- package/dist/page/canvas-diff.js +131 -0
- package/dist/page/canvas-gesture.d.ts +53 -0
- package/dist/page/canvas-gesture.js +167 -0
- package/dist/page/canvas-transform.d.ts +96 -0
- package/dist/page/canvas-transform.js +150 -0
- package/dist/page/canvas.d.ts +8 -0
- package/dist/page/canvas.js +50 -0
- package/dist/page/capture-substrate.d.ts +111 -0
- package/dist/page/capture-substrate.js +139 -0
- package/dist/page/clipboard.d.ts +25 -0
- package/dist/page/clipboard.js +50 -0
- package/dist/page/clock.d.ts +36 -0
- package/dist/page/clock.js +167 -0
- package/dist/page/compose.d.ts +55 -0
- package/dist/page/compose.js +169 -0
- package/dist/page/console.d.ts +39 -0
- package/dist/page/console.js +73 -0
- package/dist/page/coverage.d.ts +97 -0
- package/dist/page/coverage.js +280 -0
- package/dist/page/dom-export.d.ts +41 -0
- package/dist/page/dom-export.js +193 -0
- package/dist/page/dom-walk.d.ts +91 -0
- package/dist/page/dom-walk.js +267 -0
- package/dist/page/dom_diff.d.ts +48 -0
- package/dist/page/dom_diff.js +121 -0
- package/dist/page/downloads.d.ts +80 -0
- package/dist/page/downloads.js +244 -0
- package/dist/page/drop-files.d.ts +78 -0
- package/dist/page/drop-files.js +310 -0
- package/dist/page/element-export-discovery.d.ts +64 -0
- package/dist/page/element-export-discovery.js +346 -0
- package/dist/page/element-export.d.ts +46 -0
- package/dist/page/element-export.js +251 -0
- package/dist/page/emulation-substrate.d.ts +53 -0
- package/dist/page/emulation-substrate.js +87 -0
- package/dist/page/emulation.d.ts +60 -0
- package/dist/page/emulation.js +162 -0
- package/dist/page/export-playwright-script.d.ts +47 -0
- package/dist/page/export-playwright-script.js +304 -0
- package/dist/page/extract-resolve.d.ts +22 -0
- package/dist/page/extract-resolve.js +341 -0
- package/dist/page/extract-schema.d.ts +20 -0
- package/dist/page/extract-schema.js +200 -0
- package/dist/page/extract-types.d.ts +127 -0
- package/dist/page/extract-types.js +8 -0
- package/dist/page/extract-warnings.d.ts +8 -0
- package/dist/page/extract-warnings.js +56 -0
- package/dist/page/extract.d.ts +9 -0
- package/dist/page/extract.js +174 -0
- package/dist/page/fill-form.d.ts +58 -0
- package/dist/page/fill-form.js +261 -0
- package/dist/page/find.d.ts +158 -0
- package/dist/page/find.js +470 -0
- package/dist/page/frames.d.ts +45 -0
- package/dist/page/frames.js +133 -0
- package/dist/page/generate-locator.d.ts +57 -0
- package/dist/page/generate-locator.js +136 -0
- package/dist/page/gestures.d.ts +128 -0
- package/dist/page/gestures.js +198 -0
- package/dist/page/har.d.ts +91 -0
- package/dist/page/har.js +174 -0
- package/dist/page/heap.d.ts +97 -0
- package/dist/page/heap.js +285 -0
- package/dist/page/inspect.d.ts +34 -0
- package/dist/page/inspect.js +75 -0
- package/dist/page/layout-thrash.d.ts +34 -0
- package/dist/page/layout-thrash.js +232 -0
- package/dist/page/learning.d.ts +21 -0
- package/dist/page/learning.js +84 -0
- package/dist/page/locator.d.ts +54 -0
- package/dist/page/locator.js +142 -0
- package/dist/page/memory-diff.d.ts +48 -0
- package/dist/page/memory-diff.js +105 -0
- package/dist/page/network-mask.d.ts +8 -0
- package/dist/page/network-mask.js +18 -0
- package/dist/page/network-playwright.d.ts +96 -0
- package/dist/page/network-playwright.js +353 -0
- package/dist/page/network-substrate-select.d.ts +18 -0
- package/dist/page/network-substrate-select.js +32 -0
- package/dist/page/network-substrate.d.ts +109 -0
- package/dist/page/network-substrate.js +161 -0
- package/dist/page/network-ws.d.ts +46 -0
- package/dist/page/network-ws.js +113 -0
- package/dist/page/network.d.ts +194 -0
- package/dist/page/network.js +415 -0
- package/dist/page/overflow-detect.d.ts +102 -0
- package/dist/page/overflow-detect.js +449 -0
- package/dist/page/pdf.d.ts +69 -0
- package/dist/page/pdf.js +109 -0
- package/dist/page/perf-audit-analysers.d.ts +40 -0
- package/dist/page/perf-audit-analysers.js +369 -0
- package/dist/page/perf-audit-runner.d.ts +20 -0
- package/dist/page/perf-audit-runner.js +195 -0
- package/dist/page/perf-audit-types.d.ts +41 -0
- package/dist/page/perf-audit-types.js +5 -0
- package/dist/page/perf-audit.d.ts +37 -0
- package/dist/page/perf-audit.js +377 -0
- package/dist/page/perf.d.ts +127 -0
- package/dist/page/perf.js +373 -0
- package/dist/page/plan.d.ts +192 -0
- package/dist/page/plan.js +308 -0
- package/dist/page/point_probe.d.ts +46 -0
- package/dist/page/point_probe.js +99 -0
- package/dist/page/recording.d.ts +67 -0
- package/dist/page/recording.js +172 -0
- package/dist/page/refs.d.ts +92 -0
- package/dist/page/refs.js +134 -0
- package/dist/page/regions.d.ts +23 -0
- package/dist/page/regions.js +32 -0
- package/dist/page/routes.d.ts +40 -0
- package/dist/page/routes.js +87 -0
- package/dist/page/safari-actions.d.ts +12 -0
- package/dist/page/safari-actions.js +144 -0
- package/dist/page/sample.d.ts +64 -0
- package/dist/page/sample.js +216 -0
- package/dist/page/screenshot-on.d.ts +51 -0
- package/dist/page/screenshot-on.js +150 -0
- package/dist/page/screenshot-save.d.ts +36 -0
- package/dist/page/screenshot-save.js +53 -0
- package/dist/page/screenshot-schedule.d.ts +50 -0
- package/dist/page/screenshot-schedule.js +155 -0
- package/dist/page/script-substrate.d.ts +32 -0
- package/dist/page/script-substrate.js +47 -0
- package/dist/page/seed-random.d.ts +45 -0
- package/dist/page/seed-random.js +144 -0
- package/dist/page/set-of-marks.d.ts +96 -0
- package/dist/page/set-of-marks.js +245 -0
- package/dist/page/shadow.d.ts +136 -0
- package/dist/page/shadow.js +400 -0
- package/dist/page/shortcut.d.ts +50 -0
- package/dist/page/shortcut.js +147 -0
- package/dist/page/snapshot-substrate-safari.d.ts +30 -0
- package/dist/page/snapshot-substrate-safari.js +84 -0
- package/dist/page/snapshot-substrate-select.d.ts +24 -0
- package/dist/page/snapshot-substrate-select.js +34 -0
- package/dist/page/snapshot-substrate.d.ts +58 -0
- package/dist/page/snapshot-substrate.js +135 -0
- package/dist/page/snapshot.d.ts +24 -0
- package/dist/page/snapshot.js +162 -0
- package/dist/page/solve-captcha.d.ts +76 -0
- package/dist/page/solve-captcha.js +286 -0
- package/dist/page/storage-substrate-types.d.ts +221 -0
- package/dist/page/storage-substrate-types.js +6 -0
- package/dist/page/storage-substrate.d.ts +215 -0
- package/dist/page/storage-substrate.js +280 -0
- package/dist/page/structural.d.ts +9 -0
- package/dist/page/structural.js +152 -0
- package/dist/page/substrate-bundle-safari.d.ts +8 -0
- package/dist/page/substrate-bundle-safari.js +42 -0
- package/dist/page/substrate-bundle.d.ts +6 -0
- package/dist/page/substrate-bundle.js +53 -0
- package/dist/page/text_search.d.ts +44 -0
- package/dist/page/text_search.js +90 -0
- package/dist/page/upload.d.ts +28 -0
- package/dist/page/upload.js +62 -0
- package/dist/page/verify.d.ts +63 -0
- package/dist/page/verify.js +451 -0
- package/dist/page/video.d.ts +115 -0
- package/dist/page/video.js +169 -0
- package/dist/page/visibility.d.ts +22 -0
- package/dist/page/visibility.js +94 -0
- package/dist/page/watch.d.ts +29 -0
- package/dist/page/watch.js +99 -0
- package/dist/page/workers.d.ts +126 -0
- package/dist/page/workers.js +490 -0
- package/dist/page/ws-interactive.d.ts +82 -0
- package/dist/page/ws-interactive.js +318 -0
- package/dist/plugin/cli.d.ts +45 -0
- package/dist/plugin/cli.js +496 -0
- package/dist/plugin/command-registry.d.ts +9 -0
- package/dist/plugin/command-registry.js +23 -0
- package/dist/plugin/depgraph.d.ts +37 -0
- package/dist/plugin/depgraph.js +186 -0
- package/dist/plugin/manifest.d.ts +182 -0
- package/dist/plugin/manifest.js +219 -0
- package/dist/plugin/package-manager.d.ts +22 -0
- package/dist/plugin/package-manager.js +40 -0
- package/dist/plugin/resolver.d.ts +85 -0
- package/dist/plugin/resolver.js +166 -0
- package/dist/plugin/runtime.d.ts +77 -0
- package/dist/plugin/runtime.js +402 -0
- package/dist/plugin/types.d.ts +113 -0
- package/dist/plugin/types.js +4 -0
- package/dist/policy/confirm.d.ts +76 -0
- package/dist/policy/confirm.js +162 -0
- package/dist/policy/origin.d.ts +17 -0
- package/dist/policy/origin.js +79 -0
- package/dist/sdk/client.d.ts +21 -0
- package/dist/sdk/client.js +174 -0
- package/dist/sdk/index.d.ts +32 -0
- package/dist/sdk/index.js +61 -0
- package/dist/sdk/plugin-types.d.ts +33 -0
- package/dist/sdk/plugin-types.js +22 -0
- package/dist/sdk/registry.d.ts +17 -0
- package/dist/sdk/registry.js +94 -0
- package/dist/sdk/socket-transport.d.ts +20 -0
- package/dist/sdk/socket-transport.js +90 -0
- package/dist/sdk/tool-types.d.ts +634 -0
- package/dist/sdk/tool-types.js +28 -0
- package/dist/sdk/transport-in-process.d.ts +21 -0
- package/dist/sdk/transport-in-process.js +44 -0
- package/dist/sdk/transport-registry.d.ts +19 -0
- package/dist/sdk/transport-registry.js +31 -0
- package/dist/sdk/transport-socket.d.ts +12 -0
- package/dist/sdk/transport-socket.js +77 -0
- package/dist/sdk/transport-stdio-child.d.ts +10 -0
- package/dist/sdk/transport-stdio-child.js +47 -0
- package/dist/sdk/transport.d.ts +10 -0
- package/dist/sdk/transport.js +35 -0
- package/dist/sdk/types.d.ts +176 -0
- package/dist/sdk/types.js +10 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +327 -0
- package/dist/session/artifacts.d.ts +52 -0
- package/dist/session/artifacts.js +177 -0
- package/dist/session/byob-attach.d.ts +26 -0
- package/dist/session/byob-attach.js +187 -0
- package/dist/session/byob.d.ts +8 -0
- package/dist/session/byob.js +20 -0
- package/dist/session/cache-storage.d.ts +100 -0
- package/dist/session/cache-storage.js +166 -0
- package/dist/session/device-emu.d.ts +149 -0
- package/dist/session/device-emu.js +545 -0
- package/dist/session/device.d.ts +14 -0
- package/dist/session/device.js +44 -0
- package/dist/session/dialog.d.ts +62 -0
- package/dist/session/dialog.js +164 -0
- package/dist/session/emulation.d.ts +69 -0
- package/dist/session/emulation.js +168 -0
- package/dist/session/extensions.d.ts +113 -0
- package/dist/session/extensions.js +237 -0
- package/dist/session/fs-picker.d.ts +144 -0
- package/dist/session/fs-picker.js +666 -0
- package/dist/session/idb-storage.d.ts +86 -0
- package/dist/session/idb-storage.js +229 -0
- package/dist/session/incognito.d.ts +3 -0
- package/dist/session/incognito.js +20 -0
- package/dist/session/launch-options.d.ts +41 -0
- package/dist/session/launch-options.js +200 -0
- package/dist/session/managed.d.ts +3 -0
- package/dist/session/managed.js +16 -0
- package/dist/session/metrics.d.ts +45 -0
- package/dist/session/metrics.js +75 -0
- package/dist/session/notification.d.ts +122 -0
- package/dist/session/notification.js +426 -0
- package/dist/session/permission.d.ts +144 -0
- package/dist/session/permission.js +600 -0
- package/dist/session/playwright-post-wire.d.ts +8 -0
- package/dist/session/playwright-post-wire.js +148 -0
- package/dist/session/policy-buffer.d.ts +21 -0
- package/dist/session/policy-buffer.js +47 -0
- package/dist/session/profile-snapshot.d.ts +11 -0
- package/dist/session/profile-snapshot.js +53 -0
- package/dist/session/registry.d.ts +365 -0
- package/dist/session/registry.js +98 -0
- package/dist/session/safari-post-wire.d.ts +8 -0
- package/dist/session/safari-post-wire.js +28 -0
- package/dist/session/safari-session.d.ts +10 -0
- package/dist/session/safari-session.js +39 -0
- package/dist/session/storage.d.ts +148 -0
- package/dist/session/storage.js +350 -0
- package/dist/session/types.d.ts +113 -0
- package/dist/session/types.js +5 -0
- package/dist/session/wedge.d.ts +15 -0
- package/dist/session/wedge.js +41 -0
- package/dist/tools/action-core-tools.d.ts +13 -0
- package/dist/tools/action-core-tools.js +156 -0
- package/dist/tools/action-form-tools.d.ts +12 -0
- package/dist/tools/action-form-tools.js +179 -0
- package/dist/tools/action-gesture-tools.d.ts +9 -0
- package/dist/tools/action-gesture-tools.js +115 -0
- package/dist/tools/action-history-tools.d.ts +8 -0
- package/dist/tools/action-history-tools.js +67 -0
- package/dist/tools/action-tool.d.ts +42 -0
- package/dist/tools/action-tool.js +58 -0
- package/dist/tools/action-tools.d.ts +20 -0
- package/dist/tools/action-tools.js +28 -0
- package/dist/tools/batch-act-tools.d.ts +10 -0
- package/dist/tools/batch-act-tools.js +276 -0
- package/dist/tools/batch-human-tools.d.ts +8 -0
- package/dist/tools/batch-human-tools.js +148 -0
- package/dist/tools/canvas-tools.d.ts +40 -0
- package/dist/tools/canvas-tools.js +368 -0
- package/dist/tools/capture-report-diagnostics-tools.d.ts +7 -0
- package/dist/tools/capture-report-diagnostics-tools.js +318 -0
- package/dist/tools/capture-report-element-export-tools.d.ts +8 -0
- package/dist/tools/capture-report-element-export-tools.js +197 -0
- package/dist/tools/capture-report-export-tools.d.ts +8 -0
- package/dist/tools/capture-report-export-tools.js +246 -0
- package/dist/tools/capture-report-marks-tools.d.ts +9 -0
- package/dist/tools/capture-report-marks-tools.js +221 -0
- package/dist/tools/capture-report-upload-tools.d.ts +8 -0
- package/dist/tools/capture-report-upload-tools.js +277 -0
- package/dist/tools/config-approval-tools.d.ts +8 -0
- package/dist/tools/config-approval-tools.js +166 -0
- package/dist/tools/deep-coverage-tools.d.ts +8 -0
- package/dist/tools/deep-coverage-tools.js +325 -0
- package/dist/tools/deep-determinism-tools.d.ts +8 -0
- package/dist/tools/deep-determinism-tools.js +276 -0
- package/dist/tools/deep-perf-tools.d.ts +19 -0
- package/dist/tools/deep-perf-tools.js +324 -0
- package/dist/tools/device-emulation-tools.d.ts +9 -0
- package/dist/tools/device-emulation-tools.js +137 -0
- package/dist/tools/extensions-batch-tools.d.ts +18 -0
- package/dist/tools/extensions-batch-tools.js +24 -0
- package/dist/tools/extensions-rebuild.d.ts +22 -0
- package/dist/tools/extensions-rebuild.js +208 -0
- package/dist/tools/extensions-tools.d.ts +2 -0
- package/dist/tools/extensions-tools.js +331 -0
- package/dist/tools/forms-fill-tools.d.ts +8 -0
- package/dist/tools/forms-fill-tools.js +109 -0
- package/dist/tools/forms-plan-tools.d.ts +7 -0
- package/dist/tools/forms-plan-tools.js +159 -0
- package/dist/tools/forms-recording-mode-tools.d.ts +8 -0
- package/dist/tools/forms-recording-mode-tools.js +71 -0
- package/dist/tools/forms-recording-tools.d.ts +14 -0
- package/dist/tools/forms-recording-tools.js +22 -0
- package/dist/tools/forms-refs-tools.d.ts +8 -0
- package/dist/tools/forms-refs-tools.js +90 -0
- package/dist/tools/gesture-coord-tools.d.ts +8 -0
- package/dist/tools/gesture-coord-tools.js +168 -0
- package/dist/tools/gesture-emulation-tools.d.ts +8 -0
- package/dist/tools/gesture-emulation-tools.js +135 -0
- package/dist/tools/gesture-network-tools.d.ts +17 -0
- package/dist/tools/gesture-network-tools.js +27 -0
- package/dist/tools/gesture-route-tools.d.ts +8 -0
- package/dist/tools/gesture-route-tools.js +142 -0
- package/dist/tools/gesture-websocket-tools.d.ts +8 -0
- package/dist/tools/gesture-websocket-tools.js +122 -0
- package/dist/tools/gesture-worker-tools.d.ts +9 -0
- package/dist/tools/gesture-worker-tools.js +200 -0
- package/dist/tools/host-build.d.ts +76 -0
- package/dist/tools/host-build.js +516 -0
- package/dist/tools/host.d.ts +287 -0
- package/dist/tools/host.js +1 -0
- package/dist/tools/input-tools.d.ts +10 -0
- package/dist/tools/input-tools.js +176 -0
- package/dist/tools/live-emulation-tools.d.ts +9 -0
- package/dist/tools/live-emulation-tools.js +353 -0
- package/dist/tools/plugin-runtime.d.ts +36 -0
- package/dist/tools/plugin-runtime.js +274 -0
- package/dist/tools/read-observe-buffer-tools.d.ts +9 -0
- package/dist/tools/read-observe-buffer-tools.js +385 -0
- package/dist/tools/read-observe-capture-tools.d.ts +12 -0
- package/dist/tools/read-observe-capture-tools.js +376 -0
- package/dist/tools/read-observe-dom-tools.d.ts +8 -0
- package/dist/tools/read-observe-dom-tools.js +308 -0
- package/dist/tools/read-observe-extract-tools.d.ts +8 -0
- package/dist/tools/read-observe-extract-tools.js +232 -0
- package/dist/tools/read-observe-verify-tools.d.ts +8 -0
- package/dist/tools/read-observe-verify-tools.js +316 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.js +58 -0
- package/dist/tools/secrets-captcha-tools.d.ts +9 -0
- package/dist/tools/secrets-captcha-tools.js +231 -0
- package/dist/tools/session-dialog-permission-tools.d.ts +9 -0
- package/dist/tools/session-dialog-permission-tools.js +287 -0
- package/dist/tools/session-lifecycle-tools.d.ts +8 -0
- package/dist/tools/session-lifecycle-tools.js +314 -0
- package/dist/tools/session-notification-device-tools.d.ts +9 -0
- package/dist/tools/session-notification-device-tools.js +156 -0
- package/dist/tools/session-policy-tools.d.ts +16 -0
- package/dist/tools/session-policy-tools.js +22 -0
- package/dist/tools/session-registry.d.ts +28 -0
- package/dist/tools/session-registry.js +427 -0
- package/dist/tools/storage-artifact-har-video-tools.d.ts +8 -0
- package/dist/tools/storage-artifact-har-video-tools.js +311 -0
- package/dist/tools/storage-cache-idb-tools.d.ts +8 -0
- package/dist/tools/storage-cache-idb-tools.js +347 -0
- package/dist/tools/storage-state-cookies-tools.d.ts +8 -0
- package/dist/tools/storage-state-cookies-tools.js +223 -0
- package/dist/tools/storage-tools.d.ts +17 -0
- package/dist/tools/storage-tools.js +25 -0
- package/dist/tools/storage-web-auth-tools.d.ts +10 -0
- package/dist/tools/storage-web-auth-tools.js +230 -0
- package/dist/tools/tool-metadata.d.ts +8 -0
- package/dist/tools/tool-metadata.js +185 -0
- package/dist/util/batch.d.ts +83 -0
- package/dist/util/batch.js +191 -0
- package/dist/util/capabilities.d.ts +504 -0
- package/dist/util/capabilities.js +254 -0
- package/dist/util/config-store.d.ts +103 -0
- package/dist/util/config-store.js +206 -0
- package/dist/util/config.d.ts +11 -0
- package/dist/util/config.js +28 -0
- package/dist/util/credentials.d.ts +136 -0
- package/dist/util/credentials.js +622 -0
- package/dist/util/deadline.d.ts +22 -0
- package/dist/util/deadline.js +62 -0
- package/dist/util/diagnostics.d.ts +161 -0
- package/dist/util/diagnostics.js +579 -0
- package/dist/util/egress-sanitiser.d.ts +29 -0
- package/dist/util/egress-sanitiser.js +52 -0
- package/dist/util/failure.d.ts +8 -0
- package/dist/util/failure.js +50 -0
- package/dist/util/flake-check.d.ts +109 -0
- package/dist/util/flake-check.js +342 -0
- package/dist/util/invariant.d.ts +25 -0
- package/dist/util/invariant.js +66 -0
- package/dist/util/logging.d.ts +6 -0
- package/dist/util/logging.js +12 -0
- package/dist/util/predicates.d.ts +62 -0
- package/dist/util/predicates.js +340 -0
- package/dist/util/secrets.d.ts +104 -0
- package/dist/util/secrets.js +219 -0
- package/dist/util/tokens.d.ts +6 -0
- package/dist/util/tokens.js +24 -0
- package/dist/util/url-sanitizer.d.ts +19 -0
- package/dist/util/url-sanitizer.js +70 -0
- package/dist/util/version.d.ts +2 -0
- package/dist/util/version.js +21 -0
- package/dist/util/workspace.d.ts +7 -0
- package/dist/util/workspace.js +22 -0
- package/package.json +120 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Per-session cumulative metrics. Piggybacks on the existing per-call
|
|
2
|
+
// `tokensEstimate` + call-timing data the dispatch wrapper already has in hand —
|
|
3
|
+
// no new instrumentation in tool handlers, no per-call disk writes. Read-only
|
|
4
|
+
// from the agent's side via the `session_metrics` tool.
|
|
5
|
+
//
|
|
6
|
+
// Pairs with `export_session_report`: that one bundles the session's QA evidence
|
|
7
|
+
// (url, console errors, recent network summary, named regions, live sessions);
|
|
8
|
+
// this one rolls up the session's TOOL-CALL EVIDENCE (how many calls, how
|
|
9
|
+
// expensive, what failed, what was denied) so an agent / consumer can audit
|
|
10
|
+
// dispatch behaviour without re-walking a transcript.
|
|
11
|
+
//
|
|
12
|
+
// Tracks five counters per tool name (callsByTool, durationMsByTool,
|
|
13
|
+
// errorsByTool) plus two scalars (tokensEstimateSum, capabilityDenials).
|
|
14
|
+
// `capabilityDenials` is intentionally not per-tool: the denial is a property
|
|
15
|
+
// of the capability config, not the tool — when an agent hits one, the fix
|
|
16
|
+
// is at the capability layer and the count alone is the useful signal.
|
|
17
|
+
export class SessionMetrics {
|
|
18
|
+
/** Wall-clock instant the session was created. Mirrored on the rolled-up
|
|
19
|
+
* result so the consumer can derive duration without a second tool call. */
|
|
20
|
+
startedAt;
|
|
21
|
+
/** Per-tool counters. Lazily created on first dispatch of each tool. */
|
|
22
|
+
byTool = new Map();
|
|
23
|
+
/** Sum of `tokensEstimate` across every dispatched call. The field is on the
|
|
24
|
+
* result envelope (set by every tool that wraps a body via the standard
|
|
25
|
+
* helper); we read it back here so a session-wide token budget is one
|
|
26
|
+
* lookup rather than re-walking each tool's transcript. */
|
|
27
|
+
tokensSum = 0;
|
|
28
|
+
/** Count of capability-denied dispatches across the whole session — the
|
|
29
|
+
* config-shape signal, see module header. */
|
|
30
|
+
denials = 0;
|
|
31
|
+
constructor(startedAt = Date.now()) {
|
|
32
|
+
this.startedAt = startedAt;
|
|
33
|
+
}
|
|
34
|
+
/** Record one dispatch. `durationMs` is the wall-clock latency the wrapper
|
|
35
|
+
* measured; `tokensEstimate` is the `tokensEstimate` field from the result
|
|
36
|
+
* envelope (or `undefined` if the result didn't carry one — e.g. an image-
|
|
37
|
+
* only response). Outcome `denied` means the gate refused before dispatch. */
|
|
38
|
+
record(tool, outcome, durationMs, tokensEstimate) {
|
|
39
|
+
const row = this.byTool.get(tool) ?? { count: 0, durationMs: 0, errors: 0 };
|
|
40
|
+
row.count += 1;
|
|
41
|
+
row.durationMs += Math.max(0, durationMs);
|
|
42
|
+
if (outcome === "error")
|
|
43
|
+
row.errors += 1;
|
|
44
|
+
this.byTool.set(tool, row);
|
|
45
|
+
if (outcome === "denied")
|
|
46
|
+
this.denials += 1;
|
|
47
|
+
if (typeof tokensEstimate === "number" && Number.isFinite(tokensEstimate)) {
|
|
48
|
+
this.tokensSum += tokensEstimate;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Snapshot the current rollup. The returned object is plain JSON — safe to
|
|
52
|
+
* serialise straight onto the `session_metrics` tool envelope. */
|
|
53
|
+
snapshot(now = Date.now()) {
|
|
54
|
+
const callsByTool = {};
|
|
55
|
+
const durationMsByTool = {};
|
|
56
|
+
const errorsByTool = {};
|
|
57
|
+
// Emit tool entries in insertion order so two snapshots taken back-to-back
|
|
58
|
+
// are stable (matters for diff-based assertions in tests).
|
|
59
|
+
for (const [tool, row] of this.byTool) {
|
|
60
|
+
callsByTool[tool] = row.count;
|
|
61
|
+
durationMsByTool[tool] = row.durationMs;
|
|
62
|
+
if (row.errors > 0)
|
|
63
|
+
errorsByTool[tool] = row.errors;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
callsByTool,
|
|
67
|
+
durationMsByTool,
|
|
68
|
+
errorsByTool,
|
|
69
|
+
tokensEstimateSum: this.tokensSum,
|
|
70
|
+
capabilityDenials: this.denials,
|
|
71
|
+
sessionStartedAt: new Date(this.startedAt).toISOString(),
|
|
72
|
+
sessionDurationMs: Math.max(0, now - this.startedAt),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { BrowserContext } from "playwright-core";
|
|
2
|
+
export type NotificationPolicyMode = "allow" | "deny" | "raise" | "ask-human";
|
|
3
|
+
/** Public, runtime-mutable shape. */
|
|
4
|
+
export interface NotificationPolicy {
|
|
5
|
+
mode: NotificationPolicyMode;
|
|
6
|
+
}
|
|
7
|
+
/** One captured `new Notification(...)` call, exposed on
|
|
8
|
+
* `ActionResult.notifications[]`. */
|
|
9
|
+
export interface NotificationRecord {
|
|
10
|
+
/** Notification title — first argument to the constructor. */
|
|
11
|
+
title: string;
|
|
12
|
+
/** Optional fields from the constructor's `options` bag (`NotificationOptions`).
|
|
13
|
+
* Only the small documented subset is captured (body / icon / tag) — the
|
|
14
|
+
* full spec has actions/data/badge/etc but those are rarely useful in
|
|
15
|
+
* observability and bloat the result envelope. */
|
|
16
|
+
body?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
tag?: string;
|
|
19
|
+
/** epoch ms — used by the action-window slice. */
|
|
20
|
+
timestamp: number;
|
|
21
|
+
/** origin of the page that constructed it; undefined when not parseable. */
|
|
22
|
+
origin?: string;
|
|
23
|
+
/** What the server actually did. `"raised"` means the wrapper threw AND
|
|
24
|
+
* the policy was `raise`, so the action will be marked failed. */
|
|
25
|
+
handledAs: "allowed" | "denied" | "raised" | "asked-human";
|
|
26
|
+
}
|
|
27
|
+
/** Hint emitted on `ActionResult.failure.hint` when `raise` mode fired.
|
|
28
|
+
* Stable, agent-facing string — referenced in docs/tool-reference.md. */
|
|
29
|
+
export declare const UNHANDLED_NOTIFICATION_HINT: string;
|
|
30
|
+
/** Mutable per-session state. The page-side check binding reads `current()`
|
|
31
|
+
* on every constructor call, so a `set_notification_policy` call takes effect
|
|
32
|
+
* on the very next construction without page reload. */
|
|
33
|
+
export declare class NotificationPolicyState {
|
|
34
|
+
private policy;
|
|
35
|
+
/** Bounded record ring (shared `PolicyRecordBuffer`; the hard cap so a chatty
|
|
36
|
+
* page can't grow this without bound). `NotificationRecord` carries its
|
|
37
|
+
* timestamp as `timestamp`, so the buffer reads it via an explicit extractor. */
|
|
38
|
+
private readonly records;
|
|
39
|
+
/** Contexts we've already installed the init-script + binding on. */
|
|
40
|
+
private wired;
|
|
41
|
+
constructor(initial?: NotificationPolicy, cap?: number);
|
|
42
|
+
/** Resolved policy snapshot. */
|
|
43
|
+
current(): NotificationPolicy;
|
|
44
|
+
set(next: NotificationPolicy): NotificationPolicy;
|
|
45
|
+
/** Append a record. Caps the buffer at `cap`. */
|
|
46
|
+
record(rec: NotificationRecord): void;
|
|
47
|
+
/** Slice records with `timestamp >= since`. Used by the action-window. */
|
|
48
|
+
since(since: number): NotificationRecord[];
|
|
49
|
+
/** True if any record in `[since, now]` was handled in `raise` mode. */
|
|
50
|
+
raisedSince(since: number): boolean;
|
|
51
|
+
/** Has this context already been wired? Idempotent install guard. */
|
|
52
|
+
hasContext(c: BrowserContext): boolean;
|
|
53
|
+
/** Mark a context as wired. */
|
|
54
|
+
markContext(c: BrowserContext): void;
|
|
55
|
+
}
|
|
56
|
+
/** Parse the spec's compact string form for the top-level mode, or accept the
|
|
57
|
+
* object form. Idempotent. */
|
|
58
|
+
export declare function parseNotificationPolicyArg(v: string | NotificationPolicy | undefined): NotificationPolicy;
|
|
59
|
+
/** Bridge callback type. The server-side binding wires this to await-human
|
|
60
|
+
* (when the policy is `ask-human`); the page-side wrapper script consults it
|
|
61
|
+
* before deciding whether to call through. Returns the decision the wrapper
|
|
62
|
+
* should enact: `"allow"` calls through, `"deny"` throws NotAllowedError. */
|
|
63
|
+
export type NotificationAskHandler = (payload: {
|
|
64
|
+
title: string;
|
|
65
|
+
body?: string;
|
|
66
|
+
icon?: string;
|
|
67
|
+
tag?: string;
|
|
68
|
+
origin?: string;
|
|
69
|
+
}) => Promise<"allow" | "deny">;
|
|
70
|
+
/** Init script that wraps the page-side `Notification` constructor. Stringified
|
|
71
|
+
* so it can be passed to `addInitScript` and `page.evaluate`. Browser-only JS
|
|
72
|
+
* — no TS syntax. Re-injected on `framenavigated` (idempotent: guards on
|
|
73
|
+
* `window.__browx_notification_installed`).
|
|
74
|
+
*
|
|
75
|
+
* The wrapper consults `window.__browx_notification_check({title, body, …})`
|
|
76
|
+
* (an exposeBinding callable from page context) — it returns `"allow" |
|
|
77
|
+
* "deny"`. The server's binding implementation records the construction +
|
|
78
|
+
* (for `ask-human`) blocks on the bridge before answering.
|
|
79
|
+
*
|
|
80
|
+
* IMPORTANT: this script does NOT touch `Notification.requestPermission` or
|
|
81
|
+
* the `Notification.permission` static getter — those are owned by
|
|
82
|
+
* `session/permission.ts` (permission_policy). Coordination is by-
|
|
83
|
+
* construction: each script owns disjoint surface area. */
|
|
84
|
+
export declare const NOTIFICATION_PAGE_SCRIPT = "(() => {\n if (window.__browx_notification_installed) return;\n if (typeof Notification === \"undefined\") return;\n window.__browx_notification_installed = true;\n\n var OrigNotification = Notification;\n\n function check(payload) {\n try {\n if (typeof window.__browx_notification_check === \"function\") {\n return Promise.resolve(window.__browx_notification_check(JSON.stringify(payload)));\n }\n } catch (_) {}\n return Promise.resolve(\"allow\");\n }\n function notAllowed(msg) {\n var e = new Error(msg || \"notification denied by browxai notificationPolicy\");\n try { e.name = \"NotAllowedError\"; } catch (_) {}\n return e;\n }\n\n // The constructed instance is a plain object whose prototype is set to\n // `OrigNotification.prototype` AFTER own-property assignment, so\n // accessor-only props on the platform prototype (`title`, `body`, etc.)\n // don't intercept our `this.title = ...` writes. (Setting them via\n // assignment with the prototype already in place throws TypeError in\n // headless Chromium \u2014 `Notification.prototype.title` is getter-only.)\n function ProxyNotification(title, options) {\n var safeTitle = String(title);\n var payload = {\n title: safeTitle,\n body: (options && options.body) || undefined,\n icon: (options && options.icon) || undefined,\n tag: (options && options.tag) || undefined,\n origin: location.origin,\n };\n\n // SYNC throw timing \u2014 read the pre-seeded decision hint. Spec requires\n // `new Notification(...)` to throw synchronously on failure. The async\n // `check()` below still records the call (and does the ask-human dance);\n // the sync hint is purely for the throw timing.\n var syncDecision = (typeof window.__browx_notification_sync_decision === \"string\")\n ? window.__browx_notification_sync_decision\n : \"allow\";\n if (syncDecision === \"deny\" || syncDecision === \"raise\") {\n // Still record the attempt before throwing.\n try { check(payload); } catch (_) {}\n throw notAllowed(syncDecision === \"raise\"\n ? \"notification raised \u2014 set notificationPolicy\"\n : \"Notification denied by browxai notificationPolicy\");\n }\n\n // Build the stub-as-this. Own data properties first; THEN set the\n // prototype so getter-only inherited accessors don't intercept writes.\n var listeners = {};\n var realRef = null;\n var pendingClose = false;\n Object.defineProperty(this, \"title\", { value: safeTitle, writable: true, configurable: true, enumerable: true });\n Object.defineProperty(this, \"body\", { value: (options && options.body) || \"\", writable: true, configurable: true, enumerable: true });\n Object.defineProperty(this, \"icon\", { value: (options && options.icon) || \"\", writable: true, configurable: true, enumerable: true });\n Object.defineProperty(this, \"tag\", { value: (options && options.tag) || \"\", writable: true, configurable: true, enumerable: true });\n Object.defineProperty(this, \"data\", { value: (options && options.data) !== undefined ? options.data : null, writable: true, configurable: true, enumerable: true });\n var self = this;\n Object.defineProperty(this, \"close\", { value: function () {\n if (realRef) { try { realRef.close(); } catch (_) {} return; }\n pendingClose = true;\n }, writable: true, configurable: true });\n Object.defineProperty(this, \"addEventListener\", { value: function (ev, cb) {\n (listeners[ev] = listeners[ev] || []).push(cb);\n if (realRef && realRef.addEventListener) { try { realRef.addEventListener(ev, cb); } catch (_) {} }\n }, writable: true, configurable: true });\n Object.defineProperty(this, \"removeEventListener\", { value: function (ev, cb) {\n var arr = listeners[ev]; if (!arr) return;\n var i = arr.indexOf(cb); if (i >= 0) arr.splice(i, 1);\n if (realRef && realRef.removeEventListener) { try { realRef.removeEventListener(ev, cb); } catch (_) {} }\n }, writable: true, configurable: true });\n\n // ask-human / allow: dispatch the policy check + (if allowed) construct\n // the real native Notification and route the page's listeners to it.\n check(payload).then(function (decision) {\n if (decision !== \"allow\") return;\n try {\n var real = new OrigNotification(safeTitle, options || {});\n realRef = real;\n for (var ev in listeners) {\n if (!Object.prototype.hasOwnProperty.call(listeners, ev)) continue;\n for (var i = 0; i < listeners[ev].length; i++) {\n try { real.addEventListener(ev, listeners[ev][i]); } catch (_) {}\n }\n }\n if (pendingClose) { try { real.close(); } catch (_) {} }\n } catch (_) {\n // Browser refused (e.g. `Notification.permission === \"denied\"`\n // because permission_policy denied). The stub remains a no-op,\n // matching the deny branch.\n }\n });\n\n void self; // referenced via closure above\n }\n // Use a fresh prototype object \u2014 NOT `OrigNotification.prototype`, whose\n // accessor-only properties (`title`, `body`, `tag`, etc.) would intercept\n // our writes on `this` via the prototype chain (`TypeError: Cannot set\n // property title of #<Notification> which has only a getter` in headless\n // Chromium). Trade-off: `instanceof Notification` returns false for our\n // stub; apps rarely runtime-check that, and the alternative (overriding\n // the platform prototype's accessors) is messier + version-fragile.\n ProxyNotification.prototype = {};\n // Preserve the static surface \u2014 permission_policy owns these. Forward\n // every static read/write to the original constructor so the existing\n // `permission_policy` wrapper script still wraps `requestPermission` /\n // observes `permission` unchanged.\n try {\n Object.defineProperty(ProxyNotification, \"permission\", {\n get: function () { return OrigNotification.permission; },\n configurable: true,\n });\n } catch (_) {}\n ProxyNotification.requestPermission = function () {\n return OrigNotification.requestPermission.apply(OrigNotification, arguments);\n };\n try { ProxyNotification.maxActions = OrigNotification.maxActions; } catch (_) {}\n\n try {\n // Replace the global. Some browsers refuse to delete `Notification` on\n // `window` (it's a configurable: false property in newer specs); fall\n // back to a defineProperty assignment if direct assignment is silent.\n window.Notification = ProxyNotification;\n if (window.Notification !== ProxyNotification) {\n Object.defineProperty(window, \"Notification\", {\n value: ProxyNotification, writable: true, configurable: true,\n });\n }\n } catch (_) {}\n})();";
|
|
85
|
+
/** Server-side wire-up. Installs:
|
|
86
|
+
* - `__browx_notification_check` exposeBinding: page-side records +
|
|
87
|
+
* ask-human resolves to allow/deny via the bridge.
|
|
88
|
+
* - The page-side init script (above), re-injected by Playwright on every
|
|
89
|
+
* new document via `addInitScript`.
|
|
90
|
+
* - Seeds the SYNCHRONOUS decision hint
|
|
91
|
+
* (`window.__browx_notification_sync_decision`) on every wired page so
|
|
92
|
+
* the constructor wrapper can throw without awaiting a binding round-
|
|
93
|
+
* trip; refreshed on `set_notification_policy` via
|
|
94
|
+
* `propagateSyncDecision`.
|
|
95
|
+
*
|
|
96
|
+
* Idempotent on the same context (the state's `WeakSet<BrowserContext>` guard).
|
|
97
|
+
* Errors during install are logged and swallowed — when bindings fail the
|
|
98
|
+
* wrapper falls back to call-through (browser default).
|
|
99
|
+
*/
|
|
100
|
+
export declare function attachNotificationPolicy(context: BrowserContext, state: NotificationPolicyState, askHandler: NotificationAskHandler): Promise<void>;
|
|
101
|
+
/** Compute the sync decision the constructor wrapper inspects to know
|
|
102
|
+
* whether to throw synchronously. `allow` and `ask-human` BOTH yield
|
|
103
|
+
* `"allow"` here: ask-human's constructor surface is non-throwing (we
|
|
104
|
+
* return the stub and only dispatch the real notification once the human
|
|
105
|
+
* answers). `deny` and `raise` throw at construction time. */
|
|
106
|
+
export declare function syncDecisionFor(mode: NotificationPolicyMode): "allow" | "deny" | "raise";
|
|
107
|
+
/** Push the current policy's sync decision to every live page in the context
|
|
108
|
+
* AND register an additional init-script so future new documents see the
|
|
109
|
+
* fresh value. Called from `set_notification_policy` so a runtime mode flip
|
|
110
|
+
* takes effect on the very next constructor call without page reload.
|
|
111
|
+
*
|
|
112
|
+
* Init-scripts accumulate in Playwright (one per call); the seed is ~80
|
|
113
|
+
* bytes so even hundreds of flips are negligible. The constructor wrapper
|
|
114
|
+
* reads `window.__browx_notification_sync_decision` at each call, so the
|
|
115
|
+
* most-recently-evaluated seed wins. */
|
|
116
|
+
export declare function propagateSyncDecision(context: BrowserContext, state: NotificationPolicyState): Promise<void>;
|
|
117
|
+
/** Read-side: snapshot the current policy + recent records for the session.
|
|
118
|
+
* Used by tests and (potentially) a future `notification_state` tool. */
|
|
119
|
+
export declare function readNotifications(state: NotificationPolicyState, since?: number): {
|
|
120
|
+
policy: NotificationPolicy;
|
|
121
|
+
records: NotificationRecord[];
|
|
122
|
+
};
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// Per-session notification policy. Sibling of `permission_policy`. Plugs the
|
|
2
|
+
// `new Notification(title, opts)` blind spot.
|
|
3
|
+
//
|
|
4
|
+
// Why a separate policy from `permission_policy.notifications`:
|
|
5
|
+
//
|
|
6
|
+
// - `permission_policy.notifications` governs the W3C *permission* check —
|
|
7
|
+
// `Notification.requestPermission()` and the `Notification.permission`
|
|
8
|
+
// state-getter. That tells the page whether it MAY display notifications.
|
|
9
|
+
// - `notification_policy` governs the *constructor invocation* —
|
|
10
|
+
// `new Notification(title, opts)`. The constructor only succeeds when
|
|
11
|
+
// permission is granted (browser-policy), but its construction is the
|
|
12
|
+
// observability event: it's what the page actually does when it tries to
|
|
13
|
+
// notify the human. Pre-v0.5.0 browxai had no visibility into these
|
|
14
|
+
// calls; an action that fired three notifications was indistinguishable
|
|
15
|
+
// from one that fired zero.
|
|
16
|
+
//
|
|
17
|
+
// The two policies compose:
|
|
18
|
+
//
|
|
19
|
+
// - `permission_policy.notifications: "allow"` + `notification_policy: "allow"` →
|
|
20
|
+
// the page can construct + display, every construct is captured on
|
|
21
|
+
// `ActionResult.notifications[]`.
|
|
22
|
+
// - `permission_policy.notifications: "deny"` + `notification_policy: "allow"` →
|
|
23
|
+
// the page sees `Notification.permission === "denied"` and most apps don't
|
|
24
|
+
// call the constructor at all; but if they do, the constructor still throws
|
|
25
|
+
// a `NotAllowedError` (browser-policy) and we capture the attempted call.
|
|
26
|
+
// - `permission_policy.notifications: "allow"` + `notification_policy: "deny"` →
|
|
27
|
+
// the constructor throws `NotAllowedError` (our policy) before the OS-level
|
|
28
|
+
// notification fires. The page sees permission is granted but the
|
|
29
|
+
// constructor surface refuses. Use when you want to observe + suppress.
|
|
30
|
+
//
|
|
31
|
+
// Modes mirror `permission_policy`'s posture:
|
|
32
|
+
//
|
|
33
|
+
// - "allow" — DEFAULT (browser default). Constructor proceeds normally;
|
|
34
|
+
// the OS displays per its own settings. Every call captured
|
|
35
|
+
// as `handledAs:"allowed"`. Matches the browser's
|
|
36
|
+
// pre-instrumentation behaviour so adopters can turn this
|
|
37
|
+
// on without breaking apps that expect notifications.
|
|
38
|
+
// - "deny" — Constructor throws `NotAllowedError` (the same exception
|
|
39
|
+
// the browser raises when permission is denied). Recorded
|
|
40
|
+
// as `handledAs:"denied"`. Use to suppress OS notifications
|
|
41
|
+
// while still observing what the page would have shown.
|
|
42
|
+
// - "raise" — Constructor throws + recorded as `handledAs:"raised"`.
|
|
43
|
+
// The next `ActionResult` flips `ok:false` with a stable
|
|
44
|
+
// hint pointing at `set_notification_policy`. Symmetric to
|
|
45
|
+
// `permission_policy`'s `raise` — useful when an agent
|
|
46
|
+
// wants notifications to be a hard signal that the action
|
|
47
|
+
// triggered an unexpected user-facing event.
|
|
48
|
+
// - "ask-human" — Constructor blocks on `await_human({kind:"confirm"})`
|
|
49
|
+
// (the `__browx.confirm(true|false)` mechanism) and
|
|
50
|
+
// proceeds or throws per the human's answer. The
|
|
51
|
+
// constructor call returns synchronously in the browser
|
|
52
|
+
// spec, so we serialise the await via the same
|
|
53
|
+
// page-side promise pattern as `permission_policy`.
|
|
54
|
+
// NOTE: the constructor surface in the page must observe a
|
|
55
|
+
// synchronous return from `new Notification(...)`. Our
|
|
56
|
+
// wrapper returns a stub that satisfies the typeof check,
|
|
57
|
+
// but the actual native notification is only fired *after*
|
|
58
|
+
// the human-decision resolves — so apps that read
|
|
59
|
+
// `notification.close()` immediately will observe a no-op
|
|
60
|
+
// stub. Documented in the docs/tool-reference.md entry.
|
|
61
|
+
//
|
|
62
|
+
// Init-script wraps the global `Notification` constructor (and preserves the
|
|
63
|
+
// static `requestPermission` / `permission` getters so the `permission_policy`
|
|
64
|
+
// wrappers — already injected by `session/permission.ts` — keep working
|
|
65
|
+
// untouched). The two policies compose; coordination is by-construction:
|
|
66
|
+
// `permission_policy` only touches `Notification.requestPermission`, this
|
|
67
|
+
// module only touches `new Notification(...)`.
|
|
68
|
+
import { log } from "../util/logging.js";
|
|
69
|
+
import { PolicyRecordBuffer } from "./policy-buffer.js";
|
|
70
|
+
/** Hint emitted on `ActionResult.failure.hint` when `raise` mode fired.
|
|
71
|
+
* Stable, agent-facing string — referenced in docs/tool-reference.md. */
|
|
72
|
+
export const UNHANDLED_NOTIFICATION_HINT = "unhandled notification — set notificationPolicy (open_session/set_notification_policy) " +
|
|
73
|
+
'to "allow", "deny", or "ask-human" before driving an action that may construct ' +
|
|
74
|
+
"a Notification. The constructor was rejected page-side (NotAllowedError) so the page " +
|
|
75
|
+
"is not deadlocked, but the app effect is the deny branch.";
|
|
76
|
+
/** Mutable per-session state. The page-side check binding reads `current()`
|
|
77
|
+
* on every constructor call, so a `set_notification_policy` call takes effect
|
|
78
|
+
* on the very next construction without page reload. */
|
|
79
|
+
export class NotificationPolicyState {
|
|
80
|
+
policy;
|
|
81
|
+
/** Bounded record ring (shared `PolicyRecordBuffer`; the hard cap so a chatty
|
|
82
|
+
* page can't grow this without bound). `NotificationRecord` carries its
|
|
83
|
+
* timestamp as `timestamp`, so the buffer reads it via an explicit extractor. */
|
|
84
|
+
records;
|
|
85
|
+
/** Contexts we've already installed the init-script + binding on. */
|
|
86
|
+
wired = new WeakSet();
|
|
87
|
+
constructor(initial = { mode: "allow" }, cap = 200) {
|
|
88
|
+
this.policy = normalise(initial);
|
|
89
|
+
this.records = new PolicyRecordBuffer(cap, (r) => r.timestamp);
|
|
90
|
+
}
|
|
91
|
+
/** Resolved policy snapshot. */
|
|
92
|
+
current() {
|
|
93
|
+
return { mode: this.policy.mode };
|
|
94
|
+
}
|
|
95
|
+
set(next) {
|
|
96
|
+
this.policy = normalise(next);
|
|
97
|
+
return this.current();
|
|
98
|
+
}
|
|
99
|
+
/** Append a record. Caps the buffer at `cap`. */
|
|
100
|
+
record(rec) {
|
|
101
|
+
this.records.record(rec);
|
|
102
|
+
}
|
|
103
|
+
/** Slice records with `timestamp >= since`. Used by the action-window. */
|
|
104
|
+
since(since) {
|
|
105
|
+
return this.records.since(since);
|
|
106
|
+
}
|
|
107
|
+
/** True if any record in `[since, now]` was handled in `raise` mode. */
|
|
108
|
+
raisedSince(since) {
|
|
109
|
+
return this.records.matchedSince(since, (r) => r.handledAs === "raised");
|
|
110
|
+
}
|
|
111
|
+
/** Has this context already been wired? Idempotent install guard. */
|
|
112
|
+
hasContext(c) {
|
|
113
|
+
return this.wired.has(c);
|
|
114
|
+
}
|
|
115
|
+
/** Mark a context as wired. */
|
|
116
|
+
markContext(c) {
|
|
117
|
+
this.wired.add(c);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function normalise(p) {
|
|
121
|
+
if (!isPolicyMode(p.mode)) {
|
|
122
|
+
throw new Error(`notificationPolicy: invalid mode "${String(p.mode)}" — expected "allow" | "deny" | "raise" | "ask-human"`);
|
|
123
|
+
}
|
|
124
|
+
return { mode: p.mode };
|
|
125
|
+
}
|
|
126
|
+
function isPolicyMode(m) {
|
|
127
|
+
return m === "allow" || m === "deny" || m === "raise" || m === "ask-human";
|
|
128
|
+
}
|
|
129
|
+
/** Parse the spec's compact string form for the top-level mode, or accept the
|
|
130
|
+
* object form. Idempotent. */
|
|
131
|
+
export function parseNotificationPolicyArg(v) {
|
|
132
|
+
if (!v)
|
|
133
|
+
return { mode: "allow" };
|
|
134
|
+
if (typeof v === "object")
|
|
135
|
+
return normalise(v);
|
|
136
|
+
if (isPolicyMode(v))
|
|
137
|
+
return { mode: v };
|
|
138
|
+
throw new Error(`notificationPolicy: invalid value "${v}" — expected "allow" | "deny" | "raise" | "ask-human"`);
|
|
139
|
+
}
|
|
140
|
+
/** Init script that wraps the page-side `Notification` constructor. Stringified
|
|
141
|
+
* so it can be passed to `addInitScript` and `page.evaluate`. Browser-only JS
|
|
142
|
+
* — no TS syntax. Re-injected on `framenavigated` (idempotent: guards on
|
|
143
|
+
* `window.__browx_notification_installed`).
|
|
144
|
+
*
|
|
145
|
+
* The wrapper consults `window.__browx_notification_check({title, body, …})`
|
|
146
|
+
* (an exposeBinding callable from page context) — it returns `"allow" |
|
|
147
|
+
* "deny"`. The server's binding implementation records the construction +
|
|
148
|
+
* (for `ask-human`) blocks on the bridge before answering.
|
|
149
|
+
*
|
|
150
|
+
* IMPORTANT: this script does NOT touch `Notification.requestPermission` or
|
|
151
|
+
* the `Notification.permission` static getter — those are owned by
|
|
152
|
+
* `session/permission.ts` (permission_policy). Coordination is by-
|
|
153
|
+
* construction: each script owns disjoint surface area. */
|
|
154
|
+
export const NOTIFICATION_PAGE_SCRIPT = `(() => {
|
|
155
|
+
if (window.__browx_notification_installed) return;
|
|
156
|
+
if (typeof Notification === "undefined") return;
|
|
157
|
+
window.__browx_notification_installed = true;
|
|
158
|
+
|
|
159
|
+
var OrigNotification = Notification;
|
|
160
|
+
|
|
161
|
+
function check(payload) {
|
|
162
|
+
try {
|
|
163
|
+
if (typeof window.__browx_notification_check === "function") {
|
|
164
|
+
return Promise.resolve(window.__browx_notification_check(JSON.stringify(payload)));
|
|
165
|
+
}
|
|
166
|
+
} catch (_) {}
|
|
167
|
+
return Promise.resolve("allow");
|
|
168
|
+
}
|
|
169
|
+
function notAllowed(msg) {
|
|
170
|
+
var e = new Error(msg || "notification denied by browxai notificationPolicy");
|
|
171
|
+
try { e.name = "NotAllowedError"; } catch (_) {}
|
|
172
|
+
return e;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The constructed instance is a plain object whose prototype is set to
|
|
176
|
+
// \`OrigNotification.prototype\` AFTER own-property assignment, so
|
|
177
|
+
// accessor-only props on the platform prototype (\`title\`, \`body\`, etc.)
|
|
178
|
+
// don't intercept our \`this.title = ...\` writes. (Setting them via
|
|
179
|
+
// assignment with the prototype already in place throws TypeError in
|
|
180
|
+
// headless Chromium — \`Notification.prototype.title\` is getter-only.)
|
|
181
|
+
function ProxyNotification(title, options) {
|
|
182
|
+
var safeTitle = String(title);
|
|
183
|
+
var payload = {
|
|
184
|
+
title: safeTitle,
|
|
185
|
+
body: (options && options.body) || undefined,
|
|
186
|
+
icon: (options && options.icon) || undefined,
|
|
187
|
+
tag: (options && options.tag) || undefined,
|
|
188
|
+
origin: location.origin,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// SYNC throw timing — read the pre-seeded decision hint. Spec requires
|
|
192
|
+
// \`new Notification(...)\` to throw synchronously on failure. The async
|
|
193
|
+
// \`check()\` below still records the call (and does the ask-human dance);
|
|
194
|
+
// the sync hint is purely for the throw timing.
|
|
195
|
+
var syncDecision = (typeof window.__browx_notification_sync_decision === "string")
|
|
196
|
+
? window.__browx_notification_sync_decision
|
|
197
|
+
: "allow";
|
|
198
|
+
if (syncDecision === "deny" || syncDecision === "raise") {
|
|
199
|
+
// Still record the attempt before throwing.
|
|
200
|
+
try { check(payload); } catch (_) {}
|
|
201
|
+
throw notAllowed(syncDecision === "raise"
|
|
202
|
+
? "notification raised — set notificationPolicy"
|
|
203
|
+
: "Notification denied by browxai notificationPolicy");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Build the stub-as-this. Own data properties first; THEN set the
|
|
207
|
+
// prototype so getter-only inherited accessors don't intercept writes.
|
|
208
|
+
var listeners = {};
|
|
209
|
+
var realRef = null;
|
|
210
|
+
var pendingClose = false;
|
|
211
|
+
Object.defineProperty(this, "title", { value: safeTitle, writable: true, configurable: true, enumerable: true });
|
|
212
|
+
Object.defineProperty(this, "body", { value: (options && options.body) || "", writable: true, configurable: true, enumerable: true });
|
|
213
|
+
Object.defineProperty(this, "icon", { value: (options && options.icon) || "", writable: true, configurable: true, enumerable: true });
|
|
214
|
+
Object.defineProperty(this, "tag", { value: (options && options.tag) || "", writable: true, configurable: true, enumerable: true });
|
|
215
|
+
Object.defineProperty(this, "data", { value: (options && options.data) !== undefined ? options.data : null, writable: true, configurable: true, enumerable: true });
|
|
216
|
+
var self = this;
|
|
217
|
+
Object.defineProperty(this, "close", { value: function () {
|
|
218
|
+
if (realRef) { try { realRef.close(); } catch (_) {} return; }
|
|
219
|
+
pendingClose = true;
|
|
220
|
+
}, writable: true, configurable: true });
|
|
221
|
+
Object.defineProperty(this, "addEventListener", { value: function (ev, cb) {
|
|
222
|
+
(listeners[ev] = listeners[ev] || []).push(cb);
|
|
223
|
+
if (realRef && realRef.addEventListener) { try { realRef.addEventListener(ev, cb); } catch (_) {} }
|
|
224
|
+
}, writable: true, configurable: true });
|
|
225
|
+
Object.defineProperty(this, "removeEventListener", { value: function (ev, cb) {
|
|
226
|
+
var arr = listeners[ev]; if (!arr) return;
|
|
227
|
+
var i = arr.indexOf(cb); if (i >= 0) arr.splice(i, 1);
|
|
228
|
+
if (realRef && realRef.removeEventListener) { try { realRef.removeEventListener(ev, cb); } catch (_) {} }
|
|
229
|
+
}, writable: true, configurable: true });
|
|
230
|
+
|
|
231
|
+
// ask-human / allow: dispatch the policy check + (if allowed) construct
|
|
232
|
+
// the real native Notification and route the page's listeners to it.
|
|
233
|
+
check(payload).then(function (decision) {
|
|
234
|
+
if (decision !== "allow") return;
|
|
235
|
+
try {
|
|
236
|
+
var real = new OrigNotification(safeTitle, options || {});
|
|
237
|
+
realRef = real;
|
|
238
|
+
for (var ev in listeners) {
|
|
239
|
+
if (!Object.prototype.hasOwnProperty.call(listeners, ev)) continue;
|
|
240
|
+
for (var i = 0; i < listeners[ev].length; i++) {
|
|
241
|
+
try { real.addEventListener(ev, listeners[ev][i]); } catch (_) {}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (pendingClose) { try { real.close(); } catch (_) {} }
|
|
245
|
+
} catch (_) {
|
|
246
|
+
// Browser refused (e.g. \`Notification.permission === "denied"\`
|
|
247
|
+
// because permission_policy denied). The stub remains a no-op,
|
|
248
|
+
// matching the deny branch.
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
void self; // referenced via closure above
|
|
253
|
+
}
|
|
254
|
+
// Use a fresh prototype object — NOT \`OrigNotification.prototype\`, whose
|
|
255
|
+
// accessor-only properties (\`title\`, \`body\`, \`tag\`, etc.) would intercept
|
|
256
|
+
// our writes on \`this\` via the prototype chain (\`TypeError: Cannot set
|
|
257
|
+
// property title of #<Notification> which has only a getter\` in headless
|
|
258
|
+
// Chromium). Trade-off: \`instanceof Notification\` returns false for our
|
|
259
|
+
// stub; apps rarely runtime-check that, and the alternative (overriding
|
|
260
|
+
// the platform prototype's accessors) is messier + version-fragile.
|
|
261
|
+
ProxyNotification.prototype = {};
|
|
262
|
+
// Preserve the static surface — permission_policy owns these. Forward
|
|
263
|
+
// every static read/write to the original constructor so the existing
|
|
264
|
+
// \`permission_policy\` wrapper script still wraps \`requestPermission\` /
|
|
265
|
+
// observes \`permission\` unchanged.
|
|
266
|
+
try {
|
|
267
|
+
Object.defineProperty(ProxyNotification, "permission", {
|
|
268
|
+
get: function () { return OrigNotification.permission; },
|
|
269
|
+
configurable: true,
|
|
270
|
+
});
|
|
271
|
+
} catch (_) {}
|
|
272
|
+
ProxyNotification.requestPermission = function () {
|
|
273
|
+
return OrigNotification.requestPermission.apply(OrigNotification, arguments);
|
|
274
|
+
};
|
|
275
|
+
try { ProxyNotification.maxActions = OrigNotification.maxActions; } catch (_) {}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Replace the global. Some browsers refuse to delete \`Notification\` on
|
|
279
|
+
// \`window\` (it's a configurable: false property in newer specs); fall
|
|
280
|
+
// back to a defineProperty assignment if direct assignment is silent.
|
|
281
|
+
window.Notification = ProxyNotification;
|
|
282
|
+
if (window.Notification !== ProxyNotification) {
|
|
283
|
+
Object.defineProperty(window, "Notification", {
|
|
284
|
+
value: ProxyNotification, writable: true, configurable: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
} catch (_) {}
|
|
288
|
+
})();`;
|
|
289
|
+
/** Server-side wire-up. Installs:
|
|
290
|
+
* - `__browx_notification_check` exposeBinding: page-side records +
|
|
291
|
+
* ask-human resolves to allow/deny via the bridge.
|
|
292
|
+
* - The page-side init script (above), re-injected by Playwright on every
|
|
293
|
+
* new document via `addInitScript`.
|
|
294
|
+
* - Seeds the SYNCHRONOUS decision hint
|
|
295
|
+
* (`window.__browx_notification_sync_decision`) on every wired page so
|
|
296
|
+
* the constructor wrapper can throw without awaiting a binding round-
|
|
297
|
+
* trip; refreshed on `set_notification_policy` via
|
|
298
|
+
* `propagateSyncDecision`.
|
|
299
|
+
*
|
|
300
|
+
* Idempotent on the same context (the state's `WeakSet<BrowserContext>` guard).
|
|
301
|
+
* Errors during install are logged and swallowed — when bindings fail the
|
|
302
|
+
* wrapper falls back to call-through (browser default).
|
|
303
|
+
*/
|
|
304
|
+
export async function attachNotificationPolicy(context, state, askHandler) {
|
|
305
|
+
if (state.hasContext(context))
|
|
306
|
+
return;
|
|
307
|
+
state.markContext(context);
|
|
308
|
+
try {
|
|
309
|
+
await context.exposeBinding("__browx_notification_check", async (_source, payload) => {
|
|
310
|
+
try {
|
|
311
|
+
const o = JSON.parse(payload);
|
|
312
|
+
const title = String(o.title ?? "");
|
|
313
|
+
const origin = o.origin;
|
|
314
|
+
const mode = state.current().mode;
|
|
315
|
+
const ts = Date.now();
|
|
316
|
+
const baseRec = {
|
|
317
|
+
title,
|
|
318
|
+
timestamp: ts,
|
|
319
|
+
...(o.body !== undefined ? { body: o.body } : {}),
|
|
320
|
+
...(o.icon !== undefined ? { icon: o.icon } : {}),
|
|
321
|
+
...(o.tag !== undefined ? { tag: o.tag } : {}),
|
|
322
|
+
...(origin !== undefined ? { origin } : {}),
|
|
323
|
+
};
|
|
324
|
+
switch (mode) {
|
|
325
|
+
case "allow":
|
|
326
|
+
state.record({ ...baseRec, handledAs: "allowed" });
|
|
327
|
+
return "allow";
|
|
328
|
+
case "deny":
|
|
329
|
+
state.record({ ...baseRec, handledAs: "denied" });
|
|
330
|
+
return "deny";
|
|
331
|
+
case "ask-human": {
|
|
332
|
+
const decision = await askHandler({
|
|
333
|
+
title,
|
|
334
|
+
...(o.body !== undefined ? { body: o.body } : {}),
|
|
335
|
+
...(o.icon !== undefined ? { icon: o.icon } : {}),
|
|
336
|
+
...(o.tag !== undefined ? { tag: o.tag } : {}),
|
|
337
|
+
...(origin !== undefined ? { origin } : {}),
|
|
338
|
+
}).catch(() => "deny");
|
|
339
|
+
state.record({ ...baseRec, handledAs: "asked-human" });
|
|
340
|
+
return decision;
|
|
341
|
+
}
|
|
342
|
+
case "raise":
|
|
343
|
+
default:
|
|
344
|
+
state.record({ ...baseRec, handledAs: "raised" });
|
|
345
|
+
return "deny";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
log.warn("session.notification: check handler error", {
|
|
350
|
+
error: err instanceof Error ? err.message : String(err),
|
|
351
|
+
});
|
|
352
|
+
return "allow";
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
log.warn("session.notification: exposeBinding install failed; constructor falls back to call-through", {
|
|
358
|
+
error: err instanceof Error ? err.message : String(err),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Init-script — Playwright re-runs it on every new document. Idempotent
|
|
362
|
+
// via the `__browx_notification_installed` guard inside the script.
|
|
363
|
+
try {
|
|
364
|
+
await context.addInitScript({ content: NOTIFICATION_PAGE_SCRIPT });
|
|
365
|
+
// Seed the sync decision before any page script runs, plus apply to
|
|
366
|
+
// already-attached pages so the wrapper installs on the current document.
|
|
367
|
+
await context.addInitScript({ content: syncDecisionSeed(state.current().mode) });
|
|
368
|
+
for (const page of context.pages()) {
|
|
369
|
+
await page.evaluate(syncDecisionSeed(state.current().mode)).catch(() => undefined);
|
|
370
|
+
await page.evaluate(NOTIFICATION_PAGE_SCRIPT).catch(() => undefined);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
log.warn("session.notification: addInitScript failed", {
|
|
375
|
+
error: err instanceof Error ? err.message : String(err),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Compute the sync decision the constructor wrapper inspects to know
|
|
380
|
+
* whether to throw synchronously. `allow` and `ask-human` BOTH yield
|
|
381
|
+
* `"allow"` here: ask-human's constructor surface is non-throwing (we
|
|
382
|
+
* return the stub and only dispatch the real notification once the human
|
|
383
|
+
* answers). `deny` and `raise` throw at construction time. */
|
|
384
|
+
export function syncDecisionFor(mode) {
|
|
385
|
+
switch (mode) {
|
|
386
|
+
case "deny":
|
|
387
|
+
return "deny";
|
|
388
|
+
case "raise":
|
|
389
|
+
return "raise";
|
|
390
|
+
case "allow":
|
|
391
|
+
case "ask-human":
|
|
392
|
+
default:
|
|
393
|
+
return "allow";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function syncDecisionSeed(mode) {
|
|
397
|
+
const dec = syncDecisionFor(mode);
|
|
398
|
+
// Init-script: runs before any page script, sets the hint on `window`.
|
|
399
|
+
return `(() => { try { window.__browx_notification_sync_decision = ${JSON.stringify(dec)}; } catch (_) {} })();`;
|
|
400
|
+
}
|
|
401
|
+
/** Push the current policy's sync decision to every live page in the context
|
|
402
|
+
* AND register an additional init-script so future new documents see the
|
|
403
|
+
* fresh value. Called from `set_notification_policy` so a runtime mode flip
|
|
404
|
+
* takes effect on the very next constructor call without page reload.
|
|
405
|
+
*
|
|
406
|
+
* Init-scripts accumulate in Playwright (one per call); the seed is ~80
|
|
407
|
+
* bytes so even hundreds of flips are negligible. The constructor wrapper
|
|
408
|
+
* reads `window.__browx_notification_sync_decision` at each call, so the
|
|
409
|
+
* most-recently-evaluated seed wins. */
|
|
410
|
+
export async function propagateSyncDecision(context, state) {
|
|
411
|
+
const seed = syncDecisionSeed(state.current().mode);
|
|
412
|
+
try {
|
|
413
|
+
await context.addInitScript({ content: seed });
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
/* best-effort */
|
|
417
|
+
}
|
|
418
|
+
for (const page of context.pages()) {
|
|
419
|
+
await page.evaluate(seed).catch(() => undefined);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/** Read-side: snapshot the current policy + recent records for the session.
|
|
423
|
+
* Used by tests and (potentially) a future `notification_state` tool. */
|
|
424
|
+
export function readNotifications(state, since = 0) {
|
|
425
|
+
return { policy: state.current(), records: state.since(since) };
|
|
426
|
+
}
|