reactor: better handling of camoufox management

This commit is contained in:
zhom
2025-12-25 12:07:41 +04:00
parent 7b756be072
commit 0240b18377
3 changed files with 132 additions and 21 deletions
+4 -4
View File
@@ -818,7 +818,7 @@ impl ApiClient {
Ok(filtered_releases) Ok(filtered_releases)
} }
/// Check if a Brave release has compatible assets for the given platform and architecture /// Check if a Camoufox release has compatible assets for the given platform and architecture
fn has_compatible_camoufox_asset( fn has_compatible_camoufox_asset(
&self, &self,
assets: &[crate::browser::GithubAsset], assets: &[crate::browser::GithubAsset],
@@ -835,9 +835,9 @@ impl ApiClient {
_ => return false, _ => return false,
}; };
// Look for assets matching the pattern: camoufox-{version}-{release}-{os}.{arch}.zip // Look for assets matching the pattern: camoufox-{version}-beta.{number}-{os}.{arch}.zip
// Use ends_with for precise matching to avoid false positives // The separator before OS is a dash, e.g., camoufox-135.0.1-beta.24-lin.x86_64.zip
let pattern = format!(".{os_name}.{arch_name}.zip"); let pattern = format!("-{os_name}.{arch_name}.zip");
assets.iter().any(|asset| { assets.iter().any(|asset| {
let name = asset.name.to_lowercase(); let name = asset.name.to_lowercase();
name.starts_with("camoufox-") && name.ends_with(&pattern) name.starts_with("camoufox-") && name.ends_with(&pattern)
+4 -2
View File
@@ -310,7 +310,8 @@ impl Downloader {
os: &str, os: &str,
arch: &str, arch: &str,
) -> Option<String> { ) -> Option<String> {
// Camoufox asset naming pattern: camoufox-{version}-{release}-{os}.{arch}.zip // Camoufox asset naming pattern: camoufox-{version}-beta.{number}-{os}.{arch}.zip
// Example: camoufox-135.0.1-beta.24-lin.x86_64.zip
let (os_name, arch_name) = match (os, arch) { let (os_name, arch_name) = match (os, arch) {
("windows", "x64") => ("win", "x86_64"), ("windows", "x64") => ("win", "x86_64"),
("windows", "arm64") => ("win", "arm64"), ("windows", "arm64") => ("win", "arm64"),
@@ -322,7 +323,8 @@ impl Downloader {
}; };
// Use ends_with for precise matching to avoid false positives // Use ends_with for precise matching to avoid false positives
let pattern = format!(".{os_name}.{arch_name}.zip"); // The separator before OS is a dash: -lin.x86_64.zip, -mac.arm64.zip, etc.
let pattern = format!("-{os_name}.{arch_name}.zip");
let asset = assets.iter().find(|asset| { let asset = assets.iter().find(|asset| {
let name = asset.name.to_lowercase(); let name = asset.name.to_lowercase();
name.starts_with("camoufox-") && name.ends_with(&pattern) name.starts_with("camoufox-") && name.ends_with(&pattern)
+124 -15
View File
@@ -142,6 +142,10 @@ export function CreateProfileDialog({
const [showProxyForm, setShowProxyForm] = useState(false); const [showProxyForm, setShowProxyForm] = useState(false);
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const [releaseTypes, setReleaseTypes] = useState<BrowserReleaseTypes>(); const [releaseTypes, setReleaseTypes] = useState<BrowserReleaseTypes>();
const [isLoadingReleaseTypes, setIsLoadingReleaseTypes] = useState(false);
const [releaseTypesError, setReleaseTypesError] = useState<string | null>(
null,
);
const loadingBrowserRef = useRef<string | null>(null); const loadingBrowserRef = useRef<string | null>(null);
// Use the browser download hook // Use the browser download hook
@@ -179,6 +183,8 @@ export function CreateProfileDialog({
async (browser: string) => { async (browser: string) => {
// Set loading state // Set loading state
loadingBrowserRef.current = browser; loadingBrowserRef.current = browser;
setIsLoadingReleaseTypes(true);
setReleaseTypesError(null);
try { try {
const rawReleaseTypes = await invoke<BrowserReleaseTypes>( const rawReleaseTypes = await invoke<BrowserReleaseTypes>(
@@ -207,6 +213,7 @@ export function CreateProfileDialog({
filtered.stable = rawReleaseTypes.stable; filtered.stable = rawReleaseTypes.stable;
setReleaseTypes(filtered); setReleaseTypes(filtered);
} }
setReleaseTypesError(null);
} }
} catch (error) { } catch (error) {
console.error(`Failed to load release types for ${browser}:`, error); console.error(`Failed to load release types for ${browser}:`, error);
@@ -223,17 +230,29 @@ export function CreateProfileDialog({
fallback.stable = latest; fallback.stable = latest;
} }
setReleaseTypes(fallback); setReleaseTypes(fallback);
setReleaseTypesError(null);
} else if (loadingBrowserRef.current === browser) {
// No downloaded versions and API failed - show error
setReleaseTypesError(
"Failed to fetch browser versions. Please check your internet connection and try again.",
);
} }
} catch (e) { } catch (e) {
console.error( console.error(
`Failed to load downloaded versions for ${browser}:`, `Failed to load downloaded versions for ${browser}:`,
e, e,
); );
if (loadingBrowserRef.current === browser) {
setReleaseTypesError(
"Failed to fetch browser versions. Please check your internet connection and try again.",
);
}
} }
} finally { } finally {
// Clear loading state only if we're still loading this browser // Clear loading state only if we're still loading this browser
if (loadingBrowserRef.current === browser) { if (loadingBrowserRef.current === browser) {
loadingBrowserRef.current = null; loadingBrowserRef.current = null;
setIsLoadingReleaseTypes(false);
} }
} }
}, },
@@ -377,6 +396,8 @@ export function CreateProfileDialog({
setSelectedBrowser(null); setSelectedBrowser(null);
setSelectedProxyId(undefined); setSelectedProxyId(undefined);
setReleaseTypes({}); setReleaseTypes({});
setIsLoadingReleaseTypes(false);
setReleaseTypesError(null);
setCamoufoxConfig({ setCamoufoxConfig({
geoip: true, // Reset to automatic geoip geoip: true, // Reset to automatic geoip
os: getCurrentOS(), // Reset to current OS os: getCurrentOS(), // Reset to current OS
@@ -553,7 +574,34 @@ export function CreateProfileDialog({
// Camoufox Configuration // Camoufox Configuration
<div className="space-y-6"> <div className="space-y-6">
{/* Camoufox Download Status */} {/* Camoufox Download Status */}
{!isBrowserCurrentlyDownloading("camoufox") && {isLoadingReleaseTypes && (
<div className="flex gap-3 items-center p-3 rounded-md border">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
Fetching available versions...
</p>
</div>
)}
{!isLoadingReleaseTypes && releaseTypesError && (
<div className="flex gap-3 items-center p-3 rounded-md border border-destructive/50 bg-destructive/10">
<p className="flex-1 text-sm text-destructive">
{releaseTypesError}
</p>
<RippleButton
onClick={() =>
selectedBrowser &&
loadReleaseTypes(selectedBrowser)
}
size="sm"
variant="outline"
>
Retry
</RippleButton>
</div>
)}
{!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading("camoufox") &&
!isBrowserVersionAvailable("camoufox") && !isBrowserVersionAvailable("camoufox") &&
getBestAvailableVersion("camoufox") && ( getBestAvailableVersion("camoufox") && (
<div className="flex gap-3 items-center p-3 rounded-md border"> <div className="flex gap-3 items-center p-3 rounded-md border">
@@ -580,7 +628,9 @@ export function CreateProfileDialog({
</LoadingButton> </LoadingButton>
</div> </div>
)} )}
{!isBrowserCurrentlyDownloading("camoufox") && {!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading("camoufox") &&
isBrowserVersionAvailable("camoufox") && ( isBrowserVersionAvailable("camoufox") && (
<div className="p-3 text-sm rounded-md border text-muted-foreground"> <div className="p-3 text-sm rounded-md border text-muted-foreground">
{(() => { {(() => {
@@ -607,13 +657,41 @@ export function CreateProfileDialog({
/> />
</div> </div>
) : ( ) : (
// Regular Browser Configuration // Regular Browser Configuration (should not happen in anti-detect tab)
<div className="space-y-4"> <div className="space-y-4">
{selectedBrowser && ( {selectedBrowser && (
<div className="space-y-3"> <div className="space-y-3">
{!isBrowserCurrentlyDownloading( {isLoadingReleaseTypes && (
selectedBrowser, <div className="flex gap-3 items-center">
) && <div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
Fetching available versions...
</p>
</div>
)}
{!isLoadingReleaseTypes &&
releaseTypesError && (
<div className="flex gap-3 items-center p-3 rounded-md border border-destructive/50 bg-destructive/10">
<p className="flex-1 text-sm text-destructive">
{releaseTypesError}
</p>
<RippleButton
onClick={() =>
selectedBrowser &&
loadReleaseTypes(selectedBrowser)
}
size="sm"
variant="outline"
>
Retry
</RippleButton>
</div>
)}
{!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading(
selectedBrowser,
) &&
!isBrowserVersionAvailable(selectedBrowser) && !isBrowserVersionAvailable(selectedBrowser) &&
getBestAvailableVersion(selectedBrowser) && ( getBestAvailableVersion(selectedBrowser) && (
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
@@ -643,9 +721,11 @@ export function CreateProfileDialog({
</LoadingButton> </LoadingButton>
</div> </div>
)} )}
{!isBrowserCurrentlyDownloading( {!isLoadingReleaseTypes &&
selectedBrowser, !releaseTypesError &&
) && !isBrowserCurrentlyDownloading(
selectedBrowser,
) &&
isBrowserVersionAvailable( isBrowserVersionAvailable(
selectedBrowser, selectedBrowser,
) && ( ) && (
@@ -739,9 +819,36 @@ export function CreateProfileDialog({
<div className="space-y-4"> <div className="space-y-4">
{selectedBrowser && ( {selectedBrowser && (
<div className="space-y-3"> <div className="space-y-3">
{!isBrowserCurrentlyDownloading( {isLoadingReleaseTypes && (
selectedBrowser, <div className="flex gap-3 items-center">
) && <div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
Fetching available versions...
</p>
</div>
)}
{!isLoadingReleaseTypes && releaseTypesError && (
<div className="flex gap-3 items-center p-3 rounded-md border border-destructive/50 bg-destructive/10">
<p className="flex-1 text-sm text-destructive">
{releaseTypesError}
</p>
<RippleButton
onClick={() =>
selectedBrowser &&
loadReleaseTypes(selectedBrowser)
}
size="sm"
variant="outline"
>
Retry
</RippleButton>
</div>
)}
{!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading(
selectedBrowser,
) &&
!isBrowserVersionAvailable(selectedBrowser) && !isBrowserVersionAvailable(selectedBrowser) &&
getBestAvailableVersion(selectedBrowser) && ( getBestAvailableVersion(selectedBrowser) && (
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
@@ -771,9 +878,11 @@ export function CreateProfileDialog({
</LoadingButton> </LoadingButton>
</div> </div>
)} )}
{!isBrowserCurrentlyDownloading( {!isLoadingReleaseTypes &&
selectedBrowser, !releaseTypesError &&
) && !isBrowserCurrentlyDownloading(
selectedBrowser,
) &&
isBrowserVersionAvailable(selectedBrowser) && ( isBrowserVersionAvailable(selectedBrowser) && (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{(() => { {(() => {