Skip to main content

codetether_agent/browser/request/
net.rs

1//! Network request types for browser-resident HTTP replay and inspection.
2//!
3//! - [`NetworkLogRequest`] reads entries captured by the in-page `fetch`/
4//!   `XMLHttpRequest` wrappers installed in `install_page_hooks`. The agent
5//!   uses this to discover the exact headers (notably `Authorization:
6//!   Bearer …`) the page sent.
7//! - [`FetchRequest`] replays an HTTP request from inside the active tab
8//!   via `page.evaluate(fetch(…))`. Because the request runs in the page's
9//!   own JS context, cookies, TLS fingerprint, and Origin all match what a
10//!   real user click would produce.
11
12use serde::Serialize;
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Serialize)]
16pub struct NetworkLogRequest {
17    pub limit: Option<usize>,
18    pub url_contains: Option<String>,
19    pub method: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct FetchRequest {
24    pub method: String,
25    pub url: String,
26    pub headers: Option<HashMap<String, String>>,
27    pub body: Option<String>,
28    /// `omit` | `same-origin` | `include` — default `include` so cookies
29    /// and HttpOnly session tokens travel with the request.
30    pub credentials: Option<String>,
31}
32
33/// Replay an HTTP request through the page's own axios instance.
34///
35/// Unlike [`FetchRequest`], this reuses every interceptor the app has
36/// installed (auth header injection, CSRF tokens, baseURL rewrites,
37/// request IDs), which is often the only way to reproduce a request that
38/// `fetch` rejects with "Failed to fetch" due to CORS / preflight /
39/// service-worker routing differences.
40///
41/// `axios_path` lets the caller override the discovery lookup (e.g.
42/// `"window.__APP__.api"` or `"window.axios"`); when `None`, the handler
43/// auto-discovers the first object on `window` that looks like an axios
44/// instance (has `.defaults.baseURL` or `.defaults.headers.common`).
45#[derive(Debug, Clone, Serialize)]
46pub struct AxiosRequest {
47    pub method: String,
48    pub url: String,
49    pub headers: Option<HashMap<String, String>>,
50    pub body: Option<serde_json::Value>,
51    pub axios_path: Option<String>,
52}
53
54/// Diagnostic snapshot of the page's HTTP plumbing.
55///
56/// Returns a JSON blob describing service workers, discovered axios
57/// instances (with their `baseURL` and a sample of common headers),
58/// recently-seen request initiators, and document CSP. Useful when
59/// `fetch` replay fails with a transport-layer error and the agent
60/// needs to decide whether to fall back to axios, hook the app's save
61/// function, or call the worker directly.
62#[derive(Debug, Clone, Serialize, Default)]
63pub struct DiagnoseRequest {}
64
65/// Replay an HTTP request through a raw `XMLHttpRequest` inside the page.
66///
67/// Use this when [`FetchRequest`] returns "Failed to fetch" but
68/// `network_log` shows the app's own successful request had `kind: xhr`.
69/// The XHR transport differs from `fetch` in several ways that matter for
70/// WAF / CORS / service-worker routing:
71///
72/// - XHR does not send `Sec-Fetch-Mode: cors` / `Sec-Fetch-Dest: empty`
73///   headers the same way, which some edge rules use to block tool replays.
74/// - XHR inherits the document's full cookie jar and Origin by default;
75///   there is no `credentials: 'omit'` equivalent.
76/// - Service workers often pass XHR through untouched while intercepting
77///   `fetch`, so a SW-rewriting auth header won't affect this path.
78/// - Simple XHRs (GET/POST with allowlisted headers) skip CORS preflight
79///   on the same rules as the original page script.
80///
81/// Request body is sent verbatim; set `Content-Type` explicitly in
82/// `headers` when sending JSON.
83#[derive(Debug, Clone, Serialize)]
84pub struct XhrRequest {
85    pub method: String,
86    pub url: String,
87    pub headers: Option<HashMap<String, String>>,
88    pub body: Option<String>,
89    /// When true (default), sets `xhr.withCredentials = true` so cookies
90    /// and `Authorization` travel cross-origin. Set false to mimic a
91    /// public-asset request.
92    pub with_credentials: Option<bool>,
93}
94
95/// Replay a request captured in `window.__codetether_net_log` with
96/// optional edits.
97///
98/// Finds the most recent entry whose URL contains [`url_contains`]
99/// (and, optionally, matches [`method_filter`]), inherits its
100/// captured method, URL, and request headers (including
101/// `Authorization`), and re-fires via raw XHR. The captured request
102/// body is used as-is unless one of:
103///
104/// - [`body_override`] — replaces the body with an arbitrary string
105/// - [`body_patch`] — deep-merged into the captured body when it
106///   parses as JSON (other keys in the captured body are preserved)
107///
108/// [`url_override`] / [`method_override`] replace the captured URL /
109/// method if set. Headers supplied via [`extra_headers`] are overlaid
110/// on top of the captured headers.
111///
112/// This exists so the agent can perform the common "capture one real
113/// save, then re-save with different fields" workflow without
114/// reconstructing the request from scratch.
115///
116/// [`url_contains`]: Self::url_contains
117/// [`method_filter`]: Self::method_filter
118/// [`url_override`]: Self::url_override
119/// [`method_override`]: Self::method_override
120/// [`body_override`]: Self::body_override
121/// [`body_patch`]: Self::body_patch
122/// [`extra_headers`]: Self::extra_headers
123#[derive(Debug, Clone, Serialize)]
124pub struct ReplayRequest {
125    pub url_contains: String,
126    pub method_filter: Option<String>,
127    pub url_override: Option<String>,
128    pub method_override: Option<String>,
129    pub body_patch: Option<serde_json::Value>,
130    pub body_override: Option<String>,
131    pub extra_headers: Option<HashMap<String, String>>,
132    pub with_credentials: Option<bool>,
133}