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 chrome_config.browser_flags = Some(flags);
132
133 if let Some(ref proxy_url) = config.identity.proxy {
134 if !proxy_url.is_empty() {
135 let local_addr = start_overlay(proxy_url)
136 .await
137 .map_err(|e| IdentityError::ProxyError(e.to_string()))?;
138 let mut flags = chrome_config.browser_flags.take().unwrap_or_default();
139 flags.push(format!(
140 "--proxy-server=http://127.0.0.1:{}",
141 local_addr.port()
142 ));
143 chrome_config.browser_flags = Some(flags);
144 }
145 }
146
147 let mut browser = ChromeBrowser::new(chrome_config).await;
148
149 if matches!(config.identity.browser, Browser::Chrome | Browser::Edge)
157 && !matches!(config.identity.os, Os::Ios)
158 {
159 match read_chromium_version(&mut browser).await {
160 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")}},
161 None => tracing::warn!(
162 "could not read the running Chromium version; \
163 persona keeps its configured browser_version"
164 ),
165 }
166 }
167
168 match read_gpu_strings(&mut browser).await {
172 Some((vendor, renderer)) => {
173 config.identity.gpu.webgl_vendor = vendor;
174 config.identity.gpu.webgl_renderer = renderer;
175 }
176 None => tracing::warn!(
177 "could not read the real WebGL vendor/renderer; \
178 persona keeps its configured gpu strings"
179 ),
180 }
181
182 cdp::apply_identity(&mut browser, &config.identity, &timezone).await?;
183
184 Ok(Self {
185 identity: config.identity,
186 browser,
187 })
188 }
189
190 pub fn browser(&self) -> &ChromeBrowser {
192 &self.browser
193 }
194
195 pub fn browser_mut(&mut self) -> &mut ChromeBrowser {
197 &mut self.browser
198 }
199
200 pub fn identity(&self) -> &Identity {
202 &self.identity
203 }
204
205 pub async fn close(self) -> bool {
206 self.browser.close().await.map_err(|_| false).is_ok()
207 }
208}