diff --git a/crates/obscura-browser/src/page.rs b/crates/obscura-browser/src/page.rs index 3acce7f..457a3a5 100644 --- a/crates/obscura-browser/src/page.rs +++ b/crates/obscura-browser/src/page.rs @@ -9,6 +9,22 @@ use url::Url; use crate::context::BrowserContext; use crate::lifecycle::LifecycleState; +/// Parse `OBSCURA_GEOLOCATION="lat,lon"` for the navigator.geolocation shim. +/// Returns None when unset or malformed, leaving the built-in default in place. +/// Lets a deployment align the reported coordinates with the region its exit IP +/// resolves to, so timezone and location stay consistent (issue #228). +fn env_geolocation() -> Option<(f64, f64)> { + let raw = std::env::var("OBSCURA_GEOLOCATION").ok()?; + let (lat, lon) = raw.split_once(',')?; + let lat: f64 = lat.trim().parse().ok()?; + let lon: f64 = lon.trim().parse().ok()?; + let valid = lat.is_finite() + && lon.is_finite() + && (-90.0..=90.0).contains(&lat) + && (-180.0..=180.0).contains(&lon); + valid.then_some((lat, lon)) +} + fn decode_data_uri(uri: &str) -> Option> { let rest = uri.strip_prefix("data:")?; let comma = rest.find(',')?; @@ -272,6 +288,9 @@ impl Page { &self.context.ua_platform, &self.context.ua_platform_version, ); + if let Some((lat, lon)) = env_geolocation() { + rt.set_geolocation(lat, lon); + } rt.set_cookie_jar(self.context.cookie_jar.clone()); rt.set_http_client(self.http_client.clone()); diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 006388d..34b4278 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -2447,8 +2447,8 @@ globalThis.navigator = { geolocation: { getCurrentPosition(success, error) { const coords = { - latitude: 50.1109 + (_fpRand(500) - 0.5) * 0.1, - longitude: 8.6821 + (_fpRand(501) - 0.5) * 0.1, + latitude: (globalThis.__obscura_geo_lat ?? 50.1109) + (_fpRand(500) - 0.5) * 0.1, + longitude: (globalThis.__obscura_geo_lon ?? 8.6821) + (_fpRand(501) - 0.5) * 0.1, accuracy: 10 + _fpRand(502) * 40, altitude: null, altitudeAccuracy: null, @@ -2461,8 +2461,8 @@ globalThis.navigator = { watchPosition(success, error) { if (typeof success === 'function') { const coords = { - latitude: 50.1109 + (_fpRand(503) - 0.5) * 0.1, - longitude: 8.6821 + (_fpRand(504) - 0.5) * 0.1, + latitude: (globalThis.__obscura_geo_lat ?? 50.1109) + (_fpRand(503) - 0.5) * 0.1, + longitude: (globalThis.__obscura_geo_lon ?? 8.6821) + (_fpRand(504) - 0.5) * 0.1, accuracy: 10 + _fpRand(505) * 40, altitude: null, altitudeAccuracy: null, diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index ce89619..fecde47 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -219,6 +219,20 @@ impl ObscuraJsRuntime { ), ); } + + /// Override the coordinates the navigator.geolocation shim reports. The + /// values are injected as numeric globals the bootstrap reads; when unset it + /// keeps the built-in default. Callers validate the range before calling. + pub fn set_geolocation(&mut self, latitude: f64, longitude: f64) { + let _ = self.runtime.execute_script( + "", + format!( + "globalThis.__obscura_geo_lat={};globalThis.__obscura_geo_lon={};", + latitude, longitude + ), + ); + } + pub fn evaluate(&mut self, expression: &str) -> Result { let wrapped = Self::wrap_expression(expression); let result = self