mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-28 13:50:26 +02:00
refactor: cleanup
This commit is contained in:
@@ -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
@@ -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!(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user