rustenium_identity/
lib.rs1pub mod cdp;
2pub mod error;
3pub mod identity;
4pub mod local_proxy_server;
5pub mod preset;
6pub mod script;
7pub mod tz;
8pub mod ua;
9
10use error::IdentityError;
11use local_proxy_server::start_overlay;
12use rustenium::browsers::{
13 BidiBrowser,
14 chrome::browser::{ChromeBrowser, ChromeConfig},
15};
16
17pub use error::IdentityError as Error;
18pub use identity::*;
19
20pub struct IdentityConfig {
22 pub identity: Identity,
23 pub chrome: ChromeConfig,
24}
25
26impl From<Identity> for IdentityConfig {
27 fn from(identity: Identity) -> Self {
28 Self {
29 identity,
30 chrome: ChromeConfig {
31 enable_bidi: false,
32 enable_cdp: true,
33 ..Default::default()
34 },
35 }
36 }
37}
38
39impl IdentityConfig {
40 pub fn new(identity: Identity, chrome: ChromeConfig) -> Self {
41 Self { identity, chrome }
42 }
43}
44
45async fn read_chromium_version(browser: &mut ChromeBrowser) -> Option<Vec<u16>> {
49 const EXPR: &str = r#"(async () => {
54 try {
55 const d = navigator.userAgentData;
56 if (d && d.getHighEntropyValues) {
57 const v = await d.getHighEntropyValues(['uaFullVersion']);
58 if (v && v.uaFullVersion) return v.uaFullVersion;
59 }
60 } catch (e) {}
61 const m = navigator.userAgent.match(/Chrome\/([\d.]+)/);
62 return m ? m[1] : '';
63 })()"#;
64 let result =
66 rustenium::browsers::cdp_browser::CdpBrowser::evaluate_script(browser, EXPR, true)
67 .await
68 .ok()?;
69 let version = result.result.value.as_ref()?.as_str()?.to_string();
70 ua::parse_version_parts(&version)
71}
72
73async fn read_gpu_strings(browser: &mut ChromeBrowser) -> Option<(String, String)> {
81 const EXPR: &str = r#"(() => {
82 try {
83 const gl = document.createElement('canvas').getContext('webgl');
84 const ext = gl && gl.getExtension('WEBGL_debug_renderer_info');
85 if (!ext) return '[]';
86 return JSON.stringify([
87 gl.getParameter(ext.UNMASKED_VENDOR_WEBGL),
88 gl.getParameter(ext.UNMASKED_RENDERER_WEBGL),
89 ]);
90 } catch (e) { return '[]'; }
91 })()"#;
92 let result =
93 rustenium::browsers::cdp_browser::CdpBrowser::evaluate_script(browser, EXPR, false)
94 .await
95 .ok()?;
96 let json = result.result.value.as_ref()?.as_str()?;
97 let pair: Vec<String> = serde_json::from_str(json).ok()?;
98 let [vendor, renderer] = <[String; 2]>::try_from(pair).ok()?;
99 if vendor.is_empty() || renderer.is_empty() {
100 return None;
101 }
102 Some((vendor, renderer))
103}
104
105pub struct IdentitySession {
107 identity: Identity,
108 browser: ChromeBrowser,
109}
110
111impl IdentitySession {
112 pub async fn launch(config: impl Into<IdentityConfig>) -> Result<Self, IdentityError> {
116 let mut config = config.into();
117 let geo = tz::resolve_geo(
118 config.identity.timezone.as_deref(),
119 config.identity.proxy.as_deref(),
120 )
121 .await?;
122 let timezone = geo.timezone.clone();
123
124 let mut chrome_config = config.chrome;
125 chrome_config.enable_bidi = false;
126 chrome_config.enable_cdp = true;
127
128 let mut flags = chrome_config.browser_flags.take().unwrap_or_default();
130 flags.push("--disable-blink-features=AutomationControlled".to_string());
131 flags.push("--use-gl=angle".to_string());
145 flags.push("--use-angle=gl".to_string());
146 flags.push("--ignore-gpu-blocklist".to_string());
147 chrome_config.browser_flags = Some(flags);
148
149 if let Some(ref proxy_url) = config.identity.proxy {
150 if !proxy_url.is_empty() {
151 let local_addr = start_overlay(proxy_url)
152 .await
153 .map_err(|e| IdentityError::ProxyError(e.to_string()))?;
154 let mut flags = chrome_config.browser_flags.take().unwrap_or_default();
155 flags.push(format!(
156 "--proxy-server=http://127.0.0.1:{}",
157 local_addr.port()
158 ));
159 chrome_config.browser_flags = Some(flags);
160 }
161 }
162
163 let mut browser = ChromeBrowser::new(chrome_config).await;
164
165 if matches!(config.identity.browser, Browser::Chrome | Browser::Edge)
173 && !matches!(config.identity.os, Os::Ios)
174 {
175 match read_chromium_version(&mut browser).await {
176 Some(version) => {if (config.identity.browser_version == version) {tracing::warn!("The chrome version you want to spoof is different from the actual stock chrome version running on")}},
177 None => tracing::warn!(
178 "could not read the running Chromium version; \
179 persona keeps its configured browser_version"
180 ),
181 }
182 }
183
184 match read_gpu_strings(&mut browser).await {
188 Some((vendor, renderer)) => {
189 config.identity.gpu.webgl_vendor = vendor;
190 config.identity.gpu.webgl_renderer = renderer;
191 }
192 None => tracing::warn!(
193 "could not read the real WebGL vendor/renderer; \
194 persona keeps its configured gpu strings"
195 ),
196 }
197
198 cdp::apply_identity(&mut browser, &config.identity, &timezone).await?;
199
200 Ok(Self {
201 identity: config.identity,
202 browser,
203 })
204 }
205
206 pub fn browser(&self) -> &ChromeBrowser {
208 &self.browser
209 }
210
211 pub fn browser_mut(&mut self) -> &mut ChromeBrowser {
213 &mut self.browser
214 }
215
216 pub fn identity(&self) -> &Identity {
218 &self.identity
219 }
220
221 pub async fn close(self) -> bool {
222 self.browser.close().await.map_err(|_| false).is_ok()
223 }
224}