-
+
{title}
@@ -225,7 +258,13 @@ export function UnifiedToast(props: ToastProps) {
{/* Description */}
{description && (
-
+
{description}
)}
diff --git a/src/components/profile-data-table.tsx b/src/components/profile-data-table.tsx
index 5542ced..609d3a1 100644
--- a/src/components/profile-data-table.tsx
+++ b/src/components/profile-data-table.tsx
@@ -1,6 +1,5 @@
"use client";
-import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -275,70 +274,6 @@ export function ProfilesDataTable({
return browserA.localeCompare(browserB);
},
},
- {
- accessorKey: "version",
- header: "Version",
- },
- {
- id: "status",
- header: ({ column }) => {
- const isSorted = column.getIsSorted();
- return (
-
- );
- },
- cell: ({ row }) => {
- const profile = row.original;
- const isRunning = isClient && runningProfiles.has(profile.name);
- return (
-
-
- {isClient ? (isRunning ? "Running" : "Stopped") : "Loading..."}
-
- {isClient && isRunning && profile.process_id && (
-
- PID: {profile.process_id}
-
- )}
-
- );
- },
- enableSorting: true,
- sortingFn: (rowA, rowB) => {
- // If not on client, sort by name only to ensure consistency
- if (!isClient) {
- return rowA.original.name.localeCompare(rowB.original.name);
- }
-
- const isRunningA = runningProfiles.has(rowA.original.name);
- const isRunningB = runningProfiles.has(rowB.original.name);
-
- // Running profiles come first, then stopped ones
- // Secondary sort by profile name
- if (isRunningA === isRunningB) {
- return rowA.original.name.localeCompare(rowB.original.name);
- }
- return isRunningA ? -1 : 1;
- },
- },
{
id: "proxy",
header: "Proxy",
diff --git a/src/hooks/use-browser-download.ts b/src/hooks/use-browser-download.ts
index f0f70f8..c6986d9 100644
--- a/src/hooks/use-browser-download.ts
+++ b/src/hooks/use-browser-download.ts
@@ -3,9 +3,7 @@ import {
dismissToast,
showDownloadToast,
showErrorToast,
- showFetchingToast,
showSuccessToast,
- showUnifiedVersionUpdateToast,
} from "@/lib/toast-utils";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
@@ -45,15 +43,6 @@ interface BrowserVersionsResult {
total_versions_count: number;
}
-interface VersionUpdateProgress {
- current_browser: string;
- total_browsers: number;
- completed_browsers: number;
- new_versions_found: number;
- browser_new_versions: number;
- status: string;
-}
-
export function useBrowserDownload() {
const [availableVersions, setAvailableVersions] = useState
(
[],
@@ -62,7 +51,6 @@ export function useBrowserDownload() {
const [isDownloading, setIsDownloading] = useState(false);
const [downloadProgress, setDownloadProgress] =
useState(null);
- const [isUpdatingVersions, setIsUpdatingVersions] = useState(false);
// Listen for download progress events
useEffect(() => {
@@ -128,70 +116,6 @@ export function useBrowserDownload() {
};
}, []);
- // Listen for version update progress events
- useEffect(() => {
- const unlisten = listen(
- "version-update-progress",
- (event) => {
- const progress = event.payload;
-
- if (progress.status === "updating") {
- setIsUpdatingVersions(true);
-
- // Show unified progress toast
- const currentBrowserName = progress.current_browser
- ? getBrowserDisplayName(progress.current_browser)
- : undefined;
-
- showUnifiedVersionUpdateToast("Checking for browser updates...", {
- description: currentBrowserName
- ? `Fetching ${currentBrowserName} release information...`
- : "Initializing version check...",
- progress: {
- current: progress.completed_browsers,
- total: progress.total_browsers,
- found: progress.new_versions_found,
- current_browser: currentBrowserName,
- },
- });
- } else if (progress.status === "completed") {
- setIsUpdatingVersions(false);
- dismissToast("unified-version-update");
-
- if (progress.new_versions_found > 0) {
- showSuccessToast(
- `Found ${progress.new_versions_found} new browser versions!`,
- {
- duration: 4000,
- description:
- "Version information has been updated in the background",
- },
- );
- } else {
- showSuccessToast("No new browser versions found", {
- duration: 3000,
- description: "All browser versions are up to date",
- });
- }
- } else if (progress.status === "error") {
- setIsUpdatingVersions(false);
- dismissToast("unified-version-update");
-
- showErrorToast("Failed to check for new versions", {
- duration: 4000,
- description: "Check your internet connection and try again",
- });
- }
- },
- );
-
- return () => {
- void unlisten.then((fn) => {
- fn();
- });
- };
- }, []);
-
const formatTime = (seconds: number): string => {
if (seconds < 60) {
return `${Math.round(seconds)}s`;
@@ -217,10 +141,8 @@ export function useBrowserDownload() {
const loadVersions = useCallback(async (browserStr: string) => {
const browserName = getBrowserDisplayName(browserStr);
- // Show fetching toast
- const toastId = showFetchingToast(browserName, {
- id: `fetch-${browserStr}`,
- });
+ // Use a simple loading state instead of toast for version fetching
+ console.log(`Fetching ${browserName} versions...`);
try {
const versionInfos = await invoke(
@@ -239,11 +161,9 @@ export function useBrowserDownload() {
);
setAvailableVersions(githubReleases);
- dismissToast(toastId);
return githubReleases;
} catch (error) {
console.error("Failed to load versions:", error);
- dismissToast(toastId);
showErrorToast(`Failed to fetch ${browserName} versions`, {
description:
error instanceof Error ? error.message : "Unknown error occurred",
@@ -377,7 +297,6 @@ export function useBrowserDownload() {
downloadedVersions,
isDownloading,
downloadProgress,
- isUpdatingVersions,
loadVersions,
loadVersionsWithNewCount,
loadDownloadedVersions,
diff --git a/src/hooks/use-update-notifications.tsx b/src/hooks/use-update-notifications.tsx
index 35f7deb..76b3233 100644
--- a/src/hooks/use-update-notifications.tsx
+++ b/src/hooks/use-update-notifications.tsx
@@ -1,9 +1,7 @@
-import { UpdateNotificationComponent } from "@/components/update-notification";
import { getBrowserDisplayName } from "@/lib/browser-utils";
-import { showToast } from "@/lib/toast-utils";
+import { showAutoUpdateToast, showToast } from "@/lib/toast-utils";
import { invoke } from "@tauri-apps/api/core";
-import React, { useCallback, useEffect, useState } from "react";
-import { toast } from "sonner";
+import { useCallback, useEffect, useRef, useState } from "react";
interface UpdateNotification {
id: string;
@@ -23,73 +21,82 @@ export function useUpdateNotifications(
const [updatingBrowsers, setUpdatingBrowsers] = useState>(
new Set(),
);
- const [dismissedNotifications, setDismissedNotifications] = useState<
+ const [processedNotifications, setProcessedNotifications] = useState<
Set
>(new Set());
+ // Add refs to track ongoing operations to prevent duplicates
+ const isCheckingForUpdates = useRef(false);
+ const activeDownloads = useRef>(new Set()); // Track "browser-version" keys
+
const checkForUpdates = useCallback(async () => {
+ // Prevent multiple simultaneous calls
+ if (isCheckingForUpdates.current) {
+ console.log("Already checking for updates, skipping duplicate call");
+ return;
+ }
+
+ isCheckingForUpdates.current = true;
+
try {
const updates = await invoke(
"check_for_browser_updates",
);
- // Filter out dismissed notifications unless they're for a newer version
- const filteredUpdates = updates.filter((notification) => {
- // Check if this exact notification was dismissed
- if (dismissedNotifications.has(notification.id)) {
- return false;
- }
-
- // Check if we dismissed an older version for this browser
- const dismissedForBrowser = Array.from(dismissedNotifications).find(
- (dismissedId) => {
- const parts = dismissedId.split("_");
- if (parts.length >= 2) {
- const browser = parts[0];
- return browser === notification.browser;
- }
- return false;
- },
- );
-
- if (dismissedForBrowser) {
- // Extract the dismissed version to compare
- const dismissedParts = dismissedForBrowser.split("_to_");
- if (dismissedParts.length === 2) {
- const dismissedToVersion = dismissedParts[1];
- // Only show if this is a newer version than what was dismissed
- return notification.new_version !== dismissedToVersion;
- }
- }
-
- return true;
+ // Filter out already processed notifications
+ const newUpdates = updates.filter((notification) => {
+ return !processedNotifications.has(notification.id);
});
- setNotifications(filteredUpdates);
+ setNotifications(newUpdates);
- // Show toasts for new notifications - we'll define handleUpdate and handleDismiss separately
- // to avoid circular dependencies
+ // Automatically start downloads for new update notifications
+ for (const notification of newUpdates) {
+ if (!processedNotifications.has(notification.id)) {
+ setProcessedNotifications((prev) =>
+ new Set(prev).add(notification.id),
+ );
+ // Start automatic update without user interaction
+ void handleAutoUpdate(
+ notification.browser,
+ notification.new_version,
+ notification.id,
+ );
+ }
+ }
} catch (error) {
console.error("Failed to check for updates:", error);
+ } finally {
+ isCheckingForUpdates.current = false;
}
- }, [dismissedNotifications]);
+ }, [processedNotifications]);
+
+ const handleAutoUpdate = useCallback(
+ async (browser: string, newVersion: string, notificationId: string) => {
+ const downloadKey = `${browser}-${newVersion}`;
+
+ // Check if this download is already in progress
+ if (activeDownloads.current.has(downloadKey)) {
+ console.log(
+ `Download already in progress for ${downloadKey}, skipping duplicate`,
+ );
+ return;
+ }
+
+ // Mark download as active
+ activeDownloads.current.add(downloadKey);
- const handleUpdate = useCallback(
- async (browser: string, newVersion: string) => {
try {
setUpdatingBrowsers((prev) => new Set(prev).add(browser));
const browserDisplayName = getBrowserDisplayName(browser);
- // Dismiss all notifications for this browser first
- const browserNotifications = notifications.filter(
- (n) => n.browser === browser,
- );
- for (const notification of browserNotifications) {
- toast.dismiss(notification.id);
- await invoke("dismiss_update_notification", {
- notificationId: notification.id,
- });
- }
+ // Dismiss the notification in the backend
+ await invoke("dismiss_update_notification", {
+ notificationId,
+ });
+
+ // Show auto-update started toast
+ showAutoUpdateToast(browserDisplayName, newVersion);
try {
// Check if browser already exists before downloading
@@ -134,17 +141,19 @@ export function useUpdateNotifications(
: `${updatedProfiles.length} profiles have been updated`;
showToast({
+ id: `auto-update-success-${browser}-${newVersion}`,
type: "success",
title: `${browserDisplayName} update completed`,
- description: `${profileText} to version ${newVersion}. Running profiles were not updated and can be updated manually.`,
+ description: `${profileText} to version ${newVersion}. To update running profiles, restart them.`,
duration: 5000,
});
} else {
showToast({
+ id: `auto-update-success-${browser}-${newVersion}`,
type: "success",
title: `${browserDisplayName} update ready`,
description:
- "All affected profiles are currently running. Stop them and manually update their versions to use the new version.",
+ "All affected profiles are currently running. To update them, restart them.",
duration: 5000,
});
}
@@ -167,6 +176,7 @@ export function useUpdateNotifications(
}
showToast({
+ id: `auto-update-error-${browser}-${newVersion}`,
type: "error",
title: `Failed to download ${browserDisplayName} ${newVersion}`,
description: String(downloadError),
@@ -175,18 +185,21 @@ export function useUpdateNotifications(
throw downloadError;
}
- // Refresh notifications to clear any remaining ones
- await checkForUpdates();
+ // Don't call checkForUpdates() again here as it can cause recursion and duplicates
+ // The periodic checks will handle finding any remaining updates
} catch (error) {
- console.error("Failed to start update:", error);
+ console.error("Failed to start auto-update:", error);
const browserDisplayName = getBrowserDisplayName(browser);
showToast({
+ id: `auto-update-error-${browser}-${newVersion}`,
type: "error",
title: `Failed to update ${browserDisplayName}`,
description: String(error),
duration: 6000,
});
} finally {
+ // Remove from active downloads and updating browsers
+ activeDownloads.current.delete(downloadKey);
setUpdatingBrowsers((prev) => {
const next = new Set(prev);
next.delete(browser);
@@ -194,52 +207,18 @@ export function useUpdateNotifications(
});
}
},
- [notifications, checkForUpdates, onProfilesUpdated],
+ [onProfilesUpdated],
);
- const handleDismiss = useCallback(
- async (notificationId: string) => {
- try {
- toast.dismiss(notificationId);
- await invoke("dismiss_update_notification", { notificationId });
-
- // Track this notification as dismissed to prevent showing it again
- setDismissedNotifications((prev) => new Set(prev).add(notificationId));
-
- await checkForUpdates();
- } catch (error) {
- console.error("Failed to dismiss notification:", error);
- }
- },
- [checkForUpdates],
- );
-
- // Separate effect to show toasts when notifications change
+ // Clean up notifications when they're no longer needed
useEffect(() => {
- for (const notification of notifications) {
- const isUpdating = updatingBrowsers.has(notification.browser);
-
- toast.custom(
- () => (
-
- ),
- {
- id: notification.id,
- duration: Number.POSITIVE_INFINITY, // Persistent until user action
- position: "top-right",
- style: {
- zIndex: 99999, // Ensure notifications appear above dialogs
- pointerEvents: "auto", // Ensure notifications remain interactive
- },
- },
- );
- }
- }, [notifications, updatingBrowsers, handleUpdate, handleDismiss]);
+ // Remove notifications that have been processed
+ setNotifications((prev) =>
+ prev.filter(
+ (notification) => !processedNotifications.has(notification.id),
+ ),
+ );
+ }, [processedNotifications]);
return {
notifications,
diff --git a/src/hooks/use-version-updater.ts b/src/hooks/use-version-updater.ts
index 6c50810..fd2f81e 100644
--- a/src/hooks/use-version-updater.ts
+++ b/src/hooks/use-version-updater.ts
@@ -68,14 +68,11 @@ export function useVersionUpdater() {
dismissToast("unified-version-update");
if (progress.new_versions_found > 0) {
- toast.success(
- `Found ${progress.new_versions_found} new browser versions!`,
- {
- duration: 4000,
- description:
- "Version information has been updated in the background",
- },
- );
+ toast.success("Browser versions updated successfully", {
+ duration: 4000,
+ description:
+ "Version information has been updated in the background",
+ });
} else {
toast.success("No new browser versions found", {
duration: 3000,
diff --git a/src/lib/toast-utils.ts b/src/lib/toast-utils.ts
index 23a058d..778a93e 100644
--- a/src/lib/toast-utils.ts
+++ b/src/lib/toast-utils.ts
@@ -294,6 +294,26 @@ export function showTwilightUpdateToast(
});
}
+export function showAutoUpdateToast(
+ browserName: string,
+ version: string,
+ options?: {
+ id?: string;
+ description?: string;
+ duration?: number;
+ },
+) {
+ return showToast({
+ type: "loading",
+ title: `${browserName} update started`,
+ description:
+ options?.description ??
+ `Automatically downloading ${browserName} ${version}. Progress will be shown in download notifications.`,
+ id: options?.id ?? `auto-update-${browserName.toLowerCase()}-${version}`,
+ duration: options?.duration ?? 4000,
+ });
+}
+
// Generic helper for dismissing toasts
export function dismissToast(id: string) {
sonnerToast.dismiss(id);