mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-29 14:10:53 +02:00
refactor: better cookie import experience
This commit is contained in:
@@ -148,6 +148,10 @@ windows = { version = "0.62", features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Registry",
|
||||
"Win32_UI_Shell",
|
||||
# SendMessageTimeoutW, for the association-change broadcast in
|
||||
# default_browser.rs. Going through the crate rather than a hand-written
|
||||
# `extern "system"` block is what keeps `lpdwResult` typed as DWORD_PTR.
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
# CryptUnprotectData, for unwrapping the source browser's os_crypt key from
|
||||
# `Local State` during profile import.
|
||||
"Win32_Security_Cryptography",
|
||||
|
||||
@@ -4253,7 +4253,7 @@ async fn import_profiles_api(
|
||||
(status = 400, description = "Invalid cookie file or unsupported browser"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 404, description = "Profile not found"),
|
||||
(status = 409, description = "Browser is currently running"),
|
||||
(status = 409, description = "Browser is running, the profile is password-protected, or a remote session owns it"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
security(
|
||||
@@ -4300,10 +4300,16 @@ async fn import_profile_cookies(
|
||||
}))
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = e.to_lowercase();
|
||||
if msg.contains("running") {
|
||||
// The importer speaks in `{"code":…}` strings now; match those, and keep
|
||||
// the substring checks for the messages that are still plain text.
|
||||
if e.contains("COOKIE_IMPORT_BROWSER_RUNNING")
|
||||
|| e.contains("COOKIE_IMPORT_PROFILE_PROTECTED")
|
||||
|| e.contains("COOKIE_IMPORT_REMOTE_SESSION")
|
||||
{
|
||||
Err(StatusCode::CONFLICT)
|
||||
} else if msg.contains("no valid cookies") || msg.contains("unsupported browser") {
|
||||
} else if e.contains("COOKIE_IMPORT_NO_COOKIES")
|
||||
|| e.to_lowercase().contains("unsupported browser")
|
||||
{
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
} else {
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
|
||||
+692
-356
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -358,38 +358,44 @@ mod windows {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the shell that the association it has cached is stale.
|
||||
///
|
||||
/// `SHChangeNotify` is the documented announcement for an association change,
|
||||
/// and the `WM_SETTINGCHANGE` broadcast is what the shell's own settings UI
|
||||
/// sends alongside it, so both go out.
|
||||
///
|
||||
/// This used to hand-declare `SendMessageTimeoutA` with `lpdwResult` typed as
|
||||
/// `*mut u32` and pass it a `u32`. The real parameter is `PDWORD_PTR`, eight
|
||||
/// bytes on x64, so every call wrote four bytes past a stack slot. The result
|
||||
/// was a corrupted stack at the exact moment a user set Donut as their default
|
||||
/// browser, and the process died with nothing in the log. Go through the
|
||||
/// `windows` crate instead, which types the out-parameter correctly and cannot
|
||||
/// drift from the real ABI.
|
||||
fn notify_system_of_changes() {
|
||||
// Use Windows API to notify the system of association changes
|
||||
// This helps refresh the system's understanding of the changes
|
||||
use windows::core::w;
|
||||
use windows::Win32::Foundation::{LPARAM, WPARAM};
|
||||
use windows::Win32::UI::Shell::{SHChangeNotify, SHCNE_ASSOCCHANGED, SHCNF_IDLIST};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
SendMessageTimeoutW, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
use std::ffi::c_void;
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None);
|
||||
|
||||
const HWND_BROADCAST: *mut c_void = 0xffff as *mut c_void;
|
||||
const WM_SETTINGCHANGE: u32 = 0x001A;
|
||||
const SMTO_ABORTIFHUNG: u32 = 0x0002;
|
||||
|
||||
extern "system" {
|
||||
fn SendMessageTimeoutA(
|
||||
hWnd: *mut c_void,
|
||||
Msg: u32,
|
||||
wParam: usize,
|
||||
lParam: isize,
|
||||
fuFlags: u32,
|
||||
uTimeout: u32,
|
||||
lpdwResult: *mut u32,
|
||||
) -> isize;
|
||||
}
|
||||
|
||||
let mut result: u32 = 0;
|
||||
|
||||
SendMessageTimeoutA(
|
||||
// The broadcast is best-effort: a hung top-level window elsewhere on the
|
||||
// desktop must not hold up the click that triggered this, hence the
|
||||
// timeout and SMTO_ABORTIFHUNG. `WM_SETTINGCHANGE`'s lParam string is
|
||||
// marshalled cross-process by the window manager, and this one is
|
||||
// 'static, so it stays valid for the whole call.
|
||||
let mut result: usize = 0;
|
||||
SendMessageTimeoutW(
|
||||
HWND_BROADCAST,
|
||||
WM_SETTINGCHANGE,
|
||||
0,
|
||||
c"Software\\Classes".as_ptr() as isize,
|
||||
WPARAM(0),
|
||||
LPARAM(w!("Software\\Classes").as_ptr() as isize),
|
||||
SMTO_ABORTIFHUNG,
|
||||
1000,
|
||||
&mut result,
|
||||
Some(&mut result),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -105,6 +105,7 @@ mod cloud_errors;
|
||||
mod commercial_license;
|
||||
mod cookie_bot;
|
||||
mod cookie_manager;
|
||||
mod cookie_paste;
|
||||
pub mod events;
|
||||
mod mcp_integrations;
|
||||
mod mcp_server;
|
||||
@@ -476,29 +477,57 @@ async fn copy_profile_cookies(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Push a profile's freshly written cookies to the cloud, if it syncs at all.
|
||||
fn queue_profile_cookie_sync(profile_id: &str) {
|
||||
let Some(scheduler) = crate::sync::get_global_scheduler() else {
|
||||
return;
|
||||
};
|
||||
let Ok(profiles) = profile::manager::ProfileManager::instance().list_profiles() else {
|
||||
return;
|
||||
};
|
||||
let syncs = profiles
|
||||
.iter()
|
||||
.any(|p| p.id.to_string() == profile_id && p.is_sync_enabled());
|
||||
if !syncs {
|
||||
return;
|
||||
}
|
||||
let pid = profile_id.to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
scheduler.queue_profile_sync(pid).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn import_cookies_from_file(
|
||||
async fn analyze_pasted_cookies(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
content: String,
|
||||
) -> Result<cookie_manager::CookieImportResult, String> {
|
||||
let result =
|
||||
cookie_manager::CookieManager::import_cookies(&app_handle, &profile_id, &content).await?;
|
||||
site: Option<String>,
|
||||
) -> Result<cookie_manager::CookiePasteAnalysis, String> {
|
||||
cookie_manager::CookieManager::analyze_paste(&app_handle, &profile_id, &content, site.as_deref())
|
||||
.await
|
||||
}
|
||||
|
||||
// Trigger sync for the profile if sync is enabled
|
||||
if let Some(scheduler) = crate::sync::get_global_scheduler() {
|
||||
let profile_manager = profile::manager::ProfileManager::instance();
|
||||
if let Ok(profiles) = profile_manager.list_profiles() {
|
||||
if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == profile_id) {
|
||||
if profile.is_sync_enabled() {
|
||||
let pid = profile_id.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
scheduler.queue_profile_sync(pid).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[tauri::command]
|
||||
async fn import_pasted_cookies(
|
||||
app_handle: tauri::AppHandle,
|
||||
profile_id: String,
|
||||
content: String,
|
||||
site: Option<String>,
|
||||
mode: cookie_manager::CookieWriteMode,
|
||||
include_expired: bool,
|
||||
) -> Result<cookie_manager::CookiePasteImportResult, String> {
|
||||
let result = cookie_manager::CookieManager::import_paste(
|
||||
&app_handle,
|
||||
&profile_id,
|
||||
&content,
|
||||
site.as_deref(),
|
||||
mode,
|
||||
include_expired,
|
||||
)
|
||||
.await?;
|
||||
|
||||
queue_profile_cookie_sync(&profile_id);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -2821,7 +2850,8 @@ pub fn run_with_builder(
|
||||
read_profile_cookies,
|
||||
get_profile_cookie_stats,
|
||||
copy_profile_cookies,
|
||||
import_cookies_from_file,
|
||||
analyze_pasted_cookies,
|
||||
import_pasted_cookies,
|
||||
export_profile_cookies,
|
||||
check_wayfern_terms_accepted,
|
||||
check_wayfern_downloaded,
|
||||
|
||||
Reference in New Issue
Block a user