rustenium_identity/
cdp.rs1use crate::error::IdentityError;
2use crate::identity::{Browser, Identity, Os};
3
4use crate::script;
5use crate::ua;
6
7use rustenium::browsers::chrome::browser::ChromeBrowser;
8use rustenium::browsers::cdp_browser::CdpBrowser;
9use rustenium::cdp::target_manager::InitScript;
10use rustenium_cdp_definitions::browser_protocol::emulation::commands::{
11 SetDeviceMetricsOverride, SetEmitTouchEventsForMouse, SetEmitTouchEventsForMouseConfiguration,
12 SetHardwareConcurrencyOverride, SetLocaleOverride, SetTimezoneOverride,
13 SetTouchEmulationEnabled, SetUserAgentOverride,
14};
15use rustenium_cdp_definitions::browser_protocol::emulation::types::{
16 UserAgentBrandVersion, UserAgentMetadata,
17};
18use rustenium_cdp_definitions::browser_protocol::network::commands::SetUserAgentOverride as NetworkSetUserAgentOverride;
19
20fn build_client_hints(identity: &Identity) -> UserAgentMetadata {
22 let major = identity.browser_version.first().copied().unwrap_or(0).to_string();
23 let full_version = identity
24 .browser_version
25 .iter()
26 .map(|v| v.to_string())
27 .collect::<Vec<_>>()
28 .join(".");
29 let is_mobile = identity.device_model.is_some();
30
31 let (brand_name, brand_name_full) = match identity.browser {
32 Browser::Edge => ("Microsoft Edge", "Microsoft Edge"),
33 _ => ("Google Chrome", "Google Chrome"),
34 };
35
36 let grease_brand = match identity.os {
37 Os::Android => "Not-A.Brand",
38 _ => "Not;A=Brand",
39 };
40
41 let brands = vec![
42 UserAgentBrandVersion::new("Chromium", &major),
43 UserAgentBrandVersion::new(grease_brand, "24"),
44 UserAgentBrandVersion::new(brand_name, &major),
45 ];
46
47 let full_version_list = vec![
48 UserAgentBrandVersion::new("Chromium", &full_version),
49 UserAgentBrandVersion::new(grease_brand, "24.0.0.0"),
50 UserAgentBrandVersion::new(brand_name_full, &full_version),
51 ];
52
53 let platform = match identity.os {
54 Os::Windows => "Windows",
55 Os::Macintosh => "macOS",
56 Os::Linux => "Linux",
57 Os::Android => "Android",
58 Os::Ios => "iOS",
59 };
60
61 let (architecture, bitness) = if is_mobile {
63 (String::new(), String::new())
64 } else {
65 (
66 identity.platform.architecture.clone().unwrap_or_else(|| "x86".into()),
67 identity.platform.bitness.clone().unwrap_or_else(|| "64".into()),
68 )
69 };
70 let model = identity.device_model.clone().unwrap_or_default();
71
72 UserAgentMetadata::builder()
73 .brands(brands)
74 .full_version_lists(full_version_list)
75 .platform(platform)
76 .platform_version(&identity.platform.version)
77 .architecture(&architecture)
78 .model(&model)
79 .mobile(is_mobile)
80 .bitness(bitness)
81 .build()
82 .unwrap()
83}
84
85pub async fn apply_identity(
87 browser: &mut ChromeBrowser,
88 identity: &Identity,
89 timezone: &str,
90) -> Result<(), IdentityError> {
91 let user_agent = ua::build_user_agent(identity)?;
92 let lang0 = identity
93 .language
94 .first()
95 .cloned()
96 .unwrap_or_else(|| "en-US".into());
97 let is_mobile = identity.device_model.is_some();
98
99 let client_hints = if matches!(identity.browser, Browser::Chrome | Browser::Edge)
101 && !matches!(identity.os, Os::Ios)
102 {
103 Some(build_client_hints(identity))
104 } else {
105 None
106 };
107
108 let mut ua_builder = SetUserAgentOverride::builder()
114 .user_agent(&user_agent)
115 .accept_language(&lang0)
116 .platform(identity.platform.navigator_platform.as_str());
117 if let Some(ref metadata) = client_hints {
118 ua_builder = ua_builder.user_agent_metadata(metadata.clone());
119 }
120 let ua_cmd = ua_builder.build().unwrap();
121
122 let full_version = identity
131 .browser_version
132 .iter()
133 .map(|v| v.to_string())
134 .collect::<Vec<_>>()
135 .join(".");
136 let ua_extensions = serde_json::json!({
137 "userAgentMetadata": { "fullVersion": full_version }
138 });
139 browser
140 .send_command_extended(ua_cmd.into(), ua_extensions.clone())
141 .await
142 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
143
144 let mut net_builder = NetworkSetUserAgentOverride::builder()
146 .user_agent(&user_agent)
147 .accept_language(&lang0);
148 if let Some(ref metadata) = client_hints {
149 net_builder = net_builder.user_agent_metadata(metadata.clone());
150 }
151 let net_ua_cmd = net_builder.build().unwrap();
152 browser
155 .send_command_extended(net_ua_cmd.into(), ua_extensions.clone())
156 .await
157 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
158
159 if is_mobile {
161 let screen_width = (identity.screen.logical_width as f64 * identity.screen.density_pixel_ratio as f64) as i64;
162 let screen_height = (identity.screen.logical_height as f64 * identity.screen.density_pixel_ratio as f64) as i64;
163 let device_cmd = SetDeviceMetricsOverride::builder()
164 .width(identity.screen.logical_width as i64)
165 .height(identity.screen.logical_height as i64)
166 .device_scale_factor(identity.screen.density_pixel_ratio as f64)
167 .mobile(true)
168 .screen_width(screen_width)
169 .screen_height(screen_height)
170 .build()
171 .unwrap();
172 browser
173 .send_command(device_cmd.into())
174 .await
175 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
176 }
177
178 if identity.has_touch {
180 let touch_cmd = SetTouchEmulationEnabled::builder()
181 .enabled(true)
182 .max_touch_points(5i64)
183 .build()
184 .unwrap();
185 browser
186 .send_command(touch_cmd.into())
187 .await
188 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
189
190 let configuration = if is_mobile {
194 SetEmitTouchEventsForMouseConfiguration::Mobile
195 } else {
196 SetEmitTouchEventsForMouseConfiguration::Desktop
197 };
198 let emit_touch_cmd = SetEmitTouchEventsForMouse::builder()
199 .enabled(true)
200 .configuration(configuration)
201 .build()
202 .unwrap();
203 browser
204 .send_command(emit_touch_cmd.into())
205 .await
206 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
207 }
208
209 let locale_cmd = SetLocaleOverride::builder()
211 .locale(&lang0)
212 .build();
213 browser
214 .send_command(locale_cmd.into())
215 .await
216 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
217
218 let tz_cmd = SetTimezoneOverride::builder()
220 .timezone_id(timezone)
221 .build()
222 .unwrap();
223 browser
224 .send_command(tz_cmd.into())
225 .await
226 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
227
228 let hc_cmd = SetHardwareConcurrencyOverride::builder()
230 .hardware_concurrency(identity.hardware_concurrency as i64)
231 .build()
232 .unwrap();
233 browser
234 .send_command(hc_cmd.into())
235 .await
236 .map_err(|e| IdentityError::CdpError(e.to_string()))?;
237
238 browser
251 .add_init_script(InitScript {
252 page: Some(script::build_stealth_script(identity)),
253 worker: Some(script::build_worker_script(identity)),
254 })
255 .await;
256 Ok(())
257}