README.md

# cloaked_req

`cloaked_req` is a Req adapter backed by Rust [`wreq`](https://docs.rs/wreq/latest/wreq/), focused on browser impersonation and performance.

## Goal

Keep Req ergonomics while swapping transport to Rust `wreq` for impersonation and fingerprint-sensitive requests.

## Installation

```elixir
def deps do
  [
    {:cloaked_req, "~> 0.3.1"}
  ]
end
```

## Usage

Use as a Req adapter:

```elixir
request =
  Req.new(url: "https://tls.peet.ws/api/all")
  |> CloakedReq.attach(impersonate: :chrome_136)

response = Req.get!(request)
```

Set impersonation later on an existing request:

```elixir
request =
  Req.new(url: "https://example.com")
  |> CloakedReq.impersonate(:firefox_136)
```

## Adapter Options

| Option                  | Type                        | Default | Description                                  |
| ----------------------- | --------------------------- | ------- | -------------------------------------------- |
| `:impersonate`          | atom                        | `nil`   | Browser profile (e.g. `:chrome_136`)         |
| `:cookie_jar`           | `CookieJar.t()`             | `nil`   | Automatic cookie persistence across requests |
| `:insecure_skip_verify` | boolean                     | `false` | Skip TLS certificate verification            |
| `:local_address`        | IP string or IP tuple       | `nil`   | Bind outbound requests to a specific source IP |
| `:max_body_size`        | pos_integer \| `:unlimited` | 10 MB   | Max response body size                       |

Req's `:receive_timeout` (default 15s) is also respected.

```elixir
Req.new(url: "https://example.com")
|> CloakedReq.attach(local_address: {127, 0, 0, 1})
```

### Cookie Jar

Cookies are automatically stored from `set-cookie` response headers and sent with subsequent requests sharing the same jar. The jar uses PSL-based domain validation — it rejects cookies set on public suffixes and cross-origin domains.

```elixir
jar = CloakedReq.CookieJar.new()

# Login — server sets session cookie
Req.new(url: "https://example.com/login")
|> CloakedReq.attach(impersonate: :chrome_136, cookie_jar: jar)
|> Req.post!(body: "user=admin&pass=secret")

# Dashboard — session cookie sent automatically
Req.new(url: "https://example.com/dashboard")
|> CloakedReq.attach(impersonate: :chrome_136, cookie_jar: jar)
|> Req.get!()
```

## Impersonation Profiles

Profiles based on `wreq-util 3.0.0-rc.10`.

### Chrome

`:chrome_100`, `:chrome_101`, `:chrome_104`, `:chrome_105`, `:chrome_106`, `:chrome_107`, `:chrome_108`, `:chrome_109`, `:chrome_110`, `:chrome_114`, `:chrome_116`, `:chrome_117`, `:chrome_118`, `:chrome_119`, `:chrome_120`, `:chrome_123`, `:chrome_124`, `:chrome_126`, `:chrome_127`, `:chrome_128`, `:chrome_129`, `:chrome_130`, `:chrome_131`, `:chrome_132`, `:chrome_133`, `:chrome_134`, `:chrome_135`, `:chrome_136`, `:chrome_137`, `:chrome_138`, `:chrome_139`, `:chrome_140`, `:chrome_141`, `:chrome_142`, `:chrome_143`, `:chrome_144`, `:chrome_145`

### Edge

`:edge_101`, `:edge_122`, `:edge_127`, `:edge_131`, `:edge_134`, `:edge_135`, `:edge_136`, `:edge_137`, `:edge_138`, `:edge_139`, `:edge_140`, `:edge_141`, `:edge_142`, `:edge_143`, `:edge_144`, `:edge_145`

### Opera

`:opera_116`, `:opera_117`, `:opera_118`, `:opera_119`

### Firefox

`:firefox_109`, `:firefox_117`, `:firefox_128`, `:firefox_133`, `:firefox_135`, `:firefox_private_135`, `:firefox_android_135`, `:firefox_136`, `:firefox_private_136`, `:firefox_139`, `:firefox_142`, `:firefox_143`, `:firefox_144`, `:firefox_145`, `:firefox_146`, `:firefox_147`

### Safari

`:safari_16`, `:safari_18`, `:safari_ipad_18`, `:safari_26`, `:safari_ipad_26`, `:safari_ios_26`

### OkHttp

`:okhttp_5`

## Limitations

- **No HTTP/3 / QUIC** — wreq supports HTTP/1.1 and HTTP/2 only. QUIC transport fingerprinting (JA4QUIC) is not available. If HTTP/3 fingerprinting is critical for your use case, consider a Go-based alternative like [surf](https://github.com/enetx/surf) which supports HTTP/3 with full QUIC fingerprinting — though it would require a different integration approach (sidecar/Port rather than NIF).

## Benchmark

50 sequential GET requests to a local HTTP server, 3 warmup rounds. Measures pure adapter overhead without network variance.

```bash
CLOAKED_REQ_BUILD=1 mix run bench/adapter_perf.exs
```

### Run 1

| Adapter               | min     | median  | mean    | p99     | max     |
| --------------------- | ------- | ------- | ------- | ------- | ------- |
| Req (Finch)           | 0.13 ms | 0.14 ms | 0.15 ms | 0.34 ms | 0.34 ms |
| CloakedReq (wreq NIF) | 0.05 ms | 0.06 ms | 0.07 ms | 0.17 ms | 0.17 ms |

CloakedReq median is 54.7 % faster than Req.

### Run 2

| Adapter               | min     | median  | mean    | p99     | max     |
| --------------------- | ------- | ------- | ------- | ------- | ------- |
| Req (Finch)           | 0.11 ms | 0.15 ms | 0.16 ms | 0.36 ms | 0.36 ms |
| CloakedReq (wreq NIF) | 0.07 ms | 0.08 ms | 0.09 ms | 0.24 ms | 0.24 ms |

CloakedReq median is 43.5 % faster than Req.

### Run 3

| Adapter               | min     | median  | mean    | p99     | max     |
| --------------------- | ------- | ------- | ------- | ------- | ------- |
| Req (Finch)           | 0.12 ms | 0.14 ms | 0.16 ms | 0.35 ms | 0.35 ms |
| CloakedReq (wreq NIF) | 0.07 ms | 0.09 ms | 0.09 ms | 0.25 ms | 0.25 ms |

CloakedReq median is 41.0 % faster than Req.