fix(serve): plumb --user-agent through CDP server (#71) (#76)

obscura serve --user-agent <ua> only logged the value via tracing::info!
and never passed it to the CDP server, so every navigation kept using
the default Chrome UA string and httpbin.org/user-agent reported the
default rather than the override.

Plumb the option end-to-end:

- BrowserContext::with_full_options(id, proxy, stealth, user_agent)
  initializes both BrowserContext::user_agent and the underlying
  ObscuraHttpClient::user_agent RwLock at construction time so the
  first navigation already sees the override.
- CdpContext::new_with_full_options forwards the UA to the default
  BrowserContext.
- obscura_cdp::start_with_full_options accepts the UA and passes it
  through cdp_processor.
- main.rs Serve arm calls start_with_full_options with the parsed UA.
- run_multi_worker_serve forwards --user-agent <ua> when spawning
  worker subprocesses so multi-worker mode honors the flag too.

Existing two-arg start_with_options and three-arg with_options stay
as thin shims (call the full variant with None) so older callers and
the public crate API remain source-compatible.

Three new unit tests in obscura-browser cover the propagation, the
Chrome default fallback, and the legacy with_options path.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
This commit is contained in:
Ben YounesandOra Studio authored and GitHub committed 2026-05-03 17:32:38 +02:00
1 parent 9d8d3a02a9
commit 536072b323
5 files changed
+86 -7

No files matched your search

+10 -1
View File
@@ -39,10 +39,19 @@ impl CdpContext {
}
pub fn new_with_options(proxy: Option<String>, stealth: bool) -> Self {
let default_context = Arc::new(BrowserContext::with_options(
Self::new_with_full_options(proxy, stealth, None)
}
pub fn new_with_full_options(
proxy: Option<String>,
stealth: bool,
user_agent: Option<String>,
) -> Self {
let default_context = Arc::new(BrowserContext::with_full_options(
"default".to_string(),
proxy,
stealth,
user_agent,
));
CdpContext {
pages: Vec::new(),
+1 -1
View File
@@ -3,4 +3,4 @@ pub mod dispatch;
pub mod types;
pub mod domains;
pub use server::{start, start_with_options};
pub use server::{start, start_with_full_options, start_with_options};
+12 -2
View File
@@ -31,6 +31,15 @@ pub async fn start_with_options(
port: u16,
proxy: Option<String>,
stealth: bool,
) -> anyhow::Result<()> {
start_with_full_options(port, proxy, stealth, None).await
}
pub async fn start_with_full_options(
port: u16,
proxy: Option<String>,
stealth: bool,
user_agent: Option<String>,
) -> anyhow::Result<()> {
let addr = SocketAddr::from(([127, 0, 0, 1], port));
let listener = TcpListener::bind(&addr).await?;
@@ -46,7 +55,7 @@ pub async fn start_with_options(
.run_until(async {
let (msg_tx, msg_rx) = mpsc::unbounded_channel::<ServerMessage>();
let processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth));
let processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth, user_agent));
loop {
match listener.accept().await {
@@ -72,8 +81,9 @@ async fn cdp_processor(
mut rx: mpsc::UnboundedReceiver<ServerMessage>,
proxy: Option<String>,
stealth: bool,
user_agent: Option<String>,
) {
let mut ctx = CdpContext::new_with_options(proxy, stealth);
let mut ctx = CdpContext::new_with_full_options(proxy, stealth, user_agent);
let (itx, irx) = mpsc::unbounded_channel::<obscura_js::ops::InterceptedRequest>();
ctx.intercept_tx = Some(itx);
let mut intercept_rx: Option<mpsc::UnboundedReceiver<obscura_js::ops::InterceptedRequest>> = Some(irx);