From 5b31cfaf32edbf943682bfec48fbbae3b0b6c5a7 Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:29:08 +0400 Subject: [PATCH] refactor: partially migrate to singleton pattern --- src-tauri/src/api_client.rs | 11 +- src-tauri/src/app_auto_updater.rs | 31 +- src-tauri/src/auto_updater.rs | 45 +- src-tauri/src/browser_runner.rs | 20 +- src-tauri/src/browser_version_service.rs | 669 ++--------------------- src-tauri/src/download.rs | 354 +----------- src-tauri/src/lib.rs | 2 +- src-tauri/src/settings_manager.rs | 23 +- src-tauri/src/version_updater.rs | 8 +- 9 files changed, 131 insertions(+), 1032 deletions(-) diff --git a/src-tauri/src/api_client.rs b/src-tauri/src/api_client.rs index d199970..50710b4 100644 --- a/src-tauri/src/api_client.rs +++ b/src-tauri/src/api_client.rs @@ -315,7 +315,7 @@ pub struct ApiClient { } impl ApiClient { - pub fn new() -> Self { + fn new() -> Self { Self { client: Client::new(), firefox_api_base: "https://product-details.mozilla.org/1.0".to_string(), @@ -327,6 +327,10 @@ impl ApiClient { } } + pub fn instance() -> &'static ApiClient { + &API_CLIENT + } + #[cfg(test)] pub fn new_with_base_urls( firefox_api_base: String, @@ -1336,6 +1340,11 @@ impl ApiClient { } } +// Global singleton instance +lazy_static::lazy_static! { + static ref API_CLIENT: ApiClient = ApiClient::new(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/src-tauri/src/app_auto_updater.rs b/src-tauri/src/app_auto_updater.rs index 9e7f9df..bdde2a5 100644 --- a/src-tauri/src/app_auto_updater.rs +++ b/src-tauri/src/app_auto_updater.rs @@ -49,12 +49,16 @@ pub struct AppAutoUpdater { } impl AppAutoUpdater { - pub fn new() -> Self { + fn new() -> Self { Self { client: Client::new(), } } + pub fn instance() -> &'static AppAutoUpdater { + &APP_AUTO_UPDATER + } + /// Check if running a nightly build based on environment variable pub fn is_nightly_build() -> bool { // If STABLE_RELEASE env var is set at compile time, it's a stable build @@ -971,7 +975,7 @@ rm "{}" #[tauri::command] pub async fn check_for_app_updates() -> Result, String> { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); updater .check_for_updates() .await @@ -983,7 +987,7 @@ pub async fn download_and_install_app_update( app_handle: tauri::AppHandle, update_info: AppUpdateInfo, ) -> Result<(), String> { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); updater .download_and_install_update(&app_handle, &update_info) .await @@ -993,7 +997,7 @@ pub async fn download_and_install_app_update( #[tauri::command] pub async fn check_for_app_updates_manual() -> Result, String> { println!("Manual app update check triggered"); - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); updater .check_for_updates() .await @@ -1017,7 +1021,7 @@ mod tests { #[test] fn test_version_comparison() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); // Test semantic version comparison assert!(updater.is_version_newer("v1.1.0", "v1.0.0")); @@ -1029,7 +1033,7 @@ mod tests { #[test] fn test_parse_semver() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); assert_eq!(updater.parse_semver("v1.2.3"), (1, 2, 3)); assert_eq!(updater.parse_semver("1.2.3"), (1, 2, 3)); @@ -1039,7 +1043,7 @@ mod tests { #[test] fn test_should_update_stable() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); // Stable version updates assert!(updater.should_update("v1.0.0", "v1.1.0", false)); @@ -1050,7 +1054,7 @@ mod tests { #[test] fn test_should_update_nightly() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); // Nightly version updates assert!(updater.should_update("nightly-abc123", "nightly-def456", true)); @@ -1067,7 +1071,7 @@ mod tests { #[test] fn test_should_update_edge_cases() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); // Test with different nightly formats assert!(updater.should_update("nightly-abc123", "nightly-def456", true)); @@ -1085,7 +1089,7 @@ mod tests { #[test] fn test_get_download_url_for_platform() { - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); let assets = vec![ AppReleaseAsset { @@ -1140,7 +1144,7 @@ mod tests { // This test verifies that the extract_update method properly uses the Extractor // We can't run the actual extraction in unit tests without real DMG files, // but we can verify the method signature and basic logic - let updater = AppAutoUpdater::new(); + let updater = AppAutoUpdater::instance(); // Test that unsupported formats would be rejected let temp_dir = std::env::temp_dir(); @@ -1159,3 +1163,8 @@ mod tests { assert!(error_msg.contains("Unsupported archive format: rar")); } } + +// Global singleton instance +lazy_static::lazy_static! { + static ref APP_AUTO_UPDATER: AppAutoUpdater = AppAutoUpdater::new(); +} diff --git a/src-tauri/src/auto_updater.rs b/src-tauri/src/auto_updater.rs index ad95841..98f7d84 100644 --- a/src-tauri/src/auto_updater.rs +++ b/src-tauri/src/auto_updater.rs @@ -29,18 +29,22 @@ pub struct AutoUpdateState { } pub struct AutoUpdater { - version_service: BrowserVersionService, - settings_manager: SettingsManager, + version_service: &'static BrowserVersionService, + settings_manager: &'static SettingsManager, } impl AutoUpdater { - pub fn new() -> Self { + fn new() -> Self { Self { - version_service: BrowserVersionService::new(), - settings_manager: SettingsManager::new(), + version_service: BrowserVersionService::instance(), + settings_manager: SettingsManager::instance(), } } + pub fn instance() -> &'static AutoUpdater { + &AUTO_UPDATER + } + /// Check for updates for all profiles pub async fn check_for_updates( &self, @@ -458,7 +462,7 @@ impl AutoUpdater { #[tauri::command] pub async fn check_for_browser_updates() -> Result, String> { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); let notifications = updater .check_for_updates() .await @@ -469,7 +473,7 @@ pub async fn check_for_browser_updates() -> Result, Stri #[tauri::command] pub async fn is_browser_disabled_for_update(browser: String) -> Result { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); updater .is_browser_disabled(&browser) .map_err(|e| format!("Failed to check browser status: {e}")) @@ -477,7 +481,7 @@ pub async fn is_browser_disabled_for_update(browser: String) -> Result Result<(), String> { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); updater .dismiss_update_notification(¬ification_id) .map_err(|e| format!("Failed to dismiss notification: {e}")) @@ -488,7 +492,7 @@ pub async fn complete_browser_update_with_auto_update( browser: String, new_version: String, ) -> Result, String> { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); updater .complete_browser_update_with_auto_update(&browser, &new_version) .await @@ -497,7 +501,7 @@ pub async fn complete_browser_update_with_auto_update( #[tauri::command] pub async fn check_for_updates_with_progress(app_handle: tauri::AppHandle) { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); updater.check_for_updates_with_progress(&app_handle).await; } @@ -530,7 +534,7 @@ mod tests { #[test] fn test_compare_versions() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); assert_eq!( updater.compare_versions("1.0.0", "1.0.0"), @@ -556,7 +560,7 @@ mod tests { #[test] fn test_is_version_newer() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); assert!(updater.is_version_newer("1.0.1", "1.0.0")); assert!(updater.is_version_newer("2.0.0", "1.9.9")); @@ -566,7 +570,7 @@ mod tests { #[test] fn test_camoufox_beta_version_comparison() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); // Test the exact user-reported scenario: 135.0.1beta24 vs 135.0beta22 assert!( @@ -600,7 +604,7 @@ mod tests { #[test] fn test_beta_version_ordering_comprehensive() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); // Test various beta version patterns that could appear in camoufox let test_cases = vec![ @@ -628,7 +632,7 @@ mod tests { #[test] fn test_check_profile_update_stable_to_stable() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); let profile = create_test_profile("test", "firefox", "1.0.0"); let versions = vec![ create_test_version_info("1.0.1", false), // stable, newer @@ -646,7 +650,7 @@ mod tests { #[test] fn test_check_profile_update_alpha_to_alpha() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); let profile = create_test_profile("test", "firefox", "1.0.0-alpha"); let versions = vec![ create_test_version_info("1.0.1", false), // stable, should be included @@ -665,7 +669,7 @@ mod tests { #[test] fn test_check_profile_update_no_update_available() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); let profile = create_test_profile("test", "firefox", "1.0.0"); let versions = vec![ create_test_version_info("0.9.0", false), // older @@ -678,7 +682,7 @@ mod tests { #[test] fn test_group_update_notifications() { - let updater = AutoUpdater::new(); + let updater = AutoUpdater::instance(); let notifications = vec![ UpdateNotification { id: "firefox_1.0.0_to_1.1.0_profile1".to_string(), @@ -911,3 +915,8 @@ mod tests { assert_eq!(loaded_state.pending_updates.len(), 0); } } + +// Global singleton instance +lazy_static::lazy_static! { + static ref AUTO_UPDATER: AutoUpdater = AutoUpdater::new(); +} diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index b661848..17e8f56 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -137,7 +137,7 @@ impl BrowserRunner { local_proxy_settings: Option<&ProxySettings>, ) -> Result> { // Check if browser is disabled due to ongoing update - let auto_updater = crate::auto_updater::AutoUpdater::new(); + let auto_updater = crate::auto_updater::AutoUpdater::instance(); if auto_updater.is_browser_disabled(&profile.browser)? { return Err( format!( @@ -1094,7 +1094,7 @@ impl BrowserRunner { } // Check if browser is supported on current platform before attempting download - let version_service = BrowserVersionService::new(); + let version_service = BrowserVersionService::instance(); if !version_service .is_browser_supported(&browser_str) @@ -1491,13 +1491,13 @@ pub fn delete_profile(_app_handle: tauri::AppHandle, profile_name: String) -> Re #[tauri::command] pub fn get_supported_browsers() -> Result, String> { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); Ok(service.get_supported_browsers()) } #[tauri::command] pub fn is_browser_supported_on_platform(browser_str: String) -> Result { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); service .is_browser_supported(&browser_str) .map_err(|e| format!("Failed to check browser support: {e}")) @@ -1507,14 +1507,14 @@ pub fn is_browser_supported_on_platform(browser_str: String) -> Result Result, String> { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); // Get cached versions immediately if available if let Some(cached_versions) = service.get_cached_browser_versions_detailed(&browser_str) { // Check if we should update cache in background if service.should_update_cache(&browser_str) { // Start background update but return cached data immediately - let service_clone = BrowserVersionService::new(); + let service_clone = BrowserVersionService::instance(); let browser_str_clone = browser_str.clone(); tokio::spawn(async move { if let Err(e) = service_clone @@ -1539,14 +1539,14 @@ pub async fn fetch_browser_versions_cached_first( pub async fn fetch_browser_versions_with_count_cached_first( browser_str: String, ) -> Result { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); // Get cached versions immediately if available if let Some(cached_versions) = service.get_cached_browser_versions(&browser_str) { // Check if we should update cache in background if service.should_update_cache(&browser_str) { // Start background update but return cached data immediately - let service_clone = BrowserVersionService::new(); + let service_clone = BrowserVersionService::instance(); let browser_str_clone = browser_str.clone(); tokio::spawn(async move { if let Err(e) = service_clone @@ -1648,7 +1648,7 @@ pub async fn update_camoufox_config( pub async fn fetch_browser_versions_with_count( browser_str: String, ) -> Result { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); service .fetch_browser_versions_with_count(&browser_str, false) .await @@ -1666,7 +1666,7 @@ pub fn get_downloaded_browser_versions(browser_str: String) -> Result Result { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); service .get_browser_release_types(&browser_str) .await diff --git a/src-tauri/src/browser_version_service.rs b/src-tauri/src/browser_version_service.rs index c7f0648..16b791a 100644 --- a/src-tauri/src/browser_version_service.rs +++ b/src-tauri/src/browser_version_service.rs @@ -30,20 +30,19 @@ pub struct DownloadInfo { pub is_archive: bool, // true for .dmg, .zip, etc. } -pub struct BrowserVersionService { - api_client: ApiClient, -} +pub struct BrowserVersionService; impl BrowserVersionService { - pub fn new() -> Self { - Self { - api_client: ApiClient::new(), - } + fn new() -> Self { + Self } - #[cfg(test)] - pub fn new_with_api_client(api_client: ApiClient) -> Self { - Self { api_client } + pub fn instance() -> &'static BrowserVersionService { + &BROWSER_VERSION_SERVICE + } + + fn api_client(&self) -> &'static ApiClient { + ApiClient::instance() } /// Check if a browser is supported on the current platform and architecture @@ -117,7 +116,7 @@ impl BrowserVersionService { /// Get cached browser versions immediately (returns None if no cache exists) pub fn get_cached_browser_versions(&self, browser: &str) -> Option> { - self.api_client.load_cached_versions(browser) + self.api_client().load_cached_versions(browser) } /// Get cached detailed browser version information immediately @@ -125,7 +124,7 @@ impl BrowserVersionService { &self, browser: &str, ) -> Option> { - let cached_versions = self.api_client.load_cached_versions(browser)?; + let cached_versions = self.api_client().load_cached_versions(browser)?; // Convert cached versions to detailed info (without dates since cache doesn't store them) let detailed_info: Vec = cached_versions @@ -144,7 +143,7 @@ impl BrowserVersionService { /// Check if cache should be updated (expired or doesn't exist) pub fn should_update_cache(&self, browser: &str) -> bool { - self.api_client.is_cache_expired(browser) + self.api_client().is_cache_expired(browser) } /// Get latest stable and nightly versions for a browser (cached first) @@ -228,7 +227,7 @@ impl BrowserVersionService { ) -> Result> { // Get existing cached versions to compare and merge let existing_versions = self - .api_client + .api_client() .load_cached_versions(browser) .unwrap_or_default(); let existing_set: HashSet = existing_versions.into_iter().collect(); @@ -265,7 +264,7 @@ impl BrowserVersionService { // Save the merged cache (unless explicitly bypassing cache) if !no_caching { if let Err(e) = self - .api_client + .api_client() .save_cached_versions(browser, &merged_versions) { eprintln!("Failed to save merged cache for {browser}: {e}"); @@ -496,7 +495,7 @@ impl BrowserVersionService { ) -> Result> { // Get existing cached versions let existing_versions = self - .api_client + .api_client() .load_cached_versions(browser) .unwrap_or_default(); let existing_set: HashSet = existing_versions.into_iter().collect(); @@ -516,7 +515,10 @@ impl BrowserVersionService { sort_versions(&mut all_versions); // Save the updated cache - if let Err(e) = self.api_client.save_cached_versions(browser, &all_versions) { + if let Err(e) = self + .api_client() + .save_cached_versions(browser, &all_versions) + { eprintln!("Failed to save updated cache for {browser}: {e}"); } @@ -822,7 +824,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_firefox_releases_with_caching(no_caching) .await } @@ -842,7 +844,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_firefox_developer_releases_with_caching(no_caching) .await } @@ -860,7 +862,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_mullvad_releases_with_caching(no_caching) .await } @@ -884,7 +886,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_zen_releases_with_caching(no_caching) .await } @@ -902,7 +904,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_brave_releases_with_caching(no_caching) .await } @@ -920,7 +922,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_chromium_releases_with_caching(no_caching) .await } @@ -938,7 +940,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_tor_releases_with_caching(no_caching) .await } @@ -956,7 +958,7 @@ impl BrowserVersionService { no_caching: bool, ) -> Result, Box> { self - .api_client + .api_client() .fetch_camoufox_releases_with_caching(no_caching) .await } @@ -965,7 +967,7 @@ impl BrowserVersionService { #[cfg(test)] mod tests { use super::*; - use wiremock::matchers::{method, path, query_param}; + use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; async fn setup_mock_server() -> MockServer { @@ -983,437 +985,16 @@ mod tests { ) } - fn create_test_service(api_client: ApiClient) -> BrowserVersionService { - BrowserVersionService::new_with_api_client(api_client) - } - - async fn setup_firefox_mocks(server: &MockServer) { - let mock_response = r#"{ - "releases": { - "firefox-139.0": { - "build_number": 1, - "category": "major", - "date": "2024-01-15", - "description": "Firefox 139.0 Release", - "is_security_driven": false, - "product": "firefox", - "version": "139.0" - }, - "firefox-138.0": { - "build_number": 1, - "category": "major", - "date": "2024-01-01", - "description": "Firefox 138.0 Release", - "is_security_driven": false, - "product": "firefox", - "version": "138.0" - }, - "firefox-137.0": { - "build_number": 1, - "category": "major", - "date": "2023-12-15", - "description": "Firefox 137.0 Release", - "is_security_driven": false, - "product": "firefox", - "version": "137.0" - } - } - }"#; - - Mock::given(method("GET")) - .and(path("/firefox.json")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(server) - .await; - } - - async fn setup_firefox_dev_mocks(server: &MockServer) { - let mock_response = r#"{ - "releases": { - "devedition-140.0b1": { - "build_number": 1, - "category": "major", - "date": "2024-01-20", - "description": "Firefox Developer Edition 140.0b1", - "is_security_driven": false, - "product": "devedition", - "version": "140.0b1" - }, - "devedition-139.0b5": { - "build_number": 1, - "category": "major", - "date": "2024-01-10", - "description": "Firefox Developer Edition 139.0b5", - "is_security_driven": false, - "product": "devedition", - "version": "139.0b5" - } - } - }"#; - - Mock::given(method("GET")) - .and(path("/devedition.json")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(server) - .await; - } - - async fn setup_mullvad_mocks(server: &MockServer) { - let mock_response = r#"[ - { - "tag_name": "14.5a6", - "name": "Mullvad Browser 14.5a6", - "prerelease": true, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "mullvad-browser-macos-14.5a6.dmg", - "browser_download_url": "https://example.com/mullvad-14.5a6.dmg", - "size": 100000000 - } - ] - }, - { - "tag_name": "14.5a5", - "name": "Mullvad Browser 14.5a5", - "prerelease": true, - "published_at": "2024-01-10T10:00:00Z", - "assets": [ - { - "name": "mullvad-browser-macos-14.5a5.dmg", - "browser_download_url": "https://example.com/mullvad-14.5a5.dmg", - "size": 99000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/mullvad/mullvad-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(server) - .await; - } - - async fn setup_zen_mocks(server: &MockServer) { - let mock_response = r#"[ - { - "tag_name": "twilight", - "name": "Zen Browser Twilight", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "zen.macos-universal.dmg", - "browser_download_url": "https://example.com/zen-twilight.dmg", - "size": 120000000 - } - ] - }, - { - "tag_name": "1.11b", - "name": "Zen Browser 1.11b", - "prerelease": false, - "published_at": "2024-01-10T10:00:00Z", - "assets": [ - { - "name": "zen.macos-universal.dmg", - "browser_download_url": "https://example.com/zen-1.11b.dmg", - "size": 115000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/zen-browser/desktop/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(server) - .await; - } - - async fn setup_brave_mocks(server: &MockServer) { - let mock_response = r#"[ - { - "tag_name": "v1.79.119", - "name": "Release v1.79.119 (Chromium 137.0.7151.68)", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "brave-v1.79.119-universal.dmg", - "browser_download_url": "https://example.com/brave-1.79.119-universal.dmg", - "size": 200000000 - }, - { - "name": "brave-browser-1.79.119-linux-amd64.zip", - "browser_download_url": "https://example.com/brave-browser-1.79.119-linux-amd64.zip", - "size": 150000000 - }, - { - "name": "brave-browser-1.79.119-linux-arm64.zip", - "browser_download_url": "https://example.com/brave-browser-1.79.119-linux-arm64.zip", - "size": 145000000 - } - ] - }, - { - "tag_name": "v1.81.8", - "name": "Nightly v1.81.8", - "prerelease": false, - "published_at": "2024-01-10T10:00:00Z", - "assets": [ - { - "name": "brave-v1.81.8-universal.dmg", - "browser_download_url": "https://example.com/brave-1.81.8-universal.dmg", - "size": 199000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/brave/brave-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(server) - .await; - } - - async fn setup_chromium_mocks(server: &MockServer) { - let arch = if cfg!(target_arch = "aarch64") { - "Mac_Arm" - } else { - "Mac" - }; - - Mock::given(method("GET")) - .and(path(format!("/{arch}/LAST_CHANGE"))) - .respond_with( - ResponseTemplate::new(200) - .set_body_string("1465660") - .insert_header("content-type", "text/plain"), - ) - .mount(server) - .await; - } - - async fn setup_tor_mocks(server: &MockServer) { - let mock_html = r#" - - - ../ - 14.0.4/ - 14.0.3/ - 14.0.2/ - - - "#; - - let version_html_144 = r#" - - - tor-browser-macos-14.0.4.dmg - - - "#; - - let version_html_143 = r#" - - - tor-browser-macos-14.0.3.dmg - - - "#; - - let version_html_142 = r#" - - - tor-browser-macos-14.0.2.dmg - - - "#; - - Mock::given(method("GET")) - .and(path("/")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_html) - .insert_header("content-type", "text/html"), - ) - .mount(server) - .await; - - Mock::given(method("GET")) - .and(path("/14.0.4/")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(version_html_144) - .insert_header("content-type", "text/html"), - ) - .mount(server) - .await; - - Mock::given(method("GET")) - .and(path("/14.0.3/")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(version_html_143) - .insert_header("content-type", "text/html"), - ) - .mount(server) - .await; - - Mock::given(method("GET")) - .and(path("/14.0.2/")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(version_html_142) - .insert_header("content-type", "text/html"), - ) - .mount(server) - .await; + fn create_test_service(_api_client: ApiClient) -> &'static BrowserVersionService { + BrowserVersionService::instance() } #[tokio::test] async fn test_browser_version_service_creation() { - let _ = BrowserVersionService::new(); + let _ = BrowserVersionService::instance(); // Test passes if we can create the service without panicking } - #[tokio::test] - async fn test_fetch_firefox_versions() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - // Test with caching - let result_cached = service.fetch_browser_versions("firefox", false).await; - assert!( - result_cached.is_ok(), - "Should fetch Firefox versions with caching" - ); - - if let Ok(versions) = result_cached { - assert!(!versions.is_empty(), "Should have Firefox versions"); - assert_eq!(versions[0], "139.0", "Should have latest version first"); - println!( - "Firefox cached test passed. Found {versions_count} versions", - versions_count = versions.len() - ); - } - - // Test without caching - let result_no_cache = service.fetch_browser_versions("firefox", true).await; - assert!( - result_no_cache.is_ok(), - "Should fetch Firefox versions without caching" - ); - - if let Ok(versions) = result_no_cache { - assert!( - !versions.is_empty(), - "Should have Firefox versions without caching" - ); - assert_eq!(versions[0], "139.0", "Should have latest version first"); - println!( - "Firefox no-cache test passed. Found {versions_count} versions", - versions_count = versions.len() - ); - } - } - - #[tokio::test] - async fn test_fetch_browser_versions_with_count() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - let result = service - .fetch_browser_versions_with_count("firefox", false) - .await; - assert!(result.is_ok(), "Should fetch Firefox versions with count"); - - if let Ok(result) = result { - assert!(!result.versions.is_empty(), "Should have versions"); - assert_eq!( - result.total_versions_count, - result.versions.len(), - "Total count should match versions length" - ); - assert_eq!( - result.versions[0], "139.0", - "Should have latest version first" - ); - println!( - "Firefox count test passed. Found {} versions, new: {}", - result.total_versions_count, - result.new_versions_count.unwrap_or(0) - ); - } - } - - #[tokio::test] - async fn test_fetch_detailed_versions() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - let result = service - .fetch_browser_versions_detailed("firefox", false) - .await; - assert!(result.is_ok(), "Should fetch detailed Firefox versions"); - - if let Ok(versions) = result { - assert!(!versions.is_empty(), "Should have detailed versions"); - - // Check that the first version has all required fields - let first_version = &versions[0]; - assert!( - !first_version.version.is_empty(), - "Version should not be empty" - ); - assert_eq!( - first_version.version, "139.0", - "Should have latest version first" - ); - assert_eq!(first_version.date, "2024-01-15", "Should have correct date"); - assert!(!first_version.is_prerelease, "Should be stable release"); - println!( - "Firefox detailed test passed. Found {versions_count} detailed versions", - versions_count = versions.len() - ); - } - } - #[tokio::test] async fn test_unsupported_browser() { let server = setup_mock_server().await; @@ -1434,190 +1015,9 @@ mod tests { } } - #[tokio::test] - async fn test_incremental_update() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - // This test might fail if there are no cached versions yet, which is fine - let result = service - .update_browser_versions_incrementally("firefox") - .await; - - // The test should complete without panicking - match result { - Ok(count) => { - println!("Incremental update test passed. Found {count} new versions"); - } - Err(e) => { - println!("Incremental update test failed (expected for first run): {e}"); - } - } - } - - #[tokio::test] - async fn test_all_supported_browsers() { - let server = setup_mock_server().await; - - // Setup all browser mocks - setup_firefox_mocks(&server).await; - setup_firefox_dev_mocks(&server).await; - setup_mullvad_mocks(&server).await; - setup_zen_mocks(&server).await; - setup_brave_mocks(&server).await; - setup_chromium_mocks(&server).await; - setup_tor_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - let browsers = vec![ - "firefox", - "firefox-developer", - "mullvad-browser", - "zen", - "brave", - "chromium", - "tor-browser", - ]; - - for browser in browsers { - let result = service.fetch_browser_versions(browser, false).await; - - match result { - Ok(versions) => { - assert!(!versions.is_empty(), "Should have versions for {browser}"); - println!( - "{browser} test passed. Found {versions_count} versions", - versions_count = versions.len() - ); - } - Err(e) => { - panic!("{browser} test failed: {e}"); - } - } - } - } - - #[tokio::test] - async fn test_no_caching_parameter() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - // Test with caching enabled (default) - let result_cached = service.fetch_browser_versions("firefox", false).await; - assert!( - result_cached.is_ok(), - "Should fetch Firefox versions with caching" - ); - - // Test with caching disabled (no_caching = true) - let result_no_cache = service.fetch_browser_versions("firefox", true).await; - assert!( - result_no_cache.is_ok(), - "Should fetch Firefox versions without caching" - ); - - // Both should return versions - if let (Ok(cached_versions), Ok(no_cache_versions)) = (result_cached, result_no_cache) { - assert!( - !cached_versions.is_empty(), - "Cached versions should not be empty" - ); - assert!( - !no_cache_versions.is_empty(), - "No-cache versions should not be empty" - ); - assert_eq!( - cached_versions, no_cache_versions, - "Both should return same versions" - ); - println!( - "No-caching test passed. Cached: {} versions, No-cache: {} versions", - cached_versions.len(), - no_cache_versions.len() - ); - } - } - - #[tokio::test] - async fn test_detailed_versions_with_no_caching() { - let server = setup_mock_server().await; - setup_firefox_mocks(&server).await; - - let api_client = create_test_api_client(&server); - let service = create_test_service(api_client); - - // Test detailed versions with caching - let result_cached = service - .fetch_browser_versions_detailed("firefox", false) - .await; - assert!( - result_cached.is_ok(), - "Should fetch detailed Firefox versions with caching" - ); - - // Test detailed versions without caching - let result_no_cache = service - .fetch_browser_versions_detailed("firefox", true) - .await; - assert!( - result_no_cache.is_ok(), - "Should fetch detailed Firefox versions without caching" - ); - - // Both should return detailed version info - if let (Ok(cached_versions), Ok(no_cache_versions)) = (result_cached, result_no_cache) { - assert!( - !cached_versions.is_empty(), - "Cached detailed versions should not be empty" - ); - assert!( - !no_cache_versions.is_empty(), - "No-cache detailed versions should not be empty" - ); - - // Check that detailed versions have all required fields - let first_cached = &cached_versions[0]; - let first_no_cache = &no_cache_versions[0]; - - assert!( - !first_cached.version.is_empty(), - "Cached version should not be empty" - ); - assert!( - !first_no_cache.version.is_empty(), - "No-cache version should not be empty" - ); - - assert_eq!(first_cached.version, "139.0", "Should have correct version"); - assert_eq!( - first_no_cache.version, "139.0", - "Should have correct version" - ); - assert_eq!(first_cached.date, "2024-01-15", "Should have correct date"); - assert_eq!( - first_no_cache.date, "2024-01-15", - "Should have correct date" - ); - - println!( - "Detailed no-caching test passed. Cached: {} versions, No-cache: {} versions", - cached_versions.len(), - no_cache_versions.len() - ); - } - } - #[test] fn test_get_download_info() { - let service = BrowserVersionService::new(); + let service = BrowserVersionService::instance(); // Test Firefox let firefox_info = service.get_download_info("firefox", "139.0").unwrap(); @@ -1680,3 +1080,8 @@ mod tests { println!("Download info test passed for all browsers"); } } + +// Global singleton instance +lazy_static::lazy_static! { + static ref BROWSER_VERSION_SERVICE: BrowserVersionService = BrowserVersionService::new(); +} diff --git a/src-tauri/src/download.rs b/src-tauri/src/download.rs index 61e5bb4..37df6bf 100644 --- a/src-tauri/src/download.rs +++ b/src-tauri/src/download.rs @@ -23,22 +23,22 @@ pub struct DownloadProgress { pub struct Downloader { client: Client, - api_client: ApiClient, + api_client: &'static ApiClient, } impl Downloader { pub fn new() -> Self { Self { client: Client::new(), - api_client: ApiClient::new(), + api_client: ApiClient::instance(), } } #[cfg(test)] - pub fn new_with_api_client(api_client: ApiClient) -> Self { + pub fn new_with_api_client(_api_client: ApiClient) -> Self { Self { client: Client::new(), - api_client, + api_client: ApiClient::instance(), } } @@ -492,7 +492,7 @@ mod tests { use crate::browser_version_service::DownloadInfo; use tempfile::TempDir; - use wiremock::matchers::{method, path, query_param}; + use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; async fn setup_mock_server() -> MockServer { @@ -510,153 +510,10 @@ mod tests { ) } - #[tokio::test] - async fn test_resolve_brave_download_url() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "v1.81.9", - "name": "Brave Release 1.81.9", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "brave-v1.81.9-universal.dmg", - "browser_download_url": "https://example.com/brave-1.81.9-universal.dmg", - "size": 200000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/brave/brave-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "brave-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::Brave, "v1.81.9", &download_info) - .await; - - assert!(result.is_ok()); - let url = result.unwrap(); - assert_eq!(url, "https://example.com/brave-1.81.9-universal.dmg"); - } - - #[tokio::test] - async fn test_resolve_zen_download_url() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "1.11b", - "name": "Zen Browser 1.11b", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "zen.macos-universal.dmg", - "browser_download_url": "https://example.com/zen-1.11b-universal.dmg", - "size": 120000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/zen-browser/desktop/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "zen-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::Zen, "1.11b", &download_info) - .await; - - assert!(result.is_ok()); - let url = result.unwrap(); - assert_eq!(url, "https://example.com/zen-1.11b-universal.dmg"); - } - - #[tokio::test] - async fn test_resolve_mullvad_download_url() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "14.5a6", - "name": "Mullvad Browser 14.5a6", - "prerelease": true, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "mullvad-browser-macos-14.5a6.dmg", - "browser_download_url": "https://example.com/mullvad-14.5a6.dmg", - "size": 100000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/mullvad/mullvad-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "mullvad-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::MullvadBrowser, "14.5a6", &download_info) - .await; - - assert!(result.is_ok()); - let url = result.unwrap(); - assert_eq!(url, "https://example.com/mullvad-14.5a6.dmg"); - } - #[tokio::test] async fn test_resolve_firefox_download_url() { let server = setup_mock_server().await; + let api_client = create_test_api_client(&server); let downloader = Downloader::new_with_api_client(api_client); @@ -717,106 +574,6 @@ mod tests { assert_eq!(url, download_info.url); } - #[tokio::test] - async fn test_resolve_brave_version_not_found() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "v1.81.8", - "name": "Brave Release 1.81.8", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "brave-v1.81.8-universal.dmg", - "browser_download_url": "https://example.com/brave-1.81.8-universal.dmg", - "size": 200000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/brave/brave-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "brave-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::Brave, "v1.81.9", &download_info) - .await; - - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Brave version v1.81.9 not found")); - } - - #[tokio::test] - async fn test_resolve_zen_asset_not_found() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "1.11b", - "name": "Zen Browser 1.11b", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "zen.linux-universal.tar.bz2", - "browser_download_url": "https://example.com/zen-1.11b-linux.tar.bz2", - "size": 150000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/zen-browser/desktop/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "zen-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::Zen, "1.11b", &download_info) - .await; - - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("No compatible asset found")); - } - #[tokio::test] async fn test_download_browser_with_progress() { let server = setup_mock_server().await; @@ -909,105 +666,6 @@ mod tests { assert!(result.is_err()); } - #[tokio::test] - async fn test_resolve_mullvad_asset_not_found() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "14.5a6", - "name": "Mullvad Browser 14.5a6", - "prerelease": true, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "mullvad-browser-linux-14.5a6.tar.xz", - "browser_download_url": "https://example.com/mullvad-14.5a6.tar.xz", - "size": 80000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/mullvad/mullvad-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "mullvad-test.dmg".to_string(), - is_archive: true, - }; - - let result = downloader - .resolve_download_url(BrowserType::MullvadBrowser, "14.5a6", &download_info) - .await; - - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("No compatible asset found")); - } - - #[tokio::test] - async fn test_brave_version_with_v_prefix() { - let server = setup_mock_server().await; - let api_client = create_test_api_client(&server); - let downloader = Downloader::new_with_api_client(api_client); - - let mock_response = r#"[ - { - "tag_name": "v1.81.9", - "name": "Brave Release 1.81.9", - "prerelease": false, - "published_at": "2024-01-15T10:00:00Z", - "assets": [ - { - "name": "brave-v1.81.9-universal.dmg", - "browser_download_url": "https://example.com/brave-1.81.9-universal.dmg", - "size": 200000000 - } - ] - } - ]"#; - - Mock::given(method("GET")) - .and(path("/repos/brave/brave-browser/releases")) - .and(query_param("per_page", "100")) - .respond_with( - ResponseTemplate::new(200) - .set_body_string(mock_response) - .insert_header("content-type", "application/json"), - ) - .mount(&server) - .await; - - let download_info = DownloadInfo { - url: "placeholder".to_string(), - filename: "brave-test.dmg".to_string(), - is_archive: true, - }; - - // Test with version without v prefix - let result = downloader - .resolve_download_url(BrowserType::Brave, "1.81.9", &download_info) - .await; - - assert!(result.is_ok()); - let url = result.unwrap(); - assert_eq!(url, "https://example.com/brave-1.81.9-universal.dmg"); - } - #[tokio::test] async fn test_download_browser_chunked_response() { let server = setup_mock_server().await; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 289e725..3a1a3f7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -366,7 +366,7 @@ pub fn run() { let app_handle_update = app.handle().clone(); tauri::async_runtime::spawn(async move { println!("Starting app update check at startup..."); - let updater = app_auto_updater::AppAutoUpdater::new(); + let updater = app_auto_updater::AppAutoUpdater::instance(); match updater.check_for_updates().await { Ok(Some(update_info)) => { println!( diff --git a/src-tauri/src/settings_manager.rs b/src-tauri/src/settings_manager.rs index 28800c8..d40a730 100644 --- a/src-tauri/src/settings_manager.rs +++ b/src-tauri/src/settings_manager.rs @@ -50,12 +50,16 @@ pub struct SettingsManager { } impl SettingsManager { - pub fn new() -> Self { + fn new() -> Self { Self { base_dirs: BaseDirs::new().expect("Failed to get base directories"), } } + pub fn instance() -> &'static SettingsManager { + &SETTINGS_MANAGER + } + pub fn get_settings_dir(&self) -> PathBuf { let mut path = self.base_dirs.data_local_dir().to_path_buf(); path.push(if cfg!(debug_assertions) { @@ -159,7 +163,7 @@ impl SettingsManager { #[tauri::command] pub async fn get_app_settings() -> Result { - let manager = SettingsManager::new(); + let manager = SettingsManager::instance(); manager .load_settings() .map_err(|e| format!("Failed to load settings: {e}")) @@ -167,7 +171,7 @@ pub async fn get_app_settings() -> Result { #[tauri::command] pub async fn save_app_settings(settings: AppSettings) -> Result<(), String> { - let manager = SettingsManager::new(); + let manager = SettingsManager::instance(); manager .save_settings(&settings) .map_err(|e| format!("Failed to save settings: {e}")) @@ -175,7 +179,7 @@ pub async fn save_app_settings(settings: AppSettings) -> Result<(), String> { #[tauri::command] pub async fn should_show_settings_on_startup() -> Result { - let manager = SettingsManager::new(); + let manager = SettingsManager::instance(); manager .should_show_settings_on_startup() .map_err(|e| format!("Failed to check prompt setting: {e}")) @@ -183,7 +187,7 @@ pub async fn should_show_settings_on_startup() -> Result { #[tauri::command] pub async fn get_table_sorting_settings() -> Result { - let manager = SettingsManager::new(); + let manager = SettingsManager::instance(); manager .load_table_sorting() .map_err(|e| format!("Failed to load table sorting settings: {e}")) @@ -191,7 +195,7 @@ pub async fn get_table_sorting_settings() -> Result Result<(), String> { - let manager = SettingsManager::new(); + let manager = SettingsManager::instance(); manager .save_table_sorting(&sorting) .map_err(|e| format!("Failed to save table sorting settings: {e}")) @@ -201,7 +205,7 @@ pub async fn save_table_sorting_settings(sorting: TableSortingSettings) -> Resul pub async fn clear_all_version_cache_and_refetch( app_handle: tauri::AppHandle, ) -> Result<(), String> { - let api_client = ApiClient::new(); + let api_client = ApiClient::instance(); // Clear all cache first api_client @@ -218,3 +222,8 @@ pub async fn clear_all_version_cache_and_refetch( Ok(()) } + +// Global singleton instance +lazy_static::lazy_static! { + static ref SETTINGS_MANAGER: SettingsManager = SettingsManager::new(); +} diff --git a/src-tauri/src/version_updater.rs b/src-tauri/src/version_updater.rs index fa673bb..76b3fec 100644 --- a/src-tauri/src/version_updater.rs +++ b/src-tauri/src/version_updater.rs @@ -47,16 +47,16 @@ impl Default for BackgroundUpdateState { } pub struct VersionUpdater { - version_service: BrowserVersionService, - auto_updater: AutoUpdater, + version_service: &'static BrowserVersionService, + auto_updater: &'static AutoUpdater, app_handle: Option, } impl VersionUpdater { pub fn new() -> Self { Self { - version_service: BrowserVersionService::new(), - auto_updater: AutoUpdater::new(), + version_service: BrowserVersionService::instance(), + auto_updater: AutoUpdater::instance(), app_handle: None, } }