@olimsaidov/icdp 0.2.1 → 0.3.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/README.md CHANGED
@@ -1,124 +1,11 @@
1
1
  # icdp
2
2
 
3
- Chrome DevTools Protocol over an iframe boundary. External CDP tools (agent-browser, chrome-remote-interface, Playwright best-effort) drive and inspect an app embedded in an iframe — including a **cross-origin** iframe without a real browser debugging session.
3
+ Chrome DevTools Protocol over an iframe boundary drive and inspect an embedded, even **cross-origin**, app with CDP tools, without a real browser debugging session.
4
4
 
5
- ```
6
- agent-browser / CDP client
7
- │ WebSocket (standard CDP, flat sessions)
8
-
9
- Relay ← @olimsaidov/icdp/relay (+ /relay/node) server
10
- │ WebSocket (bridge protocol)
11
-
12
- Host ← @olimsaidov/icdp/host parent window
13
- │ MessagePort (per iframe)
14
-
15
- Frame Agent ← @olimsaidov/icdp/frame inside the iframe'd app
16
- ```
17
-
18
- See `CONTEXT.md` for the project language (Frame Agent, Host, Relay, Client, Target, Pairing) and `docs/adr/` for architectural decisions.
19
-
20
- ## The three pieces
21
-
22
- ### Frame Agent — in the embedded app
23
-
24
- The app under automation includes the agent itself (cooperative embedding — the Host never injects):
25
-
26
- ```ts
27
- import { startFrameAgent } from "@olimsaidov/icdp/frame";
28
-
29
- startFrameAgent({ allowedParents: ["https://shell.example.com"] });
30
- ```
31
-
32
- The agent announces itself to the parent on boot and stays dormant unless the parent's origin is allowlisted. `allowedParents: "*"` hands DOM read/write/eval to **any** embedder — only for sandboxed or throwaway pages.
33
-
34
- ### Host — in the parent window
35
-
36
- ```ts
37
- import { IcdpHost } from "@olimsaidov/icdp/host";
38
-
39
- const host = new IcdpHost();
40
- host.pair(iframeElement, { targetId: "preview", origins: ["https://app.example.com"] });
41
-
42
- // Local consumption — no server needed (e.g. a console panel):
43
- const session = host.attach("preview");
44
- session.onEvent((method, params) => {
45
- /* Runtime.consoleAPICalled, ... */
46
- });
47
- await session.send("Runtime.enable");
48
-
49
- // Forward everything to a Relay so external tools can connect:
50
- const disconnect = host.connectRelay({ url: "ws://localhost:9222/icdp/host" });
51
- ```
52
-
53
- Target identity belongs to the Pairing: reloads and navigations keep the same `targetId` (Clients see `Page.frameNavigated`); commands in flight when a document dies fail fast with `-32000`. The Relay uplink is just another consumer of the same hub — events broadcast to all attached sessions, domain enables are ref-counted.
54
-
55
- #### Client-driven target lifecycle (optional)
56
-
57
- By default only the Host creates Targets (via `pair()`), so a Client's `Target.createTarget` is rejected and `Target.closeTarget` is a no-op. Pass `onCreateTarget` / `onCloseTarget` to let a Client open and close Targets itself:
58
-
59
- ```ts
60
- const host = new IcdpHost({
61
- onCreateTarget: ({ url }) => {
62
- const iframe = document.createElement("iframe");
63
- iframe.src = url ?? "about:blank";
64
- document.body.append(iframe);
65
- const targetId = crypto.randomUUID();
66
- host.pair(iframe, { targetId, origins: ["https://app.example.com"] });
67
- return targetId; // string | Promise<string>
68
- },
69
- onCloseTarget: (targetId) => host.unpair(targetId), // + remove the iframe you made
70
- });
71
- ```
72
-
73
- The Host advertises which of these it handles, so the Relay forwards only those and keeps its defaults for any hook you leave unset (existing Hosts are unaffected). `createTarget` resolves **only once the new Target completes its handshake**, so the Client's first command can't race the not-connected gate; a Target that never connects (timeout or early destroy) is torn down rather than left as a zombie. A bare `new IcdpHost(window)` is still accepted for back-compat.
74
-
75
- ### Relay — the server
76
-
77
- ```ts
78
- import { serveRelay } from "@olimsaidov/icdp/relay/node";
79
-
80
- const relay = await serveRelay({ port: 9222 });
81
- console.log(relay.browserWsUrl); // ws://127.0.0.1:9222/devtools/browser ← CDP clients
82
- console.log(relay.hostWsUrl); // ws://127.0.0.1:9222/icdp/host ← Host uplink
83
- ```
84
-
85
- The Node adapter is built on `node:http` + `ws`. The runtime-agnostic core (`@olimsaidov/icdp/relay` → `RelayCore`) takes plain `{ send, close }` sockets, so other runtimes only need a thin adapter. HTTP discovery: `/json/version`, `/json/list`, `/icdp/status`.
86
-
87
- One Host per Relay, new-wins: a newly connecting Host replaces a stale one, with `targetDestroyed`/`targetCreated` churn surfaced to attached Clients.
88
-
89
- ## Protocol shape
90
-
91
- - **Flat sessions only.** Clients connect to the single browser-level endpoint and use `Target.getTargets` / `Target.attachToTarget` (or `Target.setAutoAttach`) + `sessionId` routing. There are no per-target WebSocket URLs. Session-scoped `Target.*`/`Browser.*` housekeeping (e.g. agent-browser's session-scoped `Target.setAutoAttach`) is answered by the Relay; the Frame Agent never sees it. Registry methods (`getTargets`/`attachToTarget`/`setAutoAttach`) are always Relay-owned; only the `Target.createTarget`/`Target.closeTarget` lifecycle can be delegated to the Host (see [Client-driven target lifecycle](#client-driven-target-lifecycle-optional)).
92
- - **Compatibility bar: agent-browser.** The supported command surface is the prior art's support matrix (AX-tree snapshots, semantic locators, click/fill/type, eval, waits, console, SPA history). Screenshots, PDF, file uploads, drag-and-drop, dialogs, and real network interception are intentionally out — page JavaScript cannot provide them. Raw Playwright over `connectOverCDP` is best-effort, not promised.
93
-
94
- ## Driving an icdp target with agent-browser
95
-
96
- Use the per-command `--cdp <relay port>` flag, and issue one `wait` first to sync agent-browser's page model from the live target:
97
-
98
- ```sh
99
- agent-browser --cdp 9222 wait --text "My App" # first command: syncs the model
100
- agent-browser --cdp 9222 snapshot -i
101
- agent-browser --cdp 9222 find role button click --name "Save"
102
- ```
103
-
104
- Do not use `agent-browser connect <ws-url>`: as of agent-browser 0.27.x the session-bound connect no longer routes follow-up commands over the connection (this reproduces against real Chrome too, both browser and page endpoints — it is not an icdp limitation).
105
-
106
- ## Playground
107
-
108
- `npm run playground` starts a runnable demo of the full topology — see [playground/README.md](./playground/README.md).
109
-
110
- ## Development
111
-
112
- Node ≥22, VoidZero tooling (Vitest, oxlint, oxfmt, tsdown/Rolldown):
5
+ **📖 [Documentation](https://olimsaidov.github.io/icdp/)** · [npm](https://www.npmjs.com/package/@olimsaidov/icdp)
113
6
 
114
7
  ```sh
115
- npm install
116
- npm test # unit + in-process integration (vitest)
117
- npm run test:e2e # conformance suite (needs agent-browser CLI + Chrome, ~90s)
118
- npm run test:all # everything
119
- npm run check # tsc + oxlint + oxfmt --check
120
- npm run fmt # oxfmt
121
- npm run build # tsdown -> dist/
8
+ npm install @olimsaidov/icdp
122
9
  ```
123
10
 
124
- The e2e conformance suite (ported from the prior art) drives the full chain with a real browser: agent-browser opens a shell page whose Host pairs with a **cross-origin** iframe running the Frame Agent, uplinked to a real Relay; a second agent-browser session then exercises snapshots, semantic locators, keyboard/mouse, navigation, and graceful failures through `--cdp`. Set `ICDP_DEBUG=1` to log all relay traffic.
11
+ MIT