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 HARDWARE_JS: &str = include_str!("../../js/hardware.js");
const WORKER_SCOPE_JS: &str = include_str!("../../js/worker_scope.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");
pub fn build_stealth_script(identity: &Identity) -> String {
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(),
};
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 shared: [(&str, String); 6] = [
("{{NAVIGATOR_PLATFORM}}", escape_js(identity.platform.navigator_platform.as_str())),
("{{HARDWARE_CONCURRENCY}}", identity.hardware_concurrency.to_string()),
("{{MEMORY}}", identity.memory.to_string()),
("{{WEBGL_VENDOR}}", escape_js(&identity.gpu.webgl_vendor)),
("{{WEBGL_RENDERER}}", escape_js(&identity.gpu.webgl_renderer)),
("{{USER_AGENT}}", crate::ua::build_user_agent(identity).map(|s| escape_js(&s)).unwrap_or_default()),
];
let apply = |src: &str| {
shared.iter().fold(src.to_string(), |acc, (key, val)| acc.replace(key, val))
};
let script = apply(MAIN_WORLD_JS)
.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);
let hardware = HARDWARE_JS.replace("{{FP_SEED}}", &fingerprint_seed(identity).to_string());
format!(
"(function() {{\n{}\n;\n{}\n;\n{}\n}})();",
PROPERTY_MODIFIER_JS, script, hardware
)
}
pub fn build_worker_script(identity: &Identity) -> String {
let shared: [(&str, String); 6] = [
("{{NAVIGATOR_PLATFORM}}", escape_js(identity.platform.navigator_platform.as_str())),
("{{HARDWARE_CONCURRENCY}}", identity.hardware_concurrency.to_string()),
("{{MEMORY}}", identity.memory.to_string()),
("{{WEBGL_VENDOR}}", escape_js(&identity.gpu.webgl_vendor)),
("{{WEBGL_RENDERER}}", escape_js(&identity.gpu.webgl_renderer)),
("{{USER_AGENT}}", crate::ua::build_user_agent(identity).map(|s| escape_js(&s)).unwrap_or_default()),
];
let worker_scope = shared
.iter()
.fold(WORKER_SCOPE_JS.to_string(), |acc, (key, val)| acc.replace(key, val));
format!(
"(function() {{\n{}\n;\n{}\n}})();",
PROPERTY_MODIFIER_JS, worker_scope
)
}
pub(crate) fn fingerprint_seed(identity: &Identity) -> u32 {
let material = format!(
"{:?}|{}|{}|{}|{}|{}x{}|{}|{}|{}",
identity.os,
identity.os_version,
identity.gpu.webgl_vendor,
identity.gpu.webgl_renderer,
identity.id.unwrap_or(0),
identity.screen.original_width,
identity.screen.original_height,
identity.hardware_concurrency,
identity.memory,
identity.platform.navigator_platform.as_str(),
);
let mut h: u32 = 0x811c_9dc5;
for b in material.as_bytes() {
h ^= *b as u32;
h = h.wrapping_mul(0x0100_0193);
}
h
}
fn escape_js(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
}