rustenium-identity 0.1.3

A versatile stealth overlay for rustenium
Documentation
use crate::identity::{Browser, Identity, Os};

const PROPERTY_MODIFIER_JS: &str = include_str!("../../js/property_modifier.js");
const MAIN_WORLD_JS: &str = include_str!("../../js/main_world.js");
const BROWSER_CHROME_JS: &str = include_str!("../../js/browser_chrome.js");
const BROWSER_SAFARI_JS: &str = include_str!("../../js/browser_safari.js");
const BROWSER_EDGE_JS: &str = include_str!("../../js/browser_edge.js");

/// Build the full stealth bootstrap JS string with identity values substituted.
pub fn build_stealth_script(identity: &Identity) -> String {
    let lang0 = identity
        .language
        .first()
        .map(|s| s.as_str())
        .unwrap_or("en-US");
    let languages_json =
        serde_json::to_string(&identity.language).unwrap_or_else(|_| r#"["en-US"]"#.into());
    let max_touch = if identity.has_touch { 5 } else { 0 };
    let avail_height = identity.screen.logical_height.saturating_sub(40);
    let charging = !identity.has_battery || identity.has_mouse;
    let charging_time = if identity.has_battery { "Infinity" } else { "0" };
    let discharging_time = if identity.has_battery { "7200" } else { "Infinity" };
    let battery_percentage: u8 = rand::random_range(20..=100);

    let history_block = match identity.history_count {
        Some(count) => format!(
            r#"(function() {{
  var historyCount = {};
  for (var n = 0; n < historyCount; ++n) {{
    if (window.history.length >= historyCount) break;
    window.history.pushState(null, '');
  }}
}})()"#,
            count
        ),
        None => String::new(),
    };

    // On iOS all browsers use WebKit — use Safari block regardless of browser enum
    let is_ios = matches!(identity.os, Os::Ios);
    let browser_block = if is_ios {
        BROWSER_SAFARI_JS.to_string()
    } else {
        match identity.browser {
            Browser::Chrome => BROWSER_CHROME_JS.to_string(),
            Browser::Safari => BROWSER_SAFARI_JS.to_string(),
            Browser::Edge => BROWSER_EDGE_JS.to_string(),
        }
    };

    let script = MAIN_WORLD_JS
        .replace("{{NAVIGATOR_PLATFORM}}", identity.platform.navigator_platform.as_str())
        .replace("{{HARDWARE_CONCURRENCY}}", &identity.hardware_concurrency.to_string())
        .replace("{{MEMORY}}", &identity.memory.to_string())
        .replace("{{LANGUAGES_JSON}}", &languages_json)
        .replace("{{LANGUAGE_0}}", lang0)
        .replace("{{MAX_TOUCH_POINTS}}", &max_touch.to_string())
        .replace("{{LOGICAL_WIDTH}}", &identity.screen.logical_width.to_string())
        .replace("{{AVAIL_HEIGHT}}", &avail_height.to_string())
        .replace("{{WEBGL_VENDOR}}", &escape_js(&identity.gpu.webgl_vendor))
        .replace("{{WEBGL_RENDERER}}", &escape_js(&identity.gpu.webgl_renderer))
        .replace("{{HISTORY_BLOCK}}", &history_block)
        .replace("{{CHARGING}}", &charging.to_string())
        .replace("{{CHARGING_TIME}}", charging_time)
        .replace("{{DISCHARGING_TIME}}", discharging_time)
        .replace("{{BATTERY_PERCENTAGE}}", &battery_percentage.to_string())
        .replace("{{BROWSER_BLOCK}}", &browser_block);

    format!("{}\n{}", PROPERTY_MODIFIER_JS, script)
}

fn escape_js(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('"', "\\\"")
        .replace('\n', "\\n")
}