rustenium_identity/script/
mod.rs1use crate::identity::{Browser, Identity, Os};
2
3const PROPERTY_MODIFIER_JS: &str = include_str!("../../js/property_modifier.js");
4const MAIN_WORLD_JS: &str = include_str!("../../js/main_world.js");
5const HARDWARE_JS: &str = include_str!("../../js/hardware.js");
6const WORKER_SCOPE_JS: &str = include_str!("../../js/worker_scope.js");
7const BROWSER_CHROME_JS: &str = include_str!("../../js/browser_chrome.js");
8const BROWSER_SAFARI_JS: &str = include_str!("../../js/browser_safari.js");
9const BROWSER_EDGE_JS: &str = include_str!("../../js/browser_edge.js");
10
11pub fn build_stealth_script(identity: &Identity) -> String {
13 let lang0 = identity
14 .language
15 .first()
16 .map(|s| s.as_str())
17 .unwrap_or("en-US");
18 let languages_json =
19 serde_json::to_string(&identity.language).unwrap_or_else(|_| r#"["en-US"]"#.into());
20 let charging = !identity.has_battery || identity.has_mouse;
21 let charging_time = if identity.has_battery { "Infinity" } else { "0" };
22 let discharging_time = if identity.has_battery { "7200" } else { "Infinity" };
23 let battery_percentage: u8 = rand::random_range(20..=100);
24
25 let history_block = match identity.history_count {
32 Some(count) => format!(
33 r#"(function() {{
34 var historyCount = {};
35 for (var n = 0; n < historyCount; ++n) {{
36 if (window.history.length >= historyCount) break;
37 window.history.pushState(null, '');
38 }}
39}})();"#,
40 count
41 ),
42 None => String::new(),
43 };
44
45 let is_ios = matches!(identity.os, Os::Ios);
47 let browser_block = if is_ios {
48 BROWSER_SAFARI_JS.to_string()
49 } else {
50 match identity.browser {
51 Browser::Chrome => BROWSER_CHROME_JS.to_string(),
52 Browser::Safari => BROWSER_SAFARI_JS.to_string(),
53 Browser::Edge => BROWSER_EDGE_JS.to_string(),
54 }
55 };
56
57 let shared: [(&str, String); 8] = [
61 ("{{NAVIGATOR_PLATFORM}}", escape_js(identity.platform.navigator_platform.as_str())),
62 ("{{HARDWARE_CONCURRENCY}}", identity.hardware_concurrency.to_string()),
63 ("{{MEMORY}}", identity.memory.to_string()),
64 ("{{LANGUAGES_JSON}}", languages_json),
65 ("{{LANGUAGE_0}}", escape_js(lang0)),
66 ("{{WEBGL_VENDOR}}", escape_js(&identity.gpu.webgl_vendor)),
67 ("{{WEBGL_RENDERER}}", escape_js(&identity.gpu.webgl_renderer)),
68 ("{{USER_AGENT}}", crate::ua::build_user_agent(identity).map(|s| escape_js(&s)).unwrap_or_default()),
70 ];
71 let apply = |src: &str| {
72 shared.iter().fold(src.to_string(), |acc, (key, val)| acc.replace(key, val))
73 };
74
75 let script = apply(MAIN_WORLD_JS)
76 .replace("{{HISTORY_BLOCK}}", &history_block)
77 .replace("{{CHARGING}}", &charging.to_string())
78 .replace("{{CHARGING_TIME}}", charging_time)
79 .replace("{{DISCHARGING_TIME}}", discharging_time)
80 .replace("{{BATTERY_PERCENTAGE}}", &battery_percentage.to_string())
81 .replace("{{BROWSER_BLOCK}}", &browser_block);
82
83 let hardware = HARDWARE_JS.replace("{{FP_SEED}}", &fingerprint_seed(identity).to_string());
86
87 format!(
93 "(function() {{\n{}\n;\n{}\n;\n{}\n}})();",
94 PROPERTY_MODIFIER_JS, script, hardware
95 )
96}
97
98pub fn build_worker_script(identity: &Identity) -> String {
112 let lang0 = identity
113 .language
114 .first()
115 .map(|s| s.as_str())
116 .unwrap_or("en-US");
117 let languages_json =
118 serde_json::to_string(&identity.language).unwrap_or_else(|_| r#"["en-US"]"#.into());
119
120 let shared: [(&str, String); 8] = [
121 ("{{NAVIGATOR_PLATFORM}}", escape_js(identity.platform.navigator_platform.as_str())),
122 ("{{HARDWARE_CONCURRENCY}}", identity.hardware_concurrency.to_string()),
123 ("{{MEMORY}}", identity.memory.to_string()),
124 ("{{LANGUAGES_JSON}}", languages_json),
125 ("{{LANGUAGE_0}}", escape_js(lang0)),
126 ("{{WEBGL_VENDOR}}", escape_js(&identity.gpu.webgl_vendor)),
127 ("{{WEBGL_RENDERER}}", escape_js(&identity.gpu.webgl_renderer)),
128 ("{{USER_AGENT}}", crate::ua::build_user_agent(identity).map(|s| escape_js(&s)).unwrap_or_default()),
129 ];
130 let worker_scope = shared
131 .iter()
132 .fold(WORKER_SCOPE_JS.to_string(), |acc, (key, val)| acc.replace(key, val));
133
134 format!(
135 "(function() {{\n{}\n;\n{}\n}})();",
136 PROPERTY_MODIFIER_JS, worker_scope
137 )
138}
139
140pub(crate) fn fingerprint_seed(identity: &Identity) -> u32 {
144 let material = format!(
145 "{:?}|{}|{}|{}|{}|{}x{}|{}|{}|{}",
146 identity.os,
147 identity.os_version,
148 identity.gpu.webgl_vendor,
149 identity.gpu.webgl_renderer,
150 identity.id.unwrap_or(0),
151 identity.screen.original_width,
152 identity.screen.original_height,
153 identity.hardware_concurrency,
154 identity.memory,
155 identity.platform.navigator_platform.as_str(),
156 );
157 let mut h: u32 = 0x811c_9dc5;
159 for b in material.as_bytes() {
160 h ^= *b as u32;
161 h = h.wrapping_mul(0x0100_0193);
162 }
163 h
164}
165
166fn escape_js(s: &str) -> String {
167 s.replace('\\', "\\\\")
168 .replace('"', "\\\"")
169 .replace('\n', "\\n")
170}