pub mod cdp;
pub mod error;
pub mod identity;
pub mod local_proxy_server;
pub mod preset;
pub mod script;
pub mod tz;
pub mod ua;
use error::IdentityError;
use local_proxy_server::start_overlay;
use rustenium::browsers::{
BidiBrowser,
chrome::browser::{ChromeBrowser, ChromeConfig},
};
pub use error::IdentityError as Error;
pub use identity::*;
pub struct IdentityConfig {
pub identity: Identity,
pub chrome: ChromeConfig,
}
impl From<Identity> for IdentityConfig {
fn from(identity: Identity) -> Self {
Self {
identity,
chrome: ChromeConfig {
enable_bidi: false,
enable_cdp: true,
..Default::default()
},
}
}
}
impl IdentityConfig {
pub fn new(identity: Identity, chrome: ChromeConfig) -> Self {
Self { identity, chrome }
}
}
async fn read_chromium_version(browser: &mut ChromeBrowser) -> Option<Vec<u16>> {
const EXPR: &str = r#"(async () => {
try {
const d = navigator.userAgentData;
if (d && d.getHighEntropyValues) {
const v = await d.getHighEntropyValues(['uaFullVersion']);
if (v && v.uaFullVersion) return v.uaFullVersion;
}
} catch (e) {}
const m = navigator.userAgent.match(/Chrome\/([\d.]+)/);
return m ? m[1] : '';
})()"#;
let result =
rustenium::browsers::cdp_browser::CdpBrowser::evaluate_script(browser, EXPR, true)
.await
.ok()?;
let version = result.result.value.as_ref()?.as_str()?.to_string();
ua::parse_version_parts(&version)
}
async fn read_gpu_strings(browser: &mut ChromeBrowser) -> Option<(String, String)> {
const EXPR: &str = r#"(() => {
try {
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl && gl.getExtension('WEBGL_debug_renderer_info');
if (!ext) return '[]';
return JSON.stringify([
gl.getParameter(ext.UNMASKED_VENDOR_WEBGL),
gl.getParameter(ext.UNMASKED_RENDERER_WEBGL),
]);
} catch (e) { return '[]'; }
})()"#;
let result =
rustenium::browsers::cdp_browser::CdpBrowser::evaluate_script(browser, EXPR, false)
.await
.ok()?;
let json = result.result.value.as_ref()?.as_str()?;
let pair: Vec<String> = serde_json::from_str(json).ok()?;
let [vendor, renderer] = <[String; 2]>::try_from(pair).ok()?;
if vendor.is_empty() || renderer.is_empty() {
return None;
}
Some((vendor, renderer))
}
pub struct IdentitySession {
identity: Identity,
browser: ChromeBrowser,
}
impl IdentitySession {
pub async fn launch(config: impl Into<IdentityConfig>) -> Result<Self, IdentityError> {
let mut config = config.into();
let geo = tz::resolve_geo(
config.identity.timezone.as_deref(),
config.identity.proxy.as_deref(),
)
.await?;
let timezone = geo.timezone.clone();
let mut chrome_config = config.chrome;
chrome_config.enable_bidi = false;
chrome_config.enable_cdp = true;
let mut flags = chrome_config.browser_flags.take().unwrap_or_default();
flags.push("--disable-blink-features=AutomationControlled".to_string());
chrome_config.browser_flags = Some(flags);
if let Some(ref proxy_url) = config.identity.proxy {
if !proxy_url.is_empty() {
let local_addr = start_overlay(proxy_url)
.await
.map_err(|e| IdentityError::ProxyError(e.to_string()))?;
let mut flags = chrome_config.browser_flags.take().unwrap_or_default();
flags.push(format!(
"--proxy-server=http://127.0.0.1:{}",
local_addr.port()
));
chrome_config.browser_flags = Some(flags);
}
}
let mut browser = ChromeBrowser::new(chrome_config).await;
if matches!(config.identity.browser, Browser::Chrome | Browser::Edge)
&& !matches!(config.identity.os, Os::Ios)
{
match read_chromium_version(&mut browser).await {
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")}},
None => tracing::warn!(
"could not read the running Chromium version; \
persona keeps its configured browser_version"
),
}
}
match read_gpu_strings(&mut browser).await {
Some((vendor, renderer)) => {
config.identity.gpu.webgl_vendor = vendor;
config.identity.gpu.webgl_renderer = renderer;
}
None => tracing::warn!(
"could not read the real WebGL vendor/renderer; \
persona keeps its configured gpu strings"
),
}
cdp::apply_identity(&mut browser, &config.identity, &timezone).await?;
Ok(Self {
identity: config.identity,
browser,
})
}
pub fn browser(&self) -> &ChromeBrowser {
&self.browser
}
pub fn browser_mut(&mut self) -> &mut ChromeBrowser {
&mut self.browser
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub async fn close(self) -> bool {
self.browser.close().await.map_err(|_| false).is_ok()
}
}