@reclaimprotocol/agent 0.1.23 → 0.1.24
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 +55 -20
- package/lib/authoring/backend.d.ts +5 -4
- package/lib/authoring/tools.js +30 -30
- package/lib/cdp/browser-proxy.d.ts +78 -0
- package/lib/cdp/browser-proxy.js +186 -0
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/client.d.ts +10 -0
- package/lib/cdp/geo.d.ts +10 -0
- package/lib/cdp/geo.js +36 -0
- package/lib/cdp/index.d.ts +1 -0
- package/lib/cdp/index.js +1 -0
- package/lib/live/cloudflared.d.ts +15 -0
- package/lib/live/cloudflared.js +268 -0
- package/lib/live/page.d.ts +20 -0
- package/lib/live/page.js +134 -0
- package/lib/live/tunnel.d.ts +23 -0
- package/lib/live/tunnel.js +120 -0
- package/lib/live/viewer.d.ts +28 -0
- package/lib/live/viewer.js +232 -0
- package/lib/mcp/api/operations.gen.js +34 -26
- package/lib/mcp/api/to-zod.d.ts +1 -1
- package/lib/mcp/api/to-zod.js +1 -1
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/instructions.d.ts +15 -2
- package/lib/mcp/instructions.js +101 -38
- package/lib/mcp/start.js +4 -3
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +8 -1
- package/lib/mcp/tools/agent/attach.js +166 -22
- package/lib/mcp/tools/agent/build.d.ts +13 -8
- package/lib/mcp/tools/agent/build.js +22 -11
- package/lib/mcp/tools/agent/capture.js +5 -1
- package/lib/mcp/tools/agent/eval.js +3 -1
- package/lib/mcp/tools/agent/find.js +6 -5
- package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
- package/lib/mcp/tools/agent/how-it-works.js +57 -17
- package/lib/mcp/tools/agent/live-view.d.ts +21 -0
- package/lib/mcp/tools/agent/live-view.js +153 -0
- package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
- package/lib/mcp/tools/agent/pod-proof.js +64 -0
- package/lib/mcp/tools/agent/propose.js +4 -2
- package/lib/mcp/tools/agent/proxy.d.ts +3 -0
- package/lib/mcp/tools/agent/proxy.js +199 -0
- package/lib/mcp/tools/agent/publish.js +47 -77
- package/lib/mcp/tools/agent/session.d.ts +2 -1
- package/lib/mcp/tools/agent/session.js +26 -7
- package/lib/mcp/tools/authenticate/authenticate.js +26 -13
- package/lib/mcp/tools/mode/mode.d.ts +1 -1
- package/lib/mcp/tools/mode/mode.js +10 -22
- package/lib/old/client.d.ts +1 -1
- package/lib/old/client.js +1 -1
- package/lib/old/instructions.d.ts +9 -1
- package/lib/old/instructions.js +4 -6
- package/lib/old/login-cdp.d.ts +2 -2
- package/lib/old/login-cdp.js +46 -6
- package/lib/old/to-register.d.ts +3 -2
- package/lib/old/to-register.js +1 -1
- package/lib/old/tools/authenticate.js +9 -1
- package/lib/old/tools/get-provider-info.js +6 -5
- package/lib/old/tools/issue-app-credentials.js +3 -2
- package/lib/old/tools/publish.d.ts +1 -1
- package/lib/old/tools/publish.js +90 -131
- package/lib/old/tools/update-provider-metadata.d.ts +1 -1
- package/lib/old/tools/update-provider-metadata.js +1 -1
- package/lib/old/tools/verify-sdk-setup.js +1 -1
- package/lib/proof/attestor.d.ts +3 -3
- package/lib/proof/debug-response-dump.js +4 -4
- package/lib/proof/index.d.ts +1 -0
- package/lib/proof/index.js +1 -0
- package/lib/proof/page-replay.d.ts +1 -1
- package/lib/proof/page-replay.js +1 -1
- package/lib/proof/pod-prover.d.ts +47 -0
- package/lib/proof/pod-prover.js +155 -0
- package/lib/provider/dom-xpath.js +11 -9
- package/lib/provider/draft.d.ts +10 -7
- package/lib/provider/draft.js +11 -9
- package/lib/provider/injection-doc.d.ts +23 -1
- package/lib/provider/injection-doc.js +34 -21
- package/lib/provider/match-redaction-warning.d.ts +2 -2
- package/lib/provider/match-redaction-warning.js +3 -3
- package/lib/provider/matcher.js +13 -12
- package/lib/provider/secrets.d.ts +3 -3
- package/lib/provider/secrets.js +3 -3
- package/lib/provider/to-version.d.ts +7 -6
- package/lib/provider/to-version.js +3 -3
- package/lib/tools/registry.d.ts +5 -5
- package/lib/tools/registry.js +5 -5
- package/package.json +3 -3
- package/samples/demo-server/README.md +7 -6
- package/skill/SKILL.builder.md +356 -189
- package/skill/SKILL.common.md +323 -97
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +58 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +235 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +60 -21
- package/skill/user-script.md +61 -0
- package/src/authoring/backend.ts +5 -4
- package/src/authoring/tools.ts +31 -30
- package/src/cdp/capture-engine.ts +8 -7
- package/src/cdp/client.ts +8 -0
- package/src/cdp/geo.ts +52 -0
- package/src/cdp/index.ts +1 -0
- package/src/consts.ts +1 -0
- package/src/live/cloudflared.ts +348 -0
- package/src/live/page.ts +145 -0
- package/src/live/tunnel.ts +157 -0
- package/src/live/viewer.ts +293 -0
- package/src/mcp/api/operations.gen.ts +34 -26
- package/src/mcp/api/to-zod.ts +1 -1
- package/src/mcp/api/tools.ts +1 -1
- package/src/mcp/instructions.ts +121 -48
- package/src/mcp/start.ts +4 -3
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +194 -22
- package/src/mcp/tools/agent/build.ts +26 -15
- package/src/mcp/tools/agent/capture.ts +5 -1
- package/src/mcp/tools/agent/eval.ts +4 -1
- package/src/mcp/tools/agent/find.ts +6 -5
- package/src/mcp/tools/agent/how-it-works.ts +62 -21
- package/src/mcp/tools/agent/live-view.ts +198 -0
- package/src/mcp/tools/agent/pod-proof.ts +90 -0
- package/src/mcp/tools/agent/propose.ts +4 -2
- package/src/mcp/tools/agent/publish.ts +52 -77
- package/src/mcp/tools/agent/session.ts +26 -5
- package/src/mcp/tools/authenticate/authenticate.ts +28 -13
- package/src/mcp/tools/mode/mode.ts +10 -22
- package/src/old/client.ts +2 -2
- package/src/old/instructions.ts +4 -10
- package/src/old/login-cdp.ts +50 -6
- package/src/old/to-register.ts +3 -2
- package/src/old/tools/authenticate.ts +9 -1
- package/src/old/tools/get-provider-info.ts +6 -5
- package/src/old/tools/issue-app-credentials.ts +3 -2
- package/src/old/tools/publish.ts +95 -131
- package/src/old/tools/update-provider-metadata.ts +1 -1
- package/src/old/tools/verify-sdk-setup.ts +1 -1
- package/src/proof/attestor.ts +3 -3
- package/src/proof/debug-response-dump.ts +4 -4
- package/src/proof/index.ts +8 -0
- package/src/proof/page-replay.ts +1 -1
- package/src/proof/pod-prover.ts +231 -0
- package/src/provider/dom-xpath.ts +11 -9
- package/src/provider/draft.ts +13 -10
- package/src/provider/injection-doc.ts +37 -24
- package/src/provider/match-redaction-warning.ts +3 -3
- package/src/provider/matcher.ts +13 -12
- package/src/provider/secrets.ts +3 -3
- package/src/provider/to-version.ts +11 -9
- package/src/tools/registry.ts +5 -5
- package/skill/injection-api.md +0 -364
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @reclaimprotocol/agent
|
|
2
2
|
|
|
3
|
-
Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to
|
|
3
|
+
Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to find, build, test, and fix **data verification providers**: the recipes that verify a data point. With it, your agent inspects and replays captured requests, checks verification sessions, evaluates proofs, and publishes provider updates.
|
|
4
4
|
|
|
5
5
|
Under the hood it's Chrome-CDP + zkTLS provider-authoring tooling: it captures network traffic from a browser, synthesizes provider definitions from it (`contains` matchers + `jsonPath` / `xPath` / `regex` redactions), runs a local replay diagnostic, and produces a verified attestor proof via `@reclaimprotocol/attestor-core`.
|
|
6
6
|
|
|
@@ -8,9 +8,13 @@ This package **is** the Reclaim MCP server (bin: `reclaim-mcp-server`). It depen
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
**Prerequisites:**
|
|
11
|
+
**Prerequisites:**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- **Node 20 or later.** Any line works, including non-LTS ones such as 25, but a current LTS line (22 or 24) is recommended.
|
|
14
|
+
- **A local Chrome, Chromium, or Edge**, used for capture and for the dashboard login.
|
|
15
|
+
- **`cloudflared`** — not required up front. `share_browser_view` uses an existing install if one is on `PATH`, and otherwise downloads it once to `~/.reclaim/bin` on first share (never at install time). Set `RECLAIM_CLOUDFLARED_PATH` to point at your own copy.
|
|
16
|
+
|
|
17
|
+
The first time you call `run_proof`, the agent downloads the ZK circuit files it needs (about 280 MB), so allow a moment for that first proof. The download is deferred to the first proof rather than run at install time, so the MCP server itself starts immediately.
|
|
14
18
|
|
|
15
19
|
It runs as a **stdio MCP server**. Point any MCP-capable agent at this command:
|
|
16
20
|
|
|
@@ -48,24 +52,24 @@ See [Backends](#backends) for builder mode (`USE_OLD_DEVTOOLS=false`) and the fu
|
|
|
48
52
|
|
|
49
53
|
### Provider-authoring guide
|
|
50
54
|
|
|
51
|
-
The MCP server is all you need — it ships a `how_it_works` tool that
|
|
55
|
+
The MCP server is all you need — it ships a `how_it_works` tool that serves the capture → prove → publish guide **one topic at a time** (`overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`, `discover`, `user-script`, `interception`, `hash-validation`, `credentials`, `never-do`, `troubleshooting`). Call it with no arguments for the overview plus the topic index; every response repeats that index, so an agent that guessed wrong self-corrects without a second round-trip. Any MCP client can call it on demand; there's nothing extra to install.
|
|
52
56
|
|
|
53
57
|
## Using it from your agent
|
|
54
58
|
|
|
55
59
|
Tip: have your agent call the `how_it_works` tool first so it follows the full authoring flow.
|
|
56
60
|
|
|
57
|
-
Then
|
|
61
|
+
Then ask in plain language. For example:
|
|
58
62
|
|
|
59
63
|
> "Use Reclaim to create a provider that proves my GitHub follower count."
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
Or more loosely:
|
|
62
66
|
|
|
63
67
|
> "I want to verify Github follow count"
|
|
64
68
|
|
|
65
69
|
The agent orchestrates the tools for you:
|
|
66
70
|
|
|
67
71
|
1. **`reclaim_authenticate`** — a Chrome window opens the Reclaim dashboard; you sign in. The token is captured automatically and the tab closes. (Or it reuses a still-valid cached identity.)
|
|
68
|
-
2. **`attach_browser` + `navigate`** — uses local Chrome, a Builder-hosted remote browser, or a custom CDP websocket; **`wait_for_page`** waits while
|
|
72
|
+
2. **`attach_browser` + `navigate`** — uses local Chrome, a Builder-hosted remote browser, or a custom CDP websocket; **`wait_for_page`** waits while you log in.
|
|
69
73
|
3. **`start_capture` → `find_requests_containing` → `get_request`** — records network traffic and locates the request carrying your value.
|
|
70
74
|
4. **`propose_provider`** — drafts the provider (a `contains` match + `jsonPath`/`xPath`/`regex` redaction), keeping any secret headers server-side.
|
|
71
75
|
5. **`replay_request` → `run_proof`** — pre-flights the draft, then produces a verified zkTLS proof via the attestor.
|
|
@@ -75,7 +79,7 @@ Your only manual steps are signing in when the browser windows open and confirmi
|
|
|
75
79
|
|
|
76
80
|
## How it works (guide tool)
|
|
77
81
|
|
|
78
|
-
The
|
|
82
|
+
The mode-aware authoring guide (`skill/SKILL.builder.md` / `skill/SKILL.old.md`, one per backend) is served on demand by the **`how_it_works`** MCP tool — call it from any MCP client (Claude Code, Cursor, opencode, Antigravity, …) to get the deep capture → prove → publish workflow, decision frameworks, and troubleshooting. It is served per topic rather than all at once, so pulling one answer costs a fraction of the guide. The server's always-on `instructions` cover the essentials; `how_it_works` is the in-depth version, with nothing to install.
|
|
79
83
|
|
|
80
84
|
## Provider-authoring tools
|
|
81
85
|
|
|
@@ -83,20 +87,20 @@ These capture/synthesis/proof tools are backend-independent and registered in bo
|
|
|
83
87
|
|
|
84
88
|
| Tool | Purpose |
|
|
85
89
|
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
86
|
-
| `attach_browser` | Connect
|
|
87
|
-
| `attach_chrome` | Backward-compatible alias for `attach_browser` |
|
|
90
|
+
| `attach_browser` | Connect through local Chrome, Builder-hosted Popcorn, or a custom CDP websocket. All four options — including how each one is watched live — are spelled out in the tool description, so an agent can put the choice to the developer and connect in one call |
|
|
88
91
|
| `list_tabs` | List open Chrome tabs |
|
|
89
92
|
| `navigate`, `wait_for_page`, `eval_in_page` | Drive the tab; poll for login completion |
|
|
90
93
|
| `start_capture`, `stop_capture` | Network capture lifecycle |
|
|
91
94
|
| `list_requests`, `get_request` | Inspect captured traffic |
|
|
92
95
|
| `find_requests_containing` | Deterministic search for a target value across captured responses |
|
|
93
|
-
| `analyze_request_constraints` | Flag concerns
|
|
96
|
+
| `analyze_request_constraints` | Flag concerns: signed request, CSRF dependency, bot challenge, short-lived token, GraphQL |
|
|
94
97
|
| `propose_provider` | Synthesize a provider — `contains` match + `jsonPath`/`xPath`/`regex` redaction |
|
|
95
98
|
| `replay_request` | Pre-flight replay via Node `fetch` (no attestor); also used for auth-bound check by omitting `secretParamsRef` |
|
|
96
99
|
| `run_proof` | End-to-end zkTLS proof through the attestor SDK |
|
|
97
100
|
| `create_provider_version_from_capture` | Publish a drafted provider to the backend |
|
|
98
101
|
| `authenticate_builder` | In old-devtools mode only, sign in to Builder for remote-browser allocation |
|
|
99
102
|
| `list_builder_organizations` | In old-devtools mode only, list organization ids for a quota-accounted Builder browser |
|
|
103
|
+
| `share_browser_view`, `stop_browser_view` | Put a LOCAL attached tab behind a public HTTPS URL (cloudflared quick tunnel) so someone elsewhere can watch it and, by default, click and type in it. Local CDP modes only — a `builder` browser already returns a hosted `liveViewUrl` |
|
|
100
104
|
| `dispose_browser` | Stop captures, disconnect CDP, and immediately release a Builder-hosted browser |
|
|
101
105
|
| `get_session`, `reset_session` | Session state |
|
|
102
106
|
|
|
@@ -114,7 +118,7 @@ Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotoco
|
|
|
114
118
|
|
|
115
119
|
## Local development (from source)
|
|
116
120
|
|
|
117
|
-
For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install
|
|
121
|
+
For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install only fetches dependencies:
|
|
118
122
|
|
|
119
123
|
```bash
|
|
120
124
|
npm install
|
|
@@ -128,14 +132,18 @@ npm run download:zk-files --workspace=packages/agent
|
|
|
128
132
|
|
|
129
133
|
## Platform notes
|
|
130
134
|
|
|
131
|
-
- **Node
|
|
135
|
+
- **Node 20 or later** is required to run the published package (declared in `engines`). The package ships compiled `lib/*.js`, so no TypeScript type-stripping is needed at runtime.
|
|
136
|
+
|
|
137
|
+
Any line works, including non-LTS ones such as 25. The one native addon that used to break this, `re2`, became an **optional** dependency in `@reclaimprotocol/attestor-core` 5.0.8 and degrades to the native `RegExp` when its binary is absent. The other native dependency, `koffi`, is N-API, so it ships ABI-independent prebuilts for every platform and was never version-sensitive. An LTS line (22 or 24) is still recommended, because `re2` is present there and gives faster, ReDoS-safe matching.
|
|
138
|
+
|
|
139
|
+
Working on the package **from a checkout** additionally needs **Node 22.18 or later**: the test suite and the dev workflow run bare `.ts` files directly, and unflagged TypeScript type-stripping landed in 22.18.
|
|
132
140
|
- **Chrome 136+ silently disables `--remote-debugging-port`** when launched against the default user profile. `dedicated` mode (the default) sidesteps this by always passing `--user-data-dir=~/.reclaim/chrome-profile`.
|
|
133
141
|
|
|
134
142
|
## Tests
|
|
135
143
|
|
|
136
|
-
`npm test` runs the unit
|
|
144
|
+
`npm test` runs the unit and integration suite. Chrome isn't needed.
|
|
137
145
|
|
|
138
|
-
The Chrome CDP end-to-end test at `tests/e2e-chrome.test.ts` is gated behind an env var. It launches headless Chrome with a dedicated temp profile, captures a navigation against a local fixture server, and asserts the
|
|
146
|
+
The Chrome CDP end-to-end test at `tests/e2e-chrome.test.ts` is gated behind an env var. It launches headless Chrome with a dedicated temp profile, captures a navigation against a local fixture server, and asserts the synthesizer finds the right request. To run it:
|
|
139
147
|
|
|
140
148
|
```bash
|
|
141
149
|
# bash / macOS / Linux
|
|
@@ -145,11 +153,11 @@ RECLAIM_AGENT_E2E=1 npm test --workspace=packages/agent
|
|
|
145
153
|
$env:RECLAIM_AGENT_E2E=1; npm test --workspace=packages/agent
|
|
146
154
|
```
|
|
147
155
|
|
|
148
|
-
|
|
156
|
+
The test requires Chrome, Chromium, or Edge installed somewhere `findChromeBinary` can locate it, or `RECLAIM_AGENT_CHROME_PATH` pointing at the binary. It self-skips when no binary is found.
|
|
149
157
|
|
|
150
|
-
##
|
|
158
|
+
## Disable the browser tools
|
|
151
159
|
|
|
152
|
-
The MCP server registers the provider-authoring (Chrome
|
|
160
|
+
The MCP server registers the provider-authoring (Chrome and CDP) tools by default in both modes. To disable them — for example, in a hosted deployment with no local Chrome — set:
|
|
153
161
|
|
|
154
162
|
```bash
|
|
155
163
|
RECLAIM_AGENT_DISABLED=1
|
|
@@ -203,8 +211,35 @@ claude mcp add reclaim -- node "$(pwd)/packages/agent/lib/mcp-bin.js"
|
|
|
203
211
|
}
|
|
204
212
|
```
|
|
205
213
|
|
|
206
|
-
To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) —
|
|
214
|
+
To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) — for example, `"USE_OLD_DEVTOOLS": "false"` for builder mode. See [Backends](#backends).
|
|
207
215
|
|
|
208
216
|
### Authoring guide
|
|
209
217
|
|
|
210
|
-
|
|
218
|
+
Everything the `how_it_works` tool serves lives in `skill/` as markdown — edit it and rebuild (`npm run build`) to change what the tool returns.
|
|
219
|
+
|
|
220
|
+
| file | what it is |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `builder-mode.md`, `old-mode.md` | The server's **always-on** `instructions`, one per backend. Every client pays for these on every turn — keep them short and keep the depth in a topic. |
|
|
223
|
+
| `SKILL.builder.md`, `SKILL.old.md` | The authoring guide per backend, split into `<!-- topic: name -->` blocks. Each block is one `how_it_works` topic. |
|
|
224
|
+
| `SKILL.common.md` | Prose shared by both guides, in `<!-- section: name -->` blocks. A guide splices one in with `<!-- common: name -->`. |
|
|
225
|
+
| `user-script.md`, `interception.md`, `hash-validation.md` | Backend-independent topics, served as-is to both modes. |
|
|
226
|
+
| `js-sdk-integration.md` | Served by the old-devtools `get_js_sdk_integration_guide` tool, not by `how_it_works`. |
|
|
227
|
+
|
|
228
|
+
Two mechanics to know when editing:
|
|
229
|
+
|
|
230
|
+
- `%CTX%` expands to the mode's consumer-param namespace (`context.` on builder, `context_` on old-devtools). It is usually the *only* difference between the two modes, which is what lets a block stay in `SKILL.common.md` instead of being duplicated and drifting.
|
|
231
|
+
- Every topic needs a one-line entry in `TOPIC_SUMMARIES` (`src/mcp/instructions.ts`) — it's what the tool advertises in its index. Assembly asserts on a missing section or summary, so a typo fails at server start rather than silently serving an empty topic.
|
|
232
|
+
|
|
233
|
+
**Do not inline docs into a tool's input schema.** A schema description is sent to the model on every request; the injection guide that used to live in the publish tool's `jsUserScripts` field was about half the entire old-devtools tool surface on its own. Put the prose in a topic and leave a pointer.
|
|
234
|
+
|
|
235
|
+
### Writing conventions for `skill/` and tool descriptions
|
|
236
|
+
|
|
237
|
+
These follow the [Agent Skills best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) and the [Google developer documentation style guide](https://developers.google.com/style):
|
|
238
|
+
|
|
239
|
+
- **Assume the model is smart.** Only write what it can't infer. Every token in a topic competes with the task.
|
|
240
|
+
- **Keep each topic focused and one level deep.** A topic links to other topics by name; it never chains through a third file. Give any topic over 100 lines a `## Contents` list at the top, so a partial read still shows its full scope.
|
|
241
|
+
- **One term per concept.** "provider", not "verification" or "recipe". "developer", not "dev", "user", or "operator". "user script", not "injection".
|
|
242
|
+
- **Reserve capitals for hard rules.** NEVER, MUST, ONLY, and NOT earn all caps; use bold for ordinary emphasis.
|
|
243
|
+
- **Write in American English, in the second person, in the present tense.** Spell out "for example" and "that is" rather than "e.g." and "i.e.".
|
|
244
|
+
- **Every tool description says what the tool does and when to call it**, plus any constraint the model can't discover by calling it.
|
|
245
|
+
- **No time-sensitive statements.** Pin facts to a version or a named behavior, not to a date.
|
|
@@ -56,9 +56,10 @@ export interface AuthoringBackend {
|
|
|
56
56
|
/** This session's drafts (propose → replay/run_proof). */
|
|
57
57
|
readonly drafts: DraftStore;
|
|
58
58
|
/** Prove a drafted recipe. Local: attestor SDK (rich claim view). Remote: TEE
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* `/reclaim/prove` (lean, secret-free summary). Returns the environment's
|
|
60
|
+
* display shape. A backend may record the proven recipe internally (for
|
|
61
|
+
* example, the cloud publish step) — that side effect is the backend's, not
|
|
62
|
+
* ours. */
|
|
62
63
|
prove(draft: Draft, opts?: ProveOptions): Promise<unknown>;
|
|
63
64
|
/**
|
|
64
65
|
* Validate/replay a drafted recipe before proving. Both local and remote
|
|
@@ -67,7 +68,7 @@ export interface AuthoringBackend {
|
|
|
67
68
|
* always reflects the real browser session. With `withoutSecrets` it is
|
|
68
69
|
* the AUTH-BOUND check: re-issue anonymously (`credentials: 'omit'`, no
|
|
69
70
|
* secret headers) and report whether the values are STILL extractable —
|
|
70
|
-
*
|
|
71
|
+
* that is, the endpoint is public. Without it, validate extraction for the
|
|
71
72
|
* authenticated request. The shape is environment-specific and passed
|
|
72
73
|
* straight through as the tool's result.
|
|
73
74
|
*/
|
package/lib/authoring/tools.js
CHANGED
|
@@ -80,7 +80,9 @@ export function authoringTools(resolveBackend) {
|
|
|
80
80
|
return [
|
|
81
81
|
defineTool({
|
|
82
82
|
name: 'navigate',
|
|
83
|
-
description: 'Navigate the
|
|
83
|
+
description: 'Navigate the captured tab to a URL and wait for its load event. '
|
|
84
|
+
+ 'Returns the URL the tab actually landed on, so you can detect a '
|
|
85
|
+
+ 'redirect to a login page.',
|
|
84
86
|
inputSchema: objectSchema({ url: { type: 'string' }, timeoutMs: { type: 'number' } }, ['url']),
|
|
85
87
|
}, async (args) => {
|
|
86
88
|
const backend = resolveBackend(args);
|
|
@@ -98,14 +100,18 @@ export function authoringTools(resolveBackend) {
|
|
|
98
100
|
}),
|
|
99
101
|
defineTool({
|
|
100
102
|
name: 'eval_in_page',
|
|
101
|
-
description: 'Evaluate a JavaScript expression in the page and return its value '
|
|
102
|
-
+ '
|
|
103
|
+
description: 'Evaluate a JavaScript expression in the page and return its value. '
|
|
104
|
+
+ 'Use it to read the value being proven out of the DOM, to check '
|
|
105
|
+
+ 'login state, or to trigger the target request (for example, '
|
|
106
|
+
+ '`location.reload()`).',
|
|
103
107
|
inputSchema: objectSchema({ expression: { type: 'string' } }, ['expression']),
|
|
104
108
|
}, (args) => evalInPage(resolveBackend(args), String(args.expression))),
|
|
105
109
|
defineTool({
|
|
106
110
|
name: 'wait_for_page',
|
|
107
|
-
description: 'Poll a JavaScript expression until it is truthy or
|
|
108
|
-
+ 'elapses
|
|
111
|
+
description: 'Poll a JavaScript expression once a second until it is truthy or '
|
|
112
|
+
+ 'the timeout elapses. Use it to wait for content to appear. To '
|
|
113
|
+
+ 'wait for a login, use wait_for_login instead — page JavaScript '
|
|
114
|
+
+ 'cannot see the httpOnly session cookie.',
|
|
109
115
|
inputSchema: objectSchema({ expression: { type: 'string' }, timeoutMs: { type: 'number' } }, ['expression']),
|
|
110
116
|
}, async (args) => {
|
|
111
117
|
const backend = resolveBackend(args);
|
|
@@ -122,26 +128,18 @@ export function authoringTools(resolveBackend) {
|
|
|
122
128
|
}),
|
|
123
129
|
defineTool({
|
|
124
130
|
name: 'test_user_script',
|
|
125
|
-
description: 'Dry-run a USER SCRIPT
|
|
126
|
-
+ '
|
|
127
|
-
+ '
|
|
128
|
-
+ '
|
|
129
|
-
+ '
|
|
130
|
-
+ '
|
|
131
|
-
+ '
|
|
132
|
-
+ '
|
|
133
|
-
+
|
|
134
|
-
+ '
|
|
135
|
-
+ '`navigateUrl`
|
|
136
|
-
+ '
|
|
137
|
-
+ '— safe to call repeatedly). Without `navigateUrl` the script is '
|
|
138
|
-
+ 'left installed for you to navigate yourself (any way you like) '
|
|
139
|
-
+ 'then re-call this tool, or read '
|
|
140
|
-
+ '`window.__reclaimUserScriptResult` via eval_in_page — in that '
|
|
141
|
-
+ 'manual mode it is NOT auto-removed and keeps running on every '
|
|
142
|
-
+ 'later navigation until a follow-up call with `navigateUrl` '
|
|
143
|
-
+ 'removes it, so avoid stacking several manual-mode calls in a '
|
|
144
|
-
+ 'row.',
|
|
131
|
+
description: 'Dry-run a USER SCRIPT (the `jsUserScripts` / `customInjection` '
|
|
132
|
+
+ 'field) in the LIVE attached tab, BEFORE publishing. Installs '
|
|
133
|
+
+ '`script` via CDP so it runs on the next navigation exactly as '
|
|
134
|
+
+ 'the real field would, then reports whether it ran and what it '
|
|
135
|
+
+ 'threw. With `navigateUrl`, it navigates there and removes the '
|
|
136
|
+
+ 'script once the result is read — self-cleaning, safe to repeat. '
|
|
137
|
+
+ 'WITHOUT `navigateUrl` the script stays installed (navigate '
|
|
138
|
+
+ 'yourself, then re-call this tool or read '
|
|
139
|
+
+ '`window.__reclaimUserScriptResult` via eval_in_page) and keeps '
|
|
140
|
+
+ 'running on every later navigation until a call WITH '
|
|
141
|
+
+ '`navigateUrl` removes it — so don\'t stack manual-mode calls. '
|
|
142
|
+
+ 'Guide: how_it_works({ topic: "user-script" }).',
|
|
145
143
|
inputSchema: objectSchema({
|
|
146
144
|
script: { type: 'string' },
|
|
147
145
|
navigateUrl: { type: 'string' },
|
|
@@ -177,8 +175,9 @@ export function authoringTools(resolveBackend) {
|
|
|
177
175
|
note: 'Installed for the NEXT navigation only — it did not run on '
|
|
178
176
|
+ 'the current page, and stays installed (runs on every later '
|
|
179
177
|
+ 'navigation too) until a follow-up call with navigateUrl '
|
|
180
|
-
+ 'removes it. Navigate now (
|
|
181
|
-
+ 'window.__reclaimUserScriptResult via
|
|
178
|
+
+ 'removes it. Navigate now (for example, with the navigate '
|
|
179
|
+
+ 'tool), then read window.__reclaimUserScriptResult via '
|
|
180
|
+
+ 'eval_in_page, or call '
|
|
182
181
|
+ 'test_user_script again with navigateUrl.',
|
|
183
182
|
};
|
|
184
183
|
}
|
|
@@ -216,7 +215,8 @@ export function authoringTools(resolveBackend) {
|
|
|
216
215
|
description: 'List the NAMES of cookies currently set for the page (values are '
|
|
217
216
|
+ 'NEVER returned). The most reliable, language- and DOM-independent '
|
|
218
217
|
+ 'login signal: after the user logs in, an auth/session cookie '
|
|
219
|
-
+ 'appears (
|
|
218
|
+
+ 'appears (for example, session, sid, token, auth, jwt, _user, '
|
|
219
|
+
+ 'logged_in). '
|
|
220
220
|
+ 'Includes httpOnly cookies the page JS cannot see.',
|
|
221
221
|
inputSchema: objectSchema({}),
|
|
222
222
|
}, async (args) => {
|
|
@@ -257,8 +257,8 @@ export function authoringTools(resolveBackend) {
|
|
|
257
257
|
'userFirstVisit', 'cf_', '_dd_', 'bcookie', 'bscookie', 'lidc',
|
|
258
258
|
].join('|') + ')', 'i');
|
|
259
259
|
// A real session/auth cookie name. Deliberately NOT bare `token` — a
|
|
260
|
-
// challenge/CSRF token (
|
|
261
|
-
// set BEFORE auth completes. Any new httpOnly cookie is also a
|
|
260
|
+
// challenge/CSRF token (for example, LinkedIn `chp_token`) contains it
|
|
261
|
+
// and is set BEFORE auth completes. Any new httpOnly cookie is also a
|
|
262
262
|
// candidate (server-set session cookies are usually httpOnly).
|
|
263
263
|
const SESSION = /sess|sid|jwt|_session|auth|logged.?in|remember|li_at/i;
|
|
264
264
|
// Auth/challenge URL paths — while the page is still on one, login is
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** The minimal CDP surface this module needs. Both the agent's `CdpTabClient`
|
|
2
|
+
* and an app-side session-scoped wrapper around `chrome-remote-interface`'s
|
|
3
|
+
* `CDP.Client` already satisfy this. */
|
|
4
|
+
export interface CdpProxySender {
|
|
5
|
+
send: (method: string, params?: object) => Promise<unknown>;
|
|
6
|
+
subscribe: <T = unknown>(method: string, listener: (params: T) => void) => () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface BrowserProxyCredentials {
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
username: string;
|
|
12
|
+
password: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TemplatedProxyUsernameOptions {
|
|
15
|
+
/** Vendor's env-configured base, e.g.
|
|
16
|
+
* `brd-customer-<id>-zone-<zone>-country-{{COUNTRY_LOCATION}}`. */
|
|
17
|
+
template: string;
|
|
18
|
+
countryCode: string;
|
|
19
|
+
/** Sticky-session key — the same value must be used for every request
|
|
20
|
+
* that should exit through the same proxy IP. */
|
|
21
|
+
proxySessionId: string;
|
|
22
|
+
}
|
|
23
|
+
/** Substitute `{{COUNTRY_LOCATION}}` in the vendor's username template (or
|
|
24
|
+
* append `-country-<cc>` if the template has no placeholder), then append
|
|
25
|
+
* the sticky-session suffix. */
|
|
26
|
+
export declare function buildTemplatedProxyUsername(opts: TemplatedProxyUsernameOptions): string;
|
|
27
|
+
/** A short alphanumeric sticky-session id suggestion for a proxy vendor's
|
|
28
|
+
* session placeholder (e.g. Bright Data's `-session-<id>` suffix) —
|
|
29
|
+
* deliberately not `captureId`/`sessionId` (long UUIDs, wrong shape for
|
|
30
|
+
* most vendors' conventions and for the attestor's own `proxySessionId`
|
|
31
|
+
* constraint: smallcase alphanumeric, 8-14 chars). Only a suggestion — the
|
|
32
|
+
* caller's own `proxySessionId`, when given, is used instead. */
|
|
33
|
+
export declare function generateProxySessionId(): string;
|
|
34
|
+
/** Parse a `PROXY_SERVER`-style `host:port` string. Throws on a malformed
|
|
35
|
+
* value — a bad env var should fail loudly, not silently proxy nowhere. */
|
|
36
|
+
export declare function parseProxyServer(server: string): {
|
|
37
|
+
host: string;
|
|
38
|
+
port: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Route the browser's network traffic through `host:port` via the Popcorn
|
|
42
|
+
* extension's page API. Never throws — a `false` return means the extension
|
|
43
|
+
* couldn't be reached (e.g. this isn't a Popcorn browser); callers still get
|
|
44
|
+
* proxy auth wired up by {@link setupProxyAuthViaCdp} independently, since a
|
|
45
|
+
* caller-supplied proxy might already be configured another way.
|
|
46
|
+
*/
|
|
47
|
+
export declare function configureBrowserProxyViaExtension(sender: CdpProxySender, config: BrowserProxyCredentials): Promise<boolean>;
|
|
48
|
+
/** Clear the extension-configured proxy, routing the browser direct again. */
|
|
49
|
+
export declare function clearBrowserProxyViaExtension(sender: CdpProxySender): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Answer the proxy's Basic-auth challenge over CDP `Fetch`, since Popcorn's
|
|
52
|
+
* extension only sets host/port routing (not credentials — the proxy vendor
|
|
53
|
+
* still challenges the browser for them the normal way). Self-disables once
|
|
54
|
+
* the first challenge is answered (or after a timeout with none), rather
|
|
55
|
+
* than intercepting every request for the session's whole lifetime.
|
|
56
|
+
*/
|
|
57
|
+
export declare function setupProxyAuthViaCdp(sender: CdpProxySender, credentials: Pick<BrowserProxyCredentials, 'username' | 'password'>): Promise<void>;
|
|
58
|
+
export interface ApplyBrowserProxyOptions extends BrowserProxyCredentials {
|
|
59
|
+
/** Defaults to true — proxy routing only affects new connections, so
|
|
60
|
+
* existing page state must reload to actually go through it. */
|
|
61
|
+
reload?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface ApplyBrowserProxyResult {
|
|
64
|
+
/** False means the extension wasn't reachable (not a Popcorn browser, or
|
|
65
|
+
* it isn't installed) — auth handling was still armed regardless, in
|
|
66
|
+
* case the proxy is configured another way. */
|
|
67
|
+
appliedViaExtension: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Configure routing + auth + reload, in the right order: routing must be
|
|
70
|
+
* live before the reload navigates, and auth must be armed before that
|
|
71
|
+
* navigation's requests go out. */
|
|
72
|
+
export declare function applyBrowserProxy(sender: CdpProxySender, opts: ApplyBrowserProxyOptions): Promise<ApplyBrowserProxyResult>;
|
|
73
|
+
/** Clear proxy routing and reload so subsequent requests go direct again. */
|
|
74
|
+
export declare function clearBrowserProxy(sender: CdpProxySender, opts?: {
|
|
75
|
+
reload?: boolean;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
cleared: boolean;
|
|
78
|
+
}>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native browser-level proxy for a Popcorn remote browser session.
|
|
3
|
+
*
|
|
4
|
+
* Routes the browser's OWN network traffic (not the attestor's zkTLS egress —
|
|
5
|
+
* that's `geoLocation`/`useProxy` on `ProviderWebSettings`) through a
|
|
6
|
+
* user-supplied proxy. Applied via the Popcorn kiosk image's `window.__pcn`
|
|
7
|
+
* page API rather than a Playwright/CDP launch arg or a new browser context —
|
|
8
|
+
* both of those would spawn a browser outside the pod's `--kiosk` window,
|
|
9
|
+
* breaking the live-view stream this session is already being watched
|
|
10
|
+
* through. Proxy auth (username/password) is answered separately over CDP's
|
|
11
|
+
* `Fetch` domain, since Popcorn's extension only sets host/port routing.
|
|
12
|
+
*
|
|
13
|
+
* Shared between the app's verification runner and the agent's MCP tools —
|
|
14
|
+
* both drive a Popcorn browser over `chrome-remote-interface`, just through
|
|
15
|
+
* different wrapper types, so this module only depends on the minimal
|
|
16
|
+
* {@link CdpProxySender} shape rather than either package's CDP client.
|
|
17
|
+
*/
|
|
18
|
+
import { randomInt } from 'node:crypto';
|
|
19
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
20
|
+
import { LOGGER } from "../logger.js";
|
|
21
|
+
/** Substitute `{{COUNTRY_LOCATION}}` in the vendor's username template (or
|
|
22
|
+
* append `-country-<cc>` if the template has no placeholder), then append
|
|
23
|
+
* the sticky-session suffix. */
|
|
24
|
+
export function buildTemplatedProxyUsername(opts) {
|
|
25
|
+
const { template, countryCode, proxySessionId } = opts;
|
|
26
|
+
const cc = countryCode.toLowerCase();
|
|
27
|
+
const base = template.includes('{{COUNTRY_LOCATION}}')
|
|
28
|
+
? template.replaceAll('{{COUNTRY_LOCATION}}', cc)
|
|
29
|
+
: `${template}-country-${cc}`;
|
|
30
|
+
return `${base}-session-${proxySessionId}`;
|
|
31
|
+
}
|
|
32
|
+
const PROXY_SESSION_ID_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
33
|
+
const PROXY_SESSION_ID_LENGTH = 10;
|
|
34
|
+
/** A short alphanumeric sticky-session id suggestion for a proxy vendor's
|
|
35
|
+
* session placeholder (e.g. Bright Data's `-session-<id>` suffix) —
|
|
36
|
+
* deliberately not `captureId`/`sessionId` (long UUIDs, wrong shape for
|
|
37
|
+
* most vendors' conventions and for the attestor's own `proxySessionId`
|
|
38
|
+
* constraint: smallcase alphanumeric, 8-14 chars). Only a suggestion — the
|
|
39
|
+
* caller's own `proxySessionId`, when given, is used instead. */
|
|
40
|
+
export function generateProxySessionId() {
|
|
41
|
+
let out = '';
|
|
42
|
+
for (let i = 0; i < PROXY_SESSION_ID_LENGTH; i++) {
|
|
43
|
+
out += PROXY_SESSION_ID_CHARS[randomInt(PROXY_SESSION_ID_CHARS.length)];
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Parse a `PROXY_SERVER`-style `host:port` string. Throws on a malformed
|
|
48
|
+
* value — a bad env var should fail loudly, not silently proxy nowhere. */
|
|
49
|
+
export function parseProxyServer(server) {
|
|
50
|
+
const match = /^([^:/]+):(\d+)$/.exec(server.trim());
|
|
51
|
+
if (!match) {
|
|
52
|
+
throw new Error(`PROXY_SERVER must be "host:port", got "${server}"`);
|
|
53
|
+
}
|
|
54
|
+
return { host: match[1], port: Number(match[2]) };
|
|
55
|
+
}
|
|
56
|
+
const PCN_READY_TIMEOUT_MS = 5000;
|
|
57
|
+
const PCN_READY_POLL_MS = 100;
|
|
58
|
+
/** Chrome caches a proxy credential per host:port after the first successful
|
|
59
|
+
* challenge, so this only needs to cover the FIRST request. If no challenge
|
|
60
|
+
* ever arrives (proxy doesn't require auth), don't leave `Fetch` enabled —
|
|
61
|
+
* it would otherwise hold every request pending on this handler forever. */
|
|
62
|
+
const PROXY_AUTH_SAFETY_TIMEOUT_MS = 15000;
|
|
63
|
+
async function isExtensionReady(sender) {
|
|
64
|
+
const { result } = await sender.send('Runtime.evaluate', {
|
|
65
|
+
expression: 'Boolean(window.__pcn && window.__pcn.ready)',
|
|
66
|
+
returnByValue: true,
|
|
67
|
+
});
|
|
68
|
+
return result?.value === true;
|
|
69
|
+
}
|
|
70
|
+
async function waitForExtensionReady(sender) {
|
|
71
|
+
const deadline = Date.now() + PCN_READY_TIMEOUT_MS;
|
|
72
|
+
while (Date.now() < deadline) {
|
|
73
|
+
if (await isExtensionReady(sender)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
await delay(PCN_READY_POLL_MS);
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Route the browser's network traffic through `host:port` via the Popcorn
|
|
82
|
+
* extension's page API. Never throws — a `false` return means the extension
|
|
83
|
+
* couldn't be reached (e.g. this isn't a Popcorn browser); callers still get
|
|
84
|
+
* proxy auth wired up by {@link setupProxyAuthViaCdp} independently, since a
|
|
85
|
+
* caller-supplied proxy might already be configured another way.
|
|
86
|
+
*/
|
|
87
|
+
export async function configureBrowserProxyViaExtension(sender, config) {
|
|
88
|
+
try {
|
|
89
|
+
if (!await isExtensionReady(sender)) {
|
|
90
|
+
// Force the content script to (re-)run so `window.__pcn` appears.
|
|
91
|
+
await sender.send('Page.navigate', { url: 'about:blank' });
|
|
92
|
+
if (!await waitForExtensionReady(sender)) {
|
|
93
|
+
LOGGER.warn('popcorn proxy extension not ready; skipping __pcn.set');
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const { result } = await sender.send('Runtime.evaluate', {
|
|
98
|
+
expression: `(async () => {
|
|
99
|
+
try { await window.__pcn.set(${JSON.stringify(config)}); return true }
|
|
100
|
+
catch(e) { return false }
|
|
101
|
+
})()`,
|
|
102
|
+
awaitPromise: true,
|
|
103
|
+
returnByValue: true,
|
|
104
|
+
});
|
|
105
|
+
return result?.value === true;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
LOGGER.warn({ err }, 'configureBrowserProxyViaExtension failed');
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Clear the extension-configured proxy, routing the browser direct again. */
|
|
113
|
+
export async function clearBrowserProxyViaExtension(sender) {
|
|
114
|
+
try {
|
|
115
|
+
const { result } = await sender.send('Runtime.evaluate', {
|
|
116
|
+
expression: `(async () => {
|
|
117
|
+
try { await window.__pcn.clear(); return true }
|
|
118
|
+
catch(e) { return false }
|
|
119
|
+
})()`,
|
|
120
|
+
awaitPromise: true,
|
|
121
|
+
returnByValue: true,
|
|
122
|
+
});
|
|
123
|
+
return result?.value === true;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
LOGGER.warn({ err }, 'clearBrowserProxyViaExtension failed');
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Answer the proxy's Basic-auth challenge over CDP `Fetch`, since Popcorn's
|
|
132
|
+
* extension only sets host/port routing (not credentials — the proxy vendor
|
|
133
|
+
* still challenges the browser for them the normal way). Self-disables once
|
|
134
|
+
* the first challenge is answered (or after a timeout with none), rather
|
|
135
|
+
* than intercepting every request for the session's whole lifetime.
|
|
136
|
+
*/
|
|
137
|
+
export async function setupProxyAuthViaCdp(sender, credentials) {
|
|
138
|
+
await sender.send('Fetch.enable', { handleAuthRequests: true });
|
|
139
|
+
let settled = false;
|
|
140
|
+
const finish = () => {
|
|
141
|
+
if (settled) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
settled = true;
|
|
145
|
+
unsubAuth();
|
|
146
|
+
unsubPaused();
|
|
147
|
+
sender.send('Fetch.disable').catch(() => { });
|
|
148
|
+
};
|
|
149
|
+
const unsubAuth = sender.subscribe('Fetch.authRequired', (params) => {
|
|
150
|
+
sender.send('Fetch.continueWithAuth', {
|
|
151
|
+
requestId: params.requestId,
|
|
152
|
+
authChallengeResponse: {
|
|
153
|
+
response: 'ProvideCredentials',
|
|
154
|
+
username: credentials.username,
|
|
155
|
+
password: credentials.password,
|
|
156
|
+
},
|
|
157
|
+
}).then(finish).catch(() => { });
|
|
158
|
+
});
|
|
159
|
+
// Requests still need releasing while `Fetch` is enabled, whether or not
|
|
160
|
+
// they end up challenged. Fire-and-forget: serializing behind auth
|
|
161
|
+
// negotiation would stall unrelated requests.
|
|
162
|
+
const unsubPaused = sender.subscribe('Fetch.requestPaused', (params) => {
|
|
163
|
+
sender.send('Fetch.continueRequest', { requestId: params.requestId })
|
|
164
|
+
.catch(() => { });
|
|
165
|
+
});
|
|
166
|
+
setTimeout(finish, PROXY_AUTH_SAFETY_TIMEOUT_MS);
|
|
167
|
+
}
|
|
168
|
+
/** Configure routing + auth + reload, in the right order: routing must be
|
|
169
|
+
* live before the reload navigates, and auth must be armed before that
|
|
170
|
+
* navigation's requests go out. */
|
|
171
|
+
export async function applyBrowserProxy(sender, opts) {
|
|
172
|
+
const appliedViaExtension = await configureBrowserProxyViaExtension(sender, opts);
|
|
173
|
+
await setupProxyAuthViaCdp(sender, opts);
|
|
174
|
+
if (opts.reload !== false) {
|
|
175
|
+
await sender.send('Page.reload', {});
|
|
176
|
+
}
|
|
177
|
+
return { appliedViaExtension };
|
|
178
|
+
}
|
|
179
|
+
/** Clear proxy routing and reload so subsequent requests go direct again. */
|
|
180
|
+
export async function clearBrowserProxy(sender, opts = {}) {
|
|
181
|
+
const cleared = await clearBrowserProxyViaExtension(sender);
|
|
182
|
+
if (opts.reload !== false) {
|
|
183
|
+
await sender.send('Page.reload', {});
|
|
184
|
+
}
|
|
185
|
+
return { cleared };
|
|
186
|
+
}
|
|
@@ -56,9 +56,10 @@ export function isStaticAsset(url) {
|
|
|
56
56
|
return STATIC_ASSET_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
|
|
57
57
|
}
|
|
58
58
|
/** Response Content-Types for static assets. The URL-extension filter misses
|
|
59
|
-
* extension-less bundler/CDN URLs (
|
|
60
|
-
* MIME at response time. `text/html`, `application/json`, and
|
|
61
|
-
* are NOT here — those are the document/API shapes recipes
|
|
59
|
+
* extension-less bundler/CDN URLs (for example, `/assets/3b8f1c`), so we also
|
|
60
|
+
* drop by MIME at response time. `text/html`, `application/json`, and
|
|
61
|
+
* `text/plain` are NOT here — those are the document/API shapes recipes
|
|
62
|
+
* match. */
|
|
62
63
|
const STATIC_ASSET_MIME_EXACT = new Set([
|
|
63
64
|
'text/css',
|
|
64
65
|
'application/javascript', 'text/javascript', 'application/x-javascript',
|
|
@@ -165,9 +166,9 @@ export function createCaptureEngine(opts) {
|
|
|
165
166
|
function onResponseReceived(p) {
|
|
166
167
|
const f = inFlight.get(p.requestId);
|
|
167
168
|
if (!f) {
|
|
168
|
-
// No in-flight entry → the request was skipped (
|
|
169
|
-
// or never seen. Discard any buffered response extra-info so a
|
|
170
|
-
// asset can't leave a dangling entry.
|
|
169
|
+
// No in-flight entry → the request was skipped (for example, a static
|
|
170
|
+
// asset) or never seen. Discard any buffered response extra-info so a
|
|
171
|
+
// skipped asset can't leave a dangling entry.
|
|
171
172
|
pendingResponseExtra.delete(p.requestId);
|
|
172
173
|
return;
|
|
173
174
|
}
|
|
@@ -225,7 +226,7 @@ export function createCaptureEngine(opts) {
|
|
|
225
226
|
}
|
|
226
227
|
catch {
|
|
227
228
|
// Body might be unavailable for
|
|
228
|
-
// some content-types (
|
|
229
|
+
// some content-types (for example, websocket). Skip.
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
}
|
package/lib/cdp/client.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ interface CdpTabEventMap {
|
|
|
19
19
|
'Network.loadingFinished': {
|
|
20
20
|
requestId: string;
|
|
21
21
|
};
|
|
22
|
+
/** Screencast frames, for the live view (`live/viewer.ts`). Each carries a
|
|
23
|
+
* `sessionId` that MUST be acked with `Page.screencastFrameAck`, or CDP
|
|
24
|
+
* stops sending. */
|
|
25
|
+
'Page.screencastFrame': {
|
|
26
|
+
data: string;
|
|
27
|
+
sessionId: number;
|
|
28
|
+
metadata?: {
|
|
29
|
+
timestamp?: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
22
32
|
}
|
|
23
33
|
export interface CdpTabClient {
|
|
24
34
|
send: (method: string, params?: object) => Promise<unknown>;
|