refactor: sync

This commit is contained in:
zhom
2026-02-20 07:22:42 +04:00
parent 8bc1ea500b
commit 4872dcc8ad
28 changed files with 678 additions and 72 deletions
+13 -1
View File
@@ -23,6 +23,7 @@ import { ProfileSyncDialog } from "@/components/profile-sync-dialog";
import { ProxyAssignmentDialog } from "@/components/proxy-assignment-dialog";
import { ProxyManagementDialog } from "@/components/proxy-management-dialog";
import { SettingsDialog } from "@/components/settings-dialog";
import { SyncAllDialog } from "@/components/sync-all-dialog";
import { SyncConfigDialog } from "@/components/sync-config-dialog";
import { WayfernTermsDialog } from "@/components/wayfern-terms-dialog";
import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications";
@@ -143,6 +144,7 @@ export default function Home() {
useState(false);
const [isBulkDeleting, setIsBulkDeleting] = useState(false);
const [syncConfigDialogOpen, setSyncConfigDialogOpen] = useState(false);
const [syncAllDialogOpen, setSyncAllDialogOpen] = useState(false);
const [profileSyncDialogOpen, setProfileSyncDialogOpen] = useState(false);
const [currentProfileForSync, setCurrentProfileForSync] =
useState<BrowserProfile | null>(null);
@@ -1118,7 +1120,17 @@ export default function Home() {
<SyncConfigDialog
isOpen={syncConfigDialogOpen}
onClose={() => setSyncConfigDialogOpen(false)}
onClose={(loginOccurred) => {
setSyncConfigDialogOpen(false);
if (loginOccurred) {
setSyncAllDialogOpen(true);
}
}}
/>
<SyncAllDialog
isOpen={syncAllDialogOpen}
onClose={() => setSyncAllDialogOpen(false)}
/>
<ProfileSyncDialog
+2 -6
View File
@@ -555,9 +555,7 @@ export function CreateProfileDialog({
})()}
</div>
<div className="text-left">
<div className="font-medium">
Chromium (Wayfern)
</div>
<div className="font-medium">Wayfern</div>
<div className="text-sm text-muted-foreground">
Anti-Detect Browser
</div>
@@ -580,9 +578,7 @@ export function CreateProfileDialog({
})()}
</div>
<div className="text-left">
<div className="font-medium">
Firefox (Camoufox)
</div>
<div className="font-medium">Camoufox</div>
<div className="text-sm text-muted-foreground">
Anti-Detect Browser
</div>
+10 -2
View File
@@ -1595,7 +1595,10 @@ export function ProfilesDataTable({
</span>
</TooltipTrigger>
<TooltipContent>
<p>Created on {osName} - view only</p>
<p>
This profile was created on {osName} and is not supported on
this system
</p>
</TooltipContent>
</Tooltip>
);
@@ -1608,7 +1611,12 @@ export function ProfilesDataTable({
: "another OS";
return (
<NonHoverableTooltip
content={<p>Created on {osName} - view only</p>}
content={
<p>
This profile was created on {osName} and is not supported on
this system
</p>
}
sideOffset={4}
horizontalOffset={8}
>
+124
View File
@@ -0,0 +1,124 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { LoadingButton } from "@/components/loading-button";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
interface UnsyncedEntityCounts {
proxies: number;
groups: number;
vpns: number;
}
interface SyncAllDialogProps {
isOpen: boolean;
onClose: () => void;
}
export function SyncAllDialog({ isOpen, onClose }: SyncAllDialogProps) {
const { t } = useTranslation();
const [counts, setCounts] = useState<UnsyncedEntityCounts | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isEnabling, setIsEnabling] = useState(false);
const loadCounts = useCallback(async () => {
setIsLoading(true);
try {
const result = await invoke<UnsyncedEntityCounts>(
"get_unsynced_entity_counts",
);
setCounts(result);
} catch (error) {
console.error("Failed to get unsynced entity counts:", error);
setCounts(null);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
if (isOpen) {
void loadCounts();
}
}, [isOpen, loadCounts]);
const handleEnableAll = useCallback(async () => {
setIsEnabling(true);
try {
await invoke("enable_sync_for_all_entities");
showSuccessToast(t("syncAll.success"));
onClose();
} catch (error) {
console.error("Failed to enable sync for all entities:", error);
showErrorToast(String(error));
} finally {
setIsEnabling(false);
}
}, [onClose, t]);
const totalCount =
(counts?.proxies ?? 0) + (counts?.groups ?? 0) + (counts?.vpns ?? 0);
// Don't show if there's nothing to sync
if (!isLoading && totalCount === 0) {
return null;
}
const parts: string[] = [];
if (counts?.proxies && counts.proxies > 0) {
parts.push(t("syncAll.proxies", { count: counts.proxies }));
}
if (counts?.groups && counts.groups > 0) {
parts.push(t("syncAll.groups", { count: counts.groups }));
}
if (counts?.vpns && counts.vpns > 0) {
parts.push(t("syncAll.vpns", { count: counts.vpns }));
}
return (
<Dialog open={isOpen && totalCount > 0} onOpenChange={onClose}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{t("syncAll.title")}</DialogTitle>
<DialogDescription>{t("syncAll.description")}</DialogDescription>
</DialogHeader>
{isLoading ? (
<div className="flex justify-center py-8">
<div className="w-6 h-6 rounded-full border-2 border-current animate-spin border-t-transparent" />
</div>
) : (
<div className="py-4">
<p className="text-sm text-muted-foreground">
{t("syncAll.itemsList", { items: parts.join(", ") })}
</p>
</div>
)}
<DialogFooter className="flex gap-2">
<Button variant="outline" onClick={onClose} disabled={isEnabling}>
{t("syncAll.skip")}
</Button>
<LoadingButton
onClick={handleEnableAll}
isLoading={isEnabling}
disabled={isLoading}
>
{t("syncAll.enableAll")}
</LoadingButton>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+3 -3
View File
@@ -28,7 +28,7 @@ import type { SyncSettings } from "@/types";
interface SyncConfigDialogProps {
isOpen: boolean;
onClose: () => void;
onClose: (loginOccurred?: boolean) => void;
}
export function SyncConfigDialog({ isOpen, onClose }: SyncConfigDialogProps) {
@@ -179,8 +179,8 @@ export function SyncConfigDialog({ isOpen, onClose }: SyncConfigDialogProps) {
} catch (e) {
console.error("Failed to restart sync service:", e);
}
// Auto-close dialog after successful login
onClose();
// Auto-close dialog after successful login, signal that login occurred
onClose(true);
} catch (error) {
console.error("OTP verification failed:", error);
showErrorToast(String(error));
+21
View File
@@ -273,6 +273,17 @@ export function useBrowserDownload() {
const progress = event.payload;
setDownloadProgress(progress);
if (
progress.stage === "downloading" ||
progress.stage === "extracting" ||
progress.stage === "verifying"
) {
setDownloadingBrowsers((prev) => {
if (prev.has(progress.browser)) return prev;
return new Set(prev).add(progress.browser);
});
}
const browserName = getBrowserDisplayName(progress.browser);
if (progress.stage === "downloading") {
@@ -311,11 +322,21 @@ export function useBrowserDownload() {
} else if (progress.stage === "verifying") {
showDownloadToast(browserName, progress.version, "verifying");
} else if (progress.stage === "cancelled") {
setDownloadingBrowsers((prev) => {
const next = new Set(prev);
next.delete(progress.browser);
return next;
});
dismissToast(
`download-${browserName.toLowerCase()}-${progress.version}`,
);
setDownloadProgress(null);
} else if (progress.stage === "completed") {
setDownloadingBrowsers((prev) => {
const next = new Set(prev);
next.delete(progress.browser);
return next;
});
// 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 {
+1 -1
View File
@@ -174,7 +174,7 @@ export function useBrowserState(
if (isCrossOsProfile(profile) && profile.host_os) {
const osName = getOSDisplayName(profile.host_os);
return `Created on ${osName}. Can only be launched on ${osName}.`;
return `This profile was created on ${osName} and is not supported on this system`;
}
const isRunning = runningProfiles.has(profile.id);
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "Anti-Detect Browser",
"description": "Choose a browser with anti-detection capabilities",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "Anti-Detect Browser"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "Spoofing a different operating system is harder — system-level APIs are more difficult to mask, making it easier for websites to detect inconsistencies. No anti-detect browser can perfectly spoof every detail across operating systems."
"crossOsWarning": "Spoofing fingerprint for a different operating system is less reliable because it is impossible to perfectly mimic all underlying components. Use with caution."
},
"syncAll": {
"title": "Enable Sync for Existing Items",
"description": "You have items that are not being synced. Would you like to enable sync for all of them?",
"itemsList": "Items not synced: {{items}}",
"proxies": "{{count}} proxy",
"proxies_plural": "{{count}} proxies",
"groups": "{{count}} group",
"groups_plural": "{{count}} groups",
"vpns": "{{count}} VPN",
"vpns_plural": "{{count}} VPNs",
"enableAll": "Enable All",
"skip": "Skip",
"success": "Sync enabled for all items"
},
"crossOs": {
"viewOnly": "Created on {{os}} - view only",
"cannotLaunch": "Created on {{os}}. Can only be launched on {{os}}.",
"viewOnly": "This profile was created on {{os}} and is not supported on this system",
"cannotLaunch": "This profile was created on {{os}} and is not supported on this system",
"cannotModify": "Cannot modify sync settings for a cross-OS profile"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "Navegador Anti-Detección",
"description": "Elige un navegador con capacidades anti-detección",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "Navegador Anti-Detección"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "Suplantar un sistema operativo diferente es más difícil: las API a nivel de sistema son más difíciles de enmascarar, lo que facilita que los sitios web detecten inconsistencias. Ningún navegador antidetección puede suplantar perfectamente cada detalle entre sistemas operativos."
"crossOsWarning": "La suplantación de huella digital para un sistema operativo diferente es menos fiable porque es imposible imitar perfectamente todos los componentes subyacentes. Usar con precaución."
},
"syncAll": {
"title": "Activar sincronización para elementos existentes",
"description": "Tienes elementos que no se están sincronizando. ¿Te gustaría activar la sincronización para todos?",
"itemsList": "Elementos no sincronizados: {{items}}",
"proxies": "{{count}} proxy",
"proxies_plural": "{{count}} proxies",
"groups": "{{count}} grupo",
"groups_plural": "{{count}} grupos",
"vpns": "{{count}} VPN",
"vpns_plural": "{{count}} VPNs",
"enableAll": "Activar todos",
"skip": "Omitir",
"success": "Sincronización activada para todos los elementos"
},
"crossOs": {
"viewOnly": "Creado en {{os}} - solo lectura",
"cannotLaunch": "Creado en {{os}}. Solo se puede iniciar en {{os}}.",
"viewOnly": "Este perfil fue creado en {{os}} y no es compatible con este sistema",
"cannotLaunch": "Este perfil fue creado en {{os}} y no es compatible con este sistema",
"cannotModify": "No se pueden modificar los ajustes de sincronización de un perfil de otro sistema operativo"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "Navigateur anti-détection",
"description": "Choisissez un navigateur avec des capacités anti-détection",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "Navigateur anti-détection"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "Usurper un système d'exploitation différent est plus difficile : les API au niveau du système sont plus difficiles à masquer, ce qui permet aux sites web de détecter plus facilement les incohérences. Aucun navigateur anti-détection ne peut parfaitement usurper chaque détail d'un système d'exploitation à l'autre."
"crossOsWarning": "L'usurpation d'empreinte pour un système d'exploitation différent est moins fiable car il est impossible d'imiter parfaitement tous les composants sous-jacents. À utiliser avec précaution."
},
"syncAll": {
"title": "Activer la synchronisation pour les éléments existants",
"description": "Vous avez des éléments qui ne sont pas synchronisés. Voulez-vous activer la synchronisation pour tous ?",
"itemsList": "Éléments non synchronisés : {{items}}",
"proxies": "{{count}} proxy",
"proxies_plural": "{{count}} proxies",
"groups": "{{count}} groupe",
"groups_plural": "{{count}} groupes",
"vpns": "{{count}} VPN",
"vpns_plural": "{{count}} VPNs",
"enableAll": "Tout activer",
"skip": "Ignorer",
"success": "Synchronisation activée pour tous les éléments"
},
"crossOs": {
"viewOnly": "Créé sur {{os}} - lecture seule",
"cannotLaunch": "Créé sur {{os}}. Ne peut être lancé que sur {{os}}.",
"viewOnly": "Ce profil a été créé sur {{os}} et n'est pas pris en charge sur ce système",
"cannotLaunch": "Ce profil a été créé sur {{os}} et n'est pas pris en charge sur ce système",
"cannotModify": "Impossible de modifier les paramètres de synchronisation d'un profil d'un autre système d'exploitation"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "アンチ検出ブラウザ",
"description": "アンチ検出機能を持つブラウザを選択",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "アンチ検出ブラウザ"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "異なるオペレーティングシステムの偽装はより困難です。システムレベルのAPIはマスクしにくく、ウェブサイトが矛盾を検出しやすくなります。どのアンチディテクトブラウザも、異なるOS間のすべての詳細を完璧に偽装することはできません。"
"crossOsWarning": "異なるオペレーティングシステムのフィンガープリント偽装は、すべての基盤コンポーネントを完璧に模倣することが不可能なため、信頼性が低くなります。注意してご使用ください。"
},
"syncAll": {
"title": "既存アイテムの同期を有効にする",
"description": "同期されていないアイテムがあります。すべての同期を有効にしますか?",
"itemsList": "未同期アイテム: {{items}}",
"proxies": "{{count}}個のプロキシ",
"proxies_plural": "{{count}}個のプロキシ",
"groups": "{{count}}個のグループ",
"groups_plural": "{{count}}個のグループ",
"vpns": "{{count}}個のVPN",
"vpns_plural": "{{count}}個のVPN",
"enableAll": "すべて有効にする",
"skip": "スキップ",
"success": "すべてのアイテムの同期が有効になりました"
},
"crossOs": {
"viewOnly": "{{os}}で作成 - 閲覧のみ",
"cannotLaunch": "{{os}}で作成されました。{{os}}でのみ起動できます。",
"viewOnly": "このプロファイルは{{os}}で作成されたもので、このシステムではサポートされていません",
"cannotLaunch": "このプロファイルは{{os}}で作成されたもので、このシステムではサポートされていません",
"cannotModify": "他のOSのプロファイルの同期設定は変更できません"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "Navegador Anti-Detecção",
"description": "Escolha um navegador com capacidades anti-detecção",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "Navegador Anti-Detecção"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "Falsificar um sistema operacional diferente é mais difícil: as APIs de nível de sistema são mais difíceis de mascarar, facilitando a detecção de inconsistências pelos sites. Nenhum navegador antidetecção consegue falsificar perfeitamente todos os detalhes entre sistemas operacionais."
"crossOsWarning": "A falsificação de impressão digital para um sistema operacional diferente é menos confiável porque é impossível imitar perfeitamente todos os componentes subjacentes. Use com cautela."
},
"syncAll": {
"title": "Ativar sincronização para itens existentes",
"description": "Você tem itens que não estão sendo sincronizados. Gostaria de ativar a sincronização para todos?",
"itemsList": "Itens não sincronizados: {{items}}",
"proxies": "{{count}} proxy",
"proxies_plural": "{{count}} proxies",
"groups": "{{count}} grupo",
"groups_plural": "{{count}} grupos",
"vpns": "{{count}} VPN",
"vpns_plural": "{{count}} VPNs",
"enableAll": "Ativar todos",
"skip": "Pular",
"success": "Sincronização ativada para todos os itens"
},
"crossOs": {
"viewOnly": "Criado em {{os}} - somente leitura",
"cannotLaunch": "Criado em {{os}}. Só pode ser iniciado em {{os}}.",
"viewOnly": "Este perfil foi criado em {{os}} e não é compatível com este sistema",
"cannotLaunch": "Este perfil foi criado em {{os}} e não é compatível com este sistema",
"cannotModify": "Não é possível modificar as configurações de sincronização de um perfil de outro sistema operacional"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "Антидетект браузер",
"description": "Выберите браузер с возможностями защиты от обнаружения",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "Антидетект браузер"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "Подмена другой операционной системы сложнее — системные API труднее замаскировать, что упрощает обнаружение несоответствий веб-сайтами. Ни один антидетект-браузер не может идеально подменить все детали при смене операционной системы."
"crossOsWarning": "Подмена отпечатка для другой операционной системы менее надёжна, так как невозможно идеально имитировать все базовые компоненты. Используйте с осторожностью."
},
"syncAll": {
"title": "Включить синхронизацию для существующих элементов",
"description": "У вас есть элементы, которые не синхронизируются. Хотите включить синхронизацию для всех?",
"itemsList": "Несинхронизированные элементы: {{items}}",
"proxies": "{{count}} прокси",
"proxies_plural": "{{count}} прокси",
"groups": "{{count}} группа",
"groups_plural": "{{count}} групп",
"vpns": "{{count}} VPN",
"vpns_plural": "{{count}} VPN",
"enableAll": "Включить все",
"skip": "Пропустить",
"success": "Синхронизация включена для всех элементов"
},
"crossOs": {
"viewOnly": "Создан на {{os}} - только просмотр",
"cannotLaunch": "Создан на {{os}}. Может быть запущен только на {{os}}.",
"viewOnly": "Этот профиль был создан на {{os}} и не поддерживается в этой системе",
"cannotLaunch": "Этот профиль был создан на {{os}} и не поддерживается в этой системе",
"cannotModify": "Невозможно изменить настройки синхронизации профиля другой ОС"
}
}
+19 -5
View File
@@ -166,8 +166,8 @@
"antiDetect": {
"title": "防检测浏览器",
"description": "选择具有防检测功能的浏览器",
"chromium": "Chromium (Wayfern)",
"firefox": "Firefox (Camoufox)",
"chromium": "Wayfern",
"firefox": "Camoufox",
"badge": "防检测浏览器"
},
"regular": {
@@ -483,11 +483,25 @@
"wayfern": "Wayfern"
},
"fingerprint": {
"crossOsWarning": "伪装不同操作系统更加困难——系统级API更难以掩盖,使网站更容易检测到不一致之处。没有任何反检测浏览器能够完美伪装跨操作系统的所有细节。"
"crossOsWarning": "伪装不同操作系统的指纹不太可靠,因为不可能完美模拟所有底层组件。请谨慎使用。"
},
"syncAll": {
"title": "为现有项目启用同步",
"description": "您有未同步的项目。是否要为所有项目启用同步?",
"itemsList": "未同步项目: {{items}}",
"proxies": "{{count}} 个代理",
"proxies_plural": "{{count}} 个代理",
"groups": "{{count}} 个分组",
"groups_plural": "{{count}} 个分组",
"vpns": "{{count}} 个 VPN",
"vpns_plural": "{{count}} 个 VPN",
"enableAll": "全部启用",
"skip": "跳过",
"success": "已为所有项目启用同步"
},
"crossOs": {
"viewOnly": "在 {{os}} 上创建 - 仅查看",
"cannotLaunch": "在 {{os}} 上创建。只能在 {{os}} 上启动。",
"viewOnly": "此配置文件在 {{os}} 上创建,不受此系统支持",
"cannotLaunch": "此配置文件在 {{os}} 上创建,不受此系统支持",
"cannotModify": "无法修改跨操作系统配置文件的同步设置"
}
}
+2 -2
View File
@@ -15,8 +15,8 @@ export function getBrowserDisplayName(browserType: string): string {
zen: "Zen Browser",
brave: "Brave",
chromium: "Chromium",
camoufox: "Firefox (Camoufox)",
wayfern: "Chromium (Wayfern)",
camoufox: "Camoufox",
wayfern: "Wayfern",
};
return browserNames[browserType] || browserType;