Skip to main content

verify/
verify.rs

1//! Drives the real library against CreepJS and prints the ratings, so the
2//! injection path can be checked end to end rather than by eye.
3//!
4//!   cargo run --example verify
5//!   cargo run --example verify -- 1 https://abrahamjuliot.github.io/creepjs/
6
7use rustenium::browsers::cdp_browser::CdpBrowser;
8use rustenium_identity::{preset, IdentitySession};
9use std::time::Duration;
10
11#[tokio::main]
12async fn main() -> Result<(), Box<dyn std::error::Error>> {
13    let identity = match std::env::args().nth(1) {
14        Some(arg) => preset::get_by_id(arg.parse()?)?,
15        None => preset::random(),
16    };
17    let url = std::env::args()
18        .nth(2)
19        .unwrap_or_else(|| "https://abrahamjuliot.github.io/creepjs/".to_string());
20
21    println!("preset #{:?}: {:?} / {:?}", identity.id, identity.os, identity.browser);
22
23    let mut session = IdentitySession::launch(identity).await?;
24    println!("launched, navigating to {url} ...");
25
26    // Collect page errors before anything else runs.
27    session.browser().add_init_script(rustenium::cdp::target_manager::InitScript {
28        page: Some(r#"window.__errs=[];
29            addEventListener('error', e => __errs.push('ERR ' + (e.message||e)));
30            addEventListener('unhandledrejection', e => __errs.push('REJ ' + (e.reason && (e.reason.message||e.reason))));
31        "#.into()),
32        worker: None,
33    }).await;
34
35    session.browser_mut().navigate(&url).await?;
36    println!("navigated; waiting for CreepJS to finish computing");
37
38    // CreepJS takes a while; poll for the widget rather than guessing a duration.
39    let mut rendered = false;
40    for _ in 0..40 {
41        tokio::time::sleep(Duration::from_secs(2)).await;
42        let probe = session
43            .browser_mut()
44            .evaluate_script("!!document.querySelector('.stealth-rating')", false)
45            .await;
46        if let Ok(v) = probe {
47            if format!("{:?}", v).contains("true") {
48                rendered = true;
49                break;
50            }
51        }
52        print!(".");
53    }
54    println!();
55    if !rendered {
56        println!("WARNING: stealth widget never rendered — page may be blocked or hung");
57    }
58    tokio::time::sleep(Duration::from_secs(3)).await;
59
60    let expr = r#"(() => {
61        const pct = (s) => { const e=document.querySelector(s); return e ? (e.textContent.match(/^\s*(\d+%)/)||['','?'])[1] : 'MISSING'; };
62        const flagged = [...document.querySelectorAll('#fingerprint-data span.hash')]
63            .map(e => [e.parentElement.textContent.trim().slice(0,12), e.className.replace('hash','').trim()])
64            .filter(([,c]) => c).map(([n,c]) => n+'='+c);
65        return 'stealth=' + pct('.stealth-rating') + ' headless=' + pct('.headless-rating')
66             + ' hashes=' + document.querySelectorAll('span.hash').length
67             + ' flagged=' + (flagged.join(',') || 'NONE');
68    })()"#;
69
70    match session.browser_mut().evaluate_script(expr, false).await {
71        Ok(v) => println!("\n=== CreepJS ===\n{:?}", v.result.value),
72        Err(e) => println!("\nevaluate failed: {e:?}"),
73    }
74
75    session.close().await;
76    Ok(())
77}