# Adding a CDP method or Web API

Two recipes for the two most common extensions: a new CDP method, and a new JS Web API.

### Adding a CDP method

Worked example: `MyDomain.doThing` that takes `{ name }` and returns `{ ok }`.

#### 1. Add the handler

Create or edit a file under `crates/obscura-cdp/src/domains/`:

```rust
// crates/obscura-cdp/src/domains/my_domain.rs
use serde_json::{json, Value};
use crate::dispatch::CdpContext;

pub async fn do_thing(
    params: &Value,
    _ctx: &mut CdpContext,
    _session_id: &Option<String>,
) -> Result<Value, String> {
    let name = params.get("name")
        .and_then(|v| v.as_str())
        .ok_or("missing name")?;

    // do the work

    Ok(json!({ "ok": true, "name": name }))
}
```

#### 2. Register in the dispatcher

In `crates/obscura-cdp/src/dispatch.rs`, add a match arm:

```rust
"MyDomain.doThing" => domains::my_domain::do_thing(&req.params, ctx, &req.session_id).await,
```

#### 3. Test it

`crates/obscura-cdp/tests/cdp_my_domain.rs`:

```rust
use obscura_cdp::dispatch::{dispatch, CdpContext};
use obscura_cdp::types::CdpRequest;
use serde_json::json;

#[tokio::test(flavor = "current_thread")]
async fn my_domain_do_thing_returns_ok() {
    let mut ctx = CdpContext::new();
    let resp = dispatch(&CdpRequest {
        id: 1,
        method: "MyDomain.doThing".into(),
        params: json!({ "name": "test" }),
        session_id: None,
    }, &mut ctx).await;

    assert!(resp.error.is_none());
    assert_eq!(resp.result.unwrap()["ok"], true);
}
```

Run:

```bash
cargo test -p obscura-cdp my_domain
```

### Adding a Web API

Worked example: `crypto.subtle.digest`, real implementation backed by a Rust hash op.

#### 1. Add the Rust op

In `crates/obscura-js/src/ops.rs`:

```rust
#[op2]
#[buffer]
fn op_subtle_digest(#[string] algorithm: &str, #[buffer] data: &[u8]) -> Vec<u8> {
    use sha1::Digest as _;
    match algorithm.to_ascii_uppercase().as_str() {
        "SHA-1"   => sha1::Sha1::digest(data).to_vec(),
        "SHA-256" => sha2::Sha256::digest(data).to_vec(),
        "SHA-384" => sha2::Sha384::digest(data).to_vec(),
        "SHA-512" => sha2::Sha512::digest(data).to_vec(),
        _         => sha2::Sha256::digest(data).to_vec(),
    }
}
```

#### 2. Register the op

In the same file, `build_extension()`:

```rust
ops: std::borrow::Cow::Owned(vec![
    op_dom(),
    op_console_msg(),
    // ...
    op_subtle_digest(),
]),
```

#### 3. Add the JS shim

In `crates/obscura-js/js/bootstrap.js`:

```js
globalThis.crypto = globalThis.crypto || {};
globalThis.crypto.subtle = globalThis.crypto.subtle || {};
globalThis.crypto.subtle.digest = function digest(algorithm, data) {
  const algName = typeof algorithm === 'string' ? algorithm : algorithm.name;
  const bytes = data instanceof ArrayBuffer
    ? new Uint8Array(data)
    : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
  const out = Deno.core.ops.op_subtle_digest(algName, bytes);
  return Promise.resolve(out.buffer);
};
```

#### 4. Add a dependency if needed

`crates/obscura-js/Cargo.toml`:

```toml
sha1 = "0.10"
sha2 = "0.10"
```

#### 5. Smoke test

```bash
cargo build --release
./target/release/obscura fetch https://example.com --eval "
  crypto.subtle.digest('SHA-256', new TextEncoder().encode('hi'))
    .then(buf => Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(''))
"
```

### Tips

* Keep the JS shim thin. All side effects go through ops.
* Use `Promise.resolve` to keep async-shaped APIs callable from sync ops.
* Match the spec: Web API names and shapes are checked by Puppeteer / Playwright wrappers.
* DOM mutations go through `op_dom`, not new ops.
* For events that need to fire across handlers, use the existing `_makeListenerBox` helper in `bootstrap.js`.

### Worked examples in the tree

* CDP method with intercept: `crates/obscura-cdp/src/domains/page.rs` `do_navigate`.
* Web API with op + JS shim: `crypto.subtle.digest` (above).
* Web API in pure JS (no op): `DOMParser` in `bootstrap.js`.
* Web API with async event firing: `WebSocket`, `IntersectionObserver` in `bootstrap.js`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.obscura.sh/contributing/adding-a-cdp-method-or-web-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
