refactor: cleanup

This commit is contained in:
zhom
2026-08-24 00:10:37 +04:00
parent c602a1ce0c
commit de88fbbafe
10 changed files with 1250 additions and 167 deletions
+85 -21
View File
@@ -458,38 +458,91 @@ impl BrowserRunner {
wayfern_config.proxy
);
// Check if we need to generate a new fingerprint on every launch
// Check if we need to generate a device for this launch.
//
// Two cases share the block: the user asked for a fresh device on every
// launch, or the profile stores none at all. The second is how a clone
// arrives here — cloning clears the fingerprint and the identity so the
// clone gets an independent device instead of the browser's default —
// and it also covers any profile that reached disk without one, which
// used to launch on whatever device the browser drew for itself.
//
// A profile that ALREADY stores a device keeps it across a browser
// upgrade: nothing here mints a replacement, and its stored payload is
// what the launch applies. The one thing that does replace a stored
// device is the user asking for it - `randomize_fingerprint_on_launch`,
// tested immediately below - which is a deliberate per-profile setting
// and not a consequence of the version.
let mut updated_profile = profile.clone();
if wayfern_config.randomize_fingerprint_on_launch == Some(true) {
log::info!(
"Generating random fingerprint for Wayfern profile: {}",
profile.name
);
let randomize_requested = wayfern_config.randomize_fingerprint_on_launch == Some(true);
let needs_device = wayfern_config.fingerprint.is_none();
if randomize_requested || needs_device {
if needs_device && !randomize_requested {
log::info!(
"No stored device for Wayfern profile {}; generating one",
profile.name
);
} else {
log::info!(
"Generating random fingerprint for Wayfern profile: {}",
profile.name
);
}
// Create a config copy without the existing fingerprint to force generation of a new one
let mut config_for_generation = wayfern_config.clone();
config_for_generation.fingerprint = None;
// Generate a new fingerprint
let (new_fingerprint, geolocation_applied) = self
// A failed generation fails the launch on purpose: continuing would
// start the browser on whatever device it drew for itself, unmanaged
// and unrecorded, while the UI still reports a successful launch. For
// an anti-detect product a silently wrong device is worse than no
// launch at all, because nothing tells the user to stop using it.
//
// Structured rather than prose, because the most common failure is the
// browser refusing a generation once the account's hourly quota is
// spent. That has to reach the user as an explanation; a raw CDP string
// is not one, and the frontend only translates a coded error.
let generated = self
.wayfern_manager
.generate_fingerprint_config(&app_handle, profile, &config_for_generation)
.await
.map_err(|e| format!("Failed to generate random fingerprint: {e}"))?;
.map_err(|e| {
let detail = e.to_string();
// BOTH refusal texts, because this path serves BOTH releases. 151
// says "Fingerprint generation limit reached for this account.";
// the shipped 150 browser says "Too many profiles are being
// created." Matching only the 151 wording leaves a quota-blocked
// 150 user staring at a raw CDP string, which is the exact defect
// this mapping exists to remove.
if detail.contains("generation limit reached") || detail.contains("Too many profiles") {
crate::backend_error_with_detail("WAYFERN_GENERATION_LIMIT_REACHED", detail)
} else {
crate::backend_error_with_detail("WAYFERN_FINGERPRINT_GENERATION_FAILED", detail)
}
})?;
let geolocation_applied = generated.geolocation_applied;
log::info!(
"New fingerprint generated, length: {} chars",
new_fingerprint.len()
"New fingerprint generated, length: {} chars, identity: {:?}",
generated.fingerprint.len(),
generated.identity_id
);
// Update the config with the new fingerprint for launching
wayfern_config.fingerprint = Some(new_fingerprint.clone());
wayfern_config.fingerprint = Some(generated.fingerprint.clone());
wayfern_config.identity_id = generated.identity_id.clone();
wayfern_config.identity_baseline = generated.identity_baseline.clone();
// Save the updated fingerprint to the profile so it persists.
let mut updated_wayfern_config = updated_profile.wayfern_config.clone().unwrap_or_default();
updated_wayfern_config.fingerprint = Some(new_fingerprint);
updated_wayfern_config.fingerprint = Some(generated.fingerprint);
updated_wayfern_config.identity_id = generated.identity_id;
updated_wayfern_config.identity_baseline = generated.identity_baseline;
// Preserve the randomize flag so it persists across launches
updated_wayfern_config.randomize_fingerprint_on_launch = Some(true);
updated_wayfern_config.randomize_fingerprint_on_launch =
wayfern_config.randomize_fingerprint_on_launch;
// Preserve the OS setting so it's used for future fingerprint generation
if wayfern_config.os.is_some() {
updated_wayfern_config.os = wayfern_config.os.clone();
@@ -601,7 +654,11 @@ impl BrowserRunner {
)
.await
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
format!("Failed to launch Wayfern: {e}").into()
// A refused apply reports itself as a structured error so the dialog
// can name the cause. Prefixing it would put English in front of the
// JSON the frontend parses, and the whole thing would reach the user
// as raw machine output.
crate::wrap_backend_error(e, "Failed to launch Wayfern").into()
})?;
// Get the process ID from launch result
@@ -653,20 +710,27 @@ impl BrowserRunner {
guard.worker_id = None;
}
// Wayfern.setFingerprint echoes back the fingerprint the browser actually
// applied, which may be UPGRADED from the stored one (e.g. when the
// stored fingerprint targets an older browser version). Persist it so the
// next launch starts from the upgraded value — saved below via
// The apply command echoes back the device the browser actually used,
// which may differ from the stored one. Persist it so the next launch
// starts from that value — saved below via
// save_process_info(&updated_profile).
if let Some(used_fp) = wayfern_result.used_fingerprint.clone() {
let mut cfg = updated_profile.wayfern_config.clone().unwrap_or_default();
if cfg.fingerprint.as_deref() != Some(used_fp.as_str()) {
let baseline_changed = wayfern_result.used_identity_baseline.is_some()
&& cfg.identity_baseline != wayfern_result.used_identity_baseline;
if cfg.fingerprint.as_deref() != Some(used_fp.as_str()) || baseline_changed {
log::info!(
"Persisting upgraded fingerprint from Wayfern.setFingerprint for profile: {} (len {})",
"Persisting applied fingerprint echoed by Wayfern for profile: {} (len {})",
profile.name,
used_fp.len()
);
cfg.fingerprint = Some(used_fp);
// The baseline must move with the fingerprint it was computed
// against, or the next launch diffs the two apart and invents
// overrides the user never asked for.
if let Some(baseline) = wayfern_result.used_identity_baseline.clone() {
cfg.identity_baseline = Some(baseline);
}
updated_profile.wayfern_config = Some(cfg);
}
}
+20 -2
View File
@@ -1263,13 +1263,27 @@ async fn list_active_vpn_connections() -> Result<Vec<vpn::VpnStatus>, String> {
)
}
/// What the fingerprint form gets back from `generate_sample_fingerprint`.
///
/// The identity fields are `None` on a browser without the identity API. They
/// have to travel with the fingerprint rather than be re-derived later: the
/// form writes all three into the profile's Wayfern config in one edit, and a
/// fingerprint stored without its identity is one the launch path would throw
/// away and mint again.
#[derive(serde::Serialize)]
struct SampleFingerprint {
fingerprint: String,
identity_id: Option<String>,
identity_baseline: Option<String>,
}
#[tauri::command]
async fn generate_sample_fingerprint(
app_handle: tauri::AppHandle,
browser: String,
version: String,
config_json: String,
) -> Result<String, String> {
) -> Result<SampleFingerprint, String> {
let temp_profile = crate::profile::BrowserProfile {
id: uuid::Uuid::new_v4(),
name: "temp_fingerprint_gen".to_string(),
@@ -1309,7 +1323,11 @@ async fn generate_sample_fingerprint(
manager
.generate_fingerprint_config(&app_handle, &temp_profile, &config)
.await
.map(|(fingerprint, _geolocation_applied)| fingerprint)
.map(|generated| SampleFingerprint {
fingerprint: generated.fingerprint,
identity_id: generated.identity_id,
identity_baseline: generated.identity_baseline,
})
.map_err(|e| format!("Failed to generate fingerprint: {e}"))
} else {
Err(format!(
+56 -4
View File
@@ -223,9 +223,14 @@ impl ProfileManager {
.generate_fingerprint_config(app_handle, &temp_profile, &config)
.await
{
Ok((generated_fingerprint, geo_applied)) => {
config.fingerprint = Some(generated_fingerprint);
geolocation_applied = geo_applied;
Ok(generated) => {
config.fingerprint = Some(generated.fingerprint);
// Set together with the fingerprint they describe. A profile that
// stored one without the other would either lose reproducibility or
// diff its whole device into overrides on the next launch.
config.identity_id = generated.identity_id;
config.identity_baseline = generated.identity_baseline;
geolocation_applied = generated.geolocation_applied;
log::info!("Successfully generated fingerprint for Wayfern profile: {name}");
}
Err(e) => {
@@ -628,6 +633,36 @@ impl ProfileManager {
return Err(format!("Browser version {version} is not downloaded").into());
}
// A move back to a version without the identity API cannot carry an
// identity-backed device with it, and leaving it in place is worse than
// dropping it: the older browser would splice it onto a freshly drawn
// device and persist the result, which then fails on every later launch.
// Clearing `fingerprint` makes the next launch generate a fresh one;
// `geo_proxy_signature` goes with it because it certifies location fields
// of a fingerprint that no longer exists.
//
// Do NOT refuse the version change: `consolidate_browser_versions` only
// reaches this direction once the newer binary is already gone from disk,
// so refusing would strand the profile pointing at a missing executable.
// Every other direction falls through untouched.
let target_speaks_identity_api = crate::wayfern_manager::supports_identity_api(version);
if let Some(cfg) = profile
.wayfern_config
.as_mut()
.filter(|c| c.identity_id.is_some() && !target_speaks_identity_api)
{
cfg.identity_id = None;
cfg.identity_baseline = None;
cfg.fingerprint = None;
cfg.geo_proxy_signature = None;
log::warn!(
"Profile '{}' moved from Wayfern {} to {}. Its stored fingerprint cannot be used there, so it was cleared and a fresh one will be generated on the next launch.",
profile.name,
profile.version,
version
);
}
// Update version
profile.version = version.to_string();
@@ -1101,6 +1136,11 @@ impl ProfileManager {
// isolation between a clone and its source.
if let Some(cfg) = new_profile.wayfern_config.as_mut() {
cfg.fingerprint = None;
// The identity is a stronger link than the payload: the same UUID rebuilds
// the SAME device on any version, so a clone that kept it would stay
// byte-identical to its source forever, not just until the next upgrade.
cfg.identity_id = None;
cfg.identity_baseline = None;
}
self.save_profile(&new_profile)?;
@@ -1116,7 +1156,7 @@ impl ProfileManager {
&self,
app_handle: tauri::AppHandle,
profile_id: &str,
config: WayfernConfig,
mut config: WayfernConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Find the profile by ID
let profile_uuid = uuid::Uuid::parse_str(profile_id).map_err(
@@ -1148,6 +1188,18 @@ impl ProfileManager {
);
}
// The identity is internal state, so a caller that edits the fingerprint
// through the API or MCP will not send it back. Dropping it would silently
// re-mint the device on the next launch and throw the edit away with it,
// which is the opposite of what an override is for. Carry it forward unless
// the caller either supplied its own or cleared the fingerprint outright.
if config.identity_id.is_none() && config.fingerprint.is_some() {
if let Some(stored) = profile.wayfern_config.as_ref() {
config.identity_id = stored.identity_id.clone();
config.identity_baseline = stored.identity_baseline.clone();
}
}
// Update the Wayfern configuration
profile.wayfern_config = Some(config);
+5 -1
View File
@@ -988,7 +988,11 @@ impl ProfileImporter {
{
// geo_proxy_signature is intentionally left unset here: the first
// launch's signature-mismatch refresh verifies the location either way.
Ok((fp, _geolocation_applied)) => config.fingerprint = Some(fp),
Ok(generated) => {
config.fingerprint = Some(generated.fingerprint);
config.identity_id = generated.identity_id;
config.identity_baseline = generated.identity_baseline;
}
Err(e) => {
let _ = fs::remove_dir_all(&new_profile_uuid_dir);
return Err(
File diff suppressed because it is too large Load Diff