diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index 56e2ceb..0ae9706 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -1220,25 +1220,13 @@ impl BrowserRunner { { Ok(path) => path, Err(e) => { - // Check if the expected archive is already present (manual download) - let expected_archive_path = browser_dir.join(&download_info.filename); - if expected_archive_path.exists() { - println!( - "Download failed, but found existing archive at {}. Continuing with extraction.", - expected_archive_path.display() - ); - expected_archive_path - } else { - // Remove only the registry entry; keep any files (including a partially downloaded archive) - let _ = registry.remove_browser(&browser_str, &version); - let _ = registry.save(); - // Remove browser-version pair from downloading set on error - { - let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap(); - downloading.remove(&download_key); - } - return Err(format!("Failed to download browser: {e}").into()); - } + // Do NOT continue with extraction on failed downloads. Partial files may exist but are invalid. + // Clean registry entry and stop here so the UI can show a single, clear error. + let _ = registry.remove_browser(&browser_str, &version); + let _ = registry.save(); + let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap(); + downloading.remove(&download_key); + return Err(format!("Failed to download browser: {e}").into()); } }; diff --git a/src/components/create-profile-dialog.tsx b/src/components/create-profile-dialog.tsx index 127923a..83b686f 100644 --- a/src/components/create-profile-dialog.tsx +++ b/src/components/create-profile-dialog.tsx @@ -1,7 +1,7 @@ "use client"; import { invoke } from "@tauri-apps/api/core"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { GoPlus } from "react-icons/go"; import { LoadingButton } from "@/components/loading-button"; import { ProxyFormDialog } from "@/components/proxy-form-dialog"; @@ -109,12 +109,13 @@ export function CreateProfileDialog({ const [activeTab, setActiveTab] = useState("anti-detect"); // Regular browser states - const [selectedBrowser, setSelectedBrowser] = useState(); + const [selectedBrowser, setSelectedBrowser] = + useState("camoufox"); const [selectedProxyId, setSelectedProxyId] = useState(); const handleTabChange = (value: string) => { if (value === "regular") { - setSelectedBrowser(undefined); + setSelectedBrowser(null); } else if (value === "anti-detect") { setSelectedBrowser("camoufox"); } @@ -355,7 +356,7 @@ export function CreateProfileDialog({ // Reset all states setProfileName(""); - setSelectedBrowser(undefined); + setSelectedBrowser(null); setSelectedProxyId(undefined); setAvailableReleaseTypes({}); setCamoufoxReleaseTypes({}); @@ -366,40 +367,49 @@ export function CreateProfileDialog({ onClose(); }; - const isCreateDisabled = () => { - if (!profileName.trim()) return true; - - if (!selectedBrowser) return true; - - if (isBrowserCurrentlyDownloading(selectedBrowser)) return true; - - if (!isBrowserVersionAvailable(selectedBrowser)) return true; - - if (selectedBrowser === "camoufox") { - return !getBestAvailableVersion(camoufoxReleaseTypes, selectedBrowser); - } - - return !getBestAvailableVersion(availableReleaseTypes, selectedBrowser); - }; - const updateCamoufoxConfig = (key: keyof CamoufoxConfig, value: unknown) => { setCamoufoxConfig((prev) => ({ ...prev, [key]: value })); }; // Check if browser version is downloaded and available - const isBrowserVersionAvailable = (browserStr: string) => { - const releaseTypes = - browserStr === "camoufox" ? camoufoxReleaseTypes : availableReleaseTypes; - const bestVersion = getBestAvailableVersion(releaseTypes, browserStr); - return bestVersion && isVersionDownloaded(bestVersion.version); - }; + const isBrowserVersionAvailable = useCallback( + (browserStr: string) => { + const releaseTypes = + browserStr === "camoufox" + ? camoufoxReleaseTypes + : availableReleaseTypes; + const bestVersion = getBestAvailableVersion(releaseTypes, browserStr); + return bestVersion && isVersionDownloaded(bestVersion.version); + }, + [ + camoufoxReleaseTypes, + availableReleaseTypes, + isVersionDownloaded, + getBestAvailableVersion, + ], + ); // Check if browser is currently downloading - const isBrowserCurrentlyDownloading = (browserStr: string) => { - return isBrowserDownloading(browserStr); - }; + const isBrowserCurrentlyDownloading = useCallback( + (browserStr: string) => { + return isBrowserDownloading(browserStr); + }, + [isBrowserDownloading], + ); - console.log(selectedBrowser); + const isCreateDisabled = useMemo(() => { + if (!profileName.trim()) return true; + if (!selectedBrowser) return true; + if (isBrowserCurrentlyDownloading(selectedBrowser)) return true; + if (!isBrowserVersionAvailable(selectedBrowser)) return true; + + return false; + }, [ + profileName, + selectedBrowser, + isBrowserCurrentlyDownloading, + isBrowserVersionAvailable, + ]); return ( @@ -639,7 +649,7 @@ export function CreateProfileDialog({ Create diff --git a/src/hooks/use-browser-download.ts b/src/hooks/use-browser-download.ts index 0868914..372ac26 100644 --- a/src/hooks/use-browser-download.ts +++ b/src/hooks/use-browser-download.ts @@ -1,4 +1,5 @@ import { invoke } from "@tauri-apps/api/core"; +import type { Event as TauriEvent } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event"; import { useCallback, useEffect, useState } from "react"; import { getBrowserDisplayName } from "@/lib/browser-utils"; @@ -7,6 +8,7 @@ import { showDownloadToast, showErrorToast, showSuccessToast, + showToast, } from "@/lib/toast-utils"; interface GithubRelease { @@ -257,21 +259,22 @@ export function useBrowserDownload() { // Legacy isDownloading for backwards compatibility const isDownloading = downloadingBrowsers.size > 0; - // Listen for download progress events + // Listen for download progress events (browsers) and GeoIP progress events useEffect(() => { - let unlistenFn: (() => void) | null = null; + let unlistenBrowser: (() => void) | null = null; + let unlistenGeoip: (() => void) | null = null; - const setupListener = async () => { + const setupListeners = async () => { try { - unlistenFn = await listen( + // Browser binaries download progress + unlistenBrowser = await listen( "download-progress", - (event) => { + async (event: TauriEvent) => { const progress = event.payload; setDownloadProgress(progress); const browserName = getBrowserDisplayName(progress.browser); - // Show toast with progress if (progress.stage === "downloading") { const speedMBps = ( progress.speed_bytes_per_sec / @@ -291,6 +294,16 @@ export function useBrowserDownload() { } else if (progress.stage === "verifying") { showDownloadToast(browserName, progress.version, "verifying"); } else if (progress.stage === "completed") { + // On completion, refresh the downloaded versions for this browser and also refresh camoufox, + // since the Create dialog implicitly uses camoufox on the anti-detect tab + try { + await Promise.all([ + loadDownloadedVersions(progress.browser), + progress.browser !== "camoufox" + ? loadDownloadedVersions("camoufox") + : Promise.resolve([]), + ]); + } catch {} showDownloadToast(browserName, progress.version, "completed"); setDownloadProgress(null); } @@ -299,20 +312,65 @@ export function useBrowserDownload() { } catch (error) { console.error("Failed to setup download progress listener:", error); } + + try { + // GeoIP database download progress + unlistenGeoip = await listen<{ + stage: string; + percentage: number; + message: string; + }>( + "geoip-download-progress", + ( + event: TauriEvent<{ + stage: string; + percentage: number; + message: string; + }>, + ) => { + const { stage, percentage } = event.payload; + if (stage === "downloading") { + showToast({ + id: "geoip-download", + type: "download", + title: "Downloading GeoIP database", + stage: "downloading", + progress: { percentage }, + }); + } else if (stage === "completed") { + showToast({ + id: "geoip-download", + type: "download", + title: "GeoIP database downloaded successfully!", + stage: "completed", + }); + } + }, + ); + } catch (error) { + console.error("Failed to setup GeoIP progress listener:", error); + } }; - setupListener(); + void setupListeners(); return () => { - if (unlistenFn) { + if (unlistenBrowser) { try { - unlistenFn(); + unlistenBrowser(); } catch (error) { - console.error("Failed to cleanup download progress listener:", error); + console.error("Failed to cleanup browser download listener:", error); + } + } + if (unlistenGeoip) { + try { + unlistenGeoip(); + } catch (error) { + console.error("Failed to cleanup GeoIP progress listener:", error); } } }; - }, [formatTime]); + }, [formatTime, loadDownloadedVersions]); return { availableVersions,