refactor: better camoufox instance tracking

This commit is contained in:
zhom
2025-07-31 03:56:41 +04:00
parent 2fd344b9bb
commit 63000c72bd
20 changed files with 1623 additions and 457 deletions
+9 -1
View File
@@ -229,17 +229,22 @@ export default function Home() {
useAppUpdateNotifications();
// Check for startup URLs but only process them once
const [hasCheckedStartupUrl, setHasCheckedStartupUrl] = useState(false);
const checkCurrentUrl = useCallback(async () => {
if (hasCheckedStartupUrl) return;
try {
const currentUrl = await getCurrent();
if (currentUrl && currentUrl.length > 0) {
console.log("Startup URL detected:", currentUrl[0]);
void handleUrlOpen(currentUrl[0]);
}
setHasCheckedStartupUrl(true);
} catch (error) {
console.error("Failed to check current URL:", error);
setHasCheckedStartupUrl(true);
}
}, [handleUrlOpen]);
}, [handleUrlOpen, hasCheckedStartupUrl]);
const checkStartupPrompt = useCallback(async () => {
// Only check once during app startup to prevent reopening after dismissing notifications
@@ -453,6 +458,9 @@ export default function Home() {
const currentRunning = runningProfilesRef.current.has(profile.name);
if (isRunning !== currentRunning) {
console.log(
`Profile ${profile.name} (${profile.browser}) status changed: ${currentRunning} -> ${isRunning}`,
);
setRunningProfiles((prev) => {
const next = new Set(prev);
if (isRunning) {
+3 -1
View File
@@ -20,7 +20,9 @@ export function GroupBadges({
if (isLoading) {
return (
<div className="flex flex-wrap gap-2 mb-4">
<div className="text-sm text-muted-foreground">Loading groups...</div>
<div className="flex items-center gap-2 px-4.5 py-1.5 text-xs">
Loading groups...
</div>
</div>
);
}
+50 -10
View File
@@ -103,6 +103,9 @@ export function ProfilesDataTable({
const [profileToDelete, setProfileToDelete] =
React.useState<BrowserProfile | null>(null);
const [isDeleting, setIsDeleting] = React.useState(false);
const [launchingProfiles, setLaunchingProfiles] = React.useState<Set<string>>(
new Set(),
);
const [storedProxies, setStoredProxies] = React.useState<StoredProxy[]>([]);
const [selectedProfiles, setSelectedProfiles] = React.useState<Set<string>>(
@@ -365,9 +368,41 @@ export function ProfilesDataTable({
const profile = row.original;
const isRunning =
browserState.isClient && runningProfiles.has(profile.name);
const isLaunching = launchingProfiles.has(profile.name);
const canLaunch = browserState.canLaunchProfile(profile);
const tooltipContent = browserState.getLaunchTooltipContent(profile);
const handleLaunchClick = async () => {
if (isRunning) {
console.log(
`Stopping ${profile.browser} profile: ${profile.name}`,
);
await onKillProfile(profile);
} else {
console.log(
`Launching ${profile.browser} profile: ${profile.name}`,
);
setLaunchingProfiles((prev) => new Set(prev).add(profile.name));
try {
await onLaunchProfile(profile);
console.log(
`Successfully launched ${profile.browser} profile: ${profile.name}`,
);
} catch (error) {
console.error(
`Failed to launch ${profile.browser} profile: ${profile.name}`,
error,
);
} finally {
setLaunchingProfiles((prev) => {
const next = new Set(prev);
next.delete(profile.name);
return next;
});
}
}
};
return (
<div className="flex gap-2 items-center">
<Tooltip>
@@ -376,18 +411,22 @@ export function ProfilesDataTable({
<Button
variant={isRunning ? "destructive" : "default"}
size="sm"
disabled={!canLaunch}
disabled={!canLaunch || isLaunching}
className={cn(
"cursor-pointer",
"cursor-pointer min-w-[70px]",
!canLaunch && "opacity-50",
)}
onClick={() =>
void (isRunning
? onKillProfile(profile)
: onLaunchProfile(profile))
}
onClick={() => void handleLaunchClick()}
>
{isRunning ? "Stop" : "Launch"}
{isLaunching ? (
<div className="flex items-center gap-1">
<div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
</div>
) : isRunning ? (
"Stop"
) : (
"Launch"
)}
</Button>
</span>
</TooltipTrigger>
@@ -408,7 +447,7 @@ export function ProfilesDataTable({
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
className="h-auto p-0 font-semibold text-left justify-start"
className="h-auto p-0 font-semibold text-left justify-start cursor-pointer"
>
Name
{column.getIsSorted() === "asc" ? (
@@ -448,7 +487,7 @@ export function ProfilesDataTable({
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
className="h-auto p-0 font-semibold text-left justify-start"
className="h-auto p-0 font-semibold text-left justify-start cursor-pointer"
>
Browser
{column.getIsSorted() === "asc" ? (
@@ -677,6 +716,7 @@ export function ProfilesDataTable({
onAssignProfilesToGroup,
isUpdating,
filteredData.length,
launchingProfiles.has,
],
);
+19 -11
View File
@@ -84,15 +84,22 @@ export function ProfileSelectorDialog({
// First, try to find a running profile that can be used for opening links
const runningAvailableProfile = profileList.find((profile) => {
const isRunning = runningProfiles.has(profile.name);
return isRunning && browserState.canUseProfileForLinks(profile);
// Simple check without browserState dependency
return (
isRunning &&
profile.browser !== "tor-browser" &&
profile.browser !== "mullvad-browser"
);
});
if (runningAvailableProfile) {
setSelectedProfile(runningAvailableProfile.name);
} else {
// If no running profile is available, find the first available profile
const availableProfile = profileList.find((profile) =>
browserState.canUseProfileForLinks(profile),
const availableProfile = profileList.find(
(profile) =>
profile.browser !== "tor-browser" &&
profile.browser !== "mullvad-browser",
);
if (availableProfile) {
setSelectedProfile(availableProfile.name);
@@ -104,7 +111,7 @@ export function ProfileSelectorDialog({
} finally {
setIsLoading(false);
}
}, [runningProfiles, browserState]);
}, [runningProfiles]);
// Helper function to get tooltip content for profiles - now uses shared hook
const getProfileTooltipContent = (profile: BrowserProfile): string | null => {
@@ -227,11 +234,12 @@ export function ProfileSelectorDialog({
return (
<Tooltip key={profile.name}>
<TooltipTrigger asChild>
<span className="inline-flex">
<SelectItem
value={profile.name}
disabled={!canUseForLinks}
>
<SelectItem
value={profile.name}
disabled={!canUseForLinks}
asChild
>
<span>
<div
className={`flex items-center gap-2 ${
!canUseForLinks ? "opacity-50" : ""
@@ -276,8 +284,8 @@ export function ProfileSelectorDialog({
</Badge>
)}
</div>
</SelectItem>
</span>
</span>
</SelectItem>
</TooltipTrigger>
{tooltipContent && (
<TooltipContent>{tooltipContent}</TooltipContent>
+19 -1
View File
@@ -1,6 +1,6 @@
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { getBrowserDisplayName } from "@/lib/browser-utils";
import {
dismissToast,
@@ -47,6 +47,9 @@ export function useVersionUpdater() {
const [updateProgress, setUpdateProgress] =
useState<VersionUpdateProgress | null>(null);
// Track active downloads to prevent duplicates
const activeDownloads = useRef(new Set<string>());
const loadUpdateStatus = useCallback(async () => {
try {
const [lastUpdate, timeUntilNext] = await invoke<[number | null, number]>(
@@ -160,6 +163,18 @@ export function useVersionUpdater() {
console.log("Browser auto-update event received:", event.payload);
const browserDisplayName = getBrowserDisplayName(browser);
const downloadKey = `${browser}-${new_version}`;
// Check if this download is already in progress
if (activeDownloads.current.has(downloadKey)) {
console.log(
`Download already in progress for ${browserDisplayName} ${new_version}, skipping`,
);
return;
}
// Mark download as active
activeDownloads.current.add(downloadKey);
try {
// Show auto-update start notification
@@ -237,6 +252,9 @@ export function useVersionUpdater() {
: "Unknown error occurred",
duration: 8000,
});
} finally {
// Remove from active downloads
activeDownloads.current.delete(downloadKey);
}
};
-3
View File
@@ -106,9 +106,6 @@ export interface CamoufoxConfig {
additional_args?: string[];
env_vars?: Record<string, string>;
firefox_prefs?: Record<string, unknown>;
// Required options for anti-detect features
disableTheming?: boolean;
showcursor?: boolean;
}
export interface CamoufoxLaunchResult {