fix(net,js): route op_fetch_url + ES module loader through proxy_url (#139) (#151)

Fixes the remaining two paths from #139: JS-level fetch()/XHR
(op_fetch_url) and dynamic ES module imports (ObscuraModuleLoader) both
built their reqwest clients with `Client::builder().build()` — no
proxy — so every JS-initiated network call silently bypassed
BrowserContext.proxy_url. (The third path, StealthHttpClient, was
already fixed in main: page.rs:61 uses with_proxy.)

Changes:

* obscura-net: expose ObscuraHttpClient::proxy_url() so callers outside
  the crate can route their own reqwest requests through the same
  upstream proxy. The field was already there — just lacked an accessor.

* obscura-js/module_loader: add `proxy_url: Option<String>` and
  `with_proxy(base_url, proxy_url)`. ObscuraModuleLoader::new keeps the
  pre-#139 direct-connection behaviour. load() builds a reqwest client
  per import, threading the proxy and bubbling up "Invalid module proxy"
  on bad URLs.

* obscura-js/ops.rs: replace the process-wide
  `static SHARED_HTTP_CLIENT: OnceLock<reqwest::Client>` (which could
  never be reconfigured after first call) with a per-request
  `build_request_client(proxy_url)` that pulls the configured proxy out
  of SharedState.http_client.proxy_url() each call. Per-request build
  cost is ~1ms vs the network round-trip, and we no longer have a
  global singleton that lies about its config.

* obscura-js/runtime: add ObscuraJsRuntime::with_base_url_and_proxy.
  with_base_url delegates to it with None so existing tests / callers
  are unaffected.

* obscura-browser/page.rs: thread context.proxy_url through to
  with_base_url_and_proxy when constructing the JS runtime.
This commit is contained in:
Ben Younes authored and GitHub committed 2026-05-14 08:25:55 +00:00
1 parent a750797d15
commit f3c783f6f5
5 files changed
+139 -16

No files matched your search

+30 -2
View File
@@ -10,12 +10,21 @@ use deno_core::RequestedModuleType;
pub struct ObscuraModuleLoader {
pub base_url: String,
/// Proxy URL threaded through to every dynamic ES-module fetch (#139).
/// `None` keeps the pre-#139 direct-connection behaviour for callers
/// that haven't been updated.
pub proxy_url: Option<String>,
}
impl ObscuraModuleLoader {
pub fn new(base_url: &str) -> Self {
Self::with_proxy(base_url, None)
}
pub fn with_proxy(base_url: &str, proxy_url: Option<String>) -> Self {
ObscuraModuleLoader {
base_url: base_url.to_string(),
proxy_url,
}
}
}
@@ -52,13 +61,32 @@ impl ModuleLoader for ObscuraModuleLoader {
_requested_module_type: RequestedModuleType,
) -> ModuleLoadResponse {
let url = module_specifier.to_string();
// Capture the loader's proxy here so the async closure below owns a
// plain Option<String> rather than borrowing &self across an `await`.
let proxy_url = self.proxy_url.clone();
ModuleLoadResponse::Async(Pin::from(Box::new(async move {
let client = reqwest::Client::builder()
let mut builder = reqwest::Client::builder();
if let Some(ref proxy) = proxy_url {
match reqwest::Proxy::all(proxy) {
Ok(p) => builder = builder.proxy(p),
Err(e) => {
return Err(io_err(format!(
"Invalid module proxy '{}': {}",
proxy, e
)));
}
}
}
let client = builder
.build()
.map_err(|e| io_err(format!("HTTP client error: {}", e)))?;
tracing::debug!("Loading ES module: {}", url);
tracing::debug!(
"Loading ES module: {} (proxy: {})",
url,
proxy_url.as_deref().unwrap_or("direct")
);
let resp = client
.get(&url)