mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 04:02:02 +08:00
refactor: replace JSON deep copy with deepClone helper and extract useTauriEvent hook (#3140)
* refactor: replace JSON.parse(JSON.stringify()) with structuredClone and extract useTauriEvent hook Replace all `JSON.parse(JSON.stringify())` deep copy patterns with native `structuredClone()` across production source (9 occurrences), tests (11 occurrences), and a hand-rolled `deepClone` utility in providerConfigUtils.ts. Add "ES2022" to tsconfig lib for type support. Extract a `useTauriEvent` hook to eliminate the repeated Tauri event listener boilerplate (`useEffect` + `active/disposed` flag + async `listen`) that was duplicated across App.tsx (3 listeners) and useUsageCacheBridge.ts. The hook handles async registration, race-condition guards, and cleanup automatically. * fix: add compatible deepClone helper - Add a shared deepClone helper with a structuredClone runtime guard and fallback. - Route clone call sites through the helper. - Preserve universal-provider-synced listener ordering and drop the dead-directory diff. * fix: harden Tauri event handling - Guard WebDAV sync status events against missing payloads. - Preserve settings query invalidation ordering before showing auto-sync errors. - Simplify useTauriEvent subscriptions to avoid dependency-driven re-listens. --------- Co-authored-by: zcb <zhangchongbiao@qiyuanlab.com> Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
+37
-112
@@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { toast } from "sonner";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Plus,
|
||||
@@ -46,9 +45,11 @@ import { hermesApi } from "@/lib/api/hermes";
|
||||
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
||||
import { useAutoCompact } from "@/hooks/useAutoCompact";
|
||||
import { useUsageCacheBridge } from "@/hooks/useUsageCacheBridge";
|
||||
import { useTauriEvent } from "@/hooks/useTauriEvent";
|
||||
import { useLastValidValue } from "@/hooks/useLastValidValue";
|
||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||
import { isTextEditableTarget } from "@/utils/domUtils";
|
||||
import { deepClone } from "@/utils/deepClone";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
isWindows,
|
||||
@@ -361,117 +362,43 @@ function App() {
|
||||
};
|
||||
}, [activeApp, refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
let active = true;
|
||||
useTauriEvent("universal-provider-synced", async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["providers"] });
|
||||
try {
|
||||
await providersApi.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error("[App] Failed to update tray menu", error);
|
||||
}
|
||||
});
|
||||
|
||||
const setupListener = async () => {
|
||||
try {
|
||||
const { listen } = await import("@tauri-apps/api/event");
|
||||
const off = await listen("universal-provider-synced", async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["providers"] });
|
||||
try {
|
||||
await providersApi.updateTrayMenu();
|
||||
} catch (error) {
|
||||
console.error("[App] Failed to update tray menu", error);
|
||||
}
|
||||
});
|
||||
if (!active) {
|
||||
off();
|
||||
return;
|
||||
}
|
||||
unsubscribe = off;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[App] Failed to subscribe universal-provider-synced event",
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
void setupListener();
|
||||
return () => {
|
||||
active = false;
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, [queryClient]);
|
||||
|
||||
useEffect(() => {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
let active = true;
|
||||
|
||||
const setupListener = async () => {
|
||||
try {
|
||||
const off = await listen(
|
||||
"webdav-sync-status-updated",
|
||||
async (event) => {
|
||||
const payload = (event.payload ??
|
||||
{}) as WebDavSyncStatusUpdatedPayload;
|
||||
await queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
|
||||
if (payload.source !== "auto" || payload.status !== "error") {
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error(
|
||||
t("settings.webdavSync.autoSyncFailedToast", {
|
||||
error: payload.error || t("common.unknown"),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (!active) {
|
||||
off();
|
||||
return;
|
||||
}
|
||||
unsubscribe = off;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[App] Failed to subscribe webdav-sync-status-updated event",
|
||||
error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
void setupListener();
|
||||
return () => {
|
||||
active = false;
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, [queryClient, t]);
|
||||
|
||||
// Listen for proxy-official-warning: warn when takeover is enabled with an official provider
|
||||
useEffect(() => {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
let active = true;
|
||||
|
||||
const setup = async () => {
|
||||
const off = await listen("proxy-official-warning", (event) => {
|
||||
const { providerName } = event.payload as {
|
||||
appType: string;
|
||||
providerName: string;
|
||||
};
|
||||
toast.warning(
|
||||
t("notifications.proxyOfficialWarning", {
|
||||
name: providerName,
|
||||
defaultValue: `当前供应商 ${providerName} 是官方供应商,建议切换到第三方供应商后再使用代理接管`,
|
||||
}),
|
||||
{ duration: 8000 },
|
||||
);
|
||||
});
|
||||
if (!active) {
|
||||
off();
|
||||
useTauriEvent<WebDavSyncStatusUpdatedPayload | null | undefined>(
|
||||
"webdav-sync-status-updated",
|
||||
async (payload) => {
|
||||
const statusPayload = payload ?? {};
|
||||
await queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
if (statusPayload.source !== "auto" || statusPayload.status !== "error") {
|
||||
return;
|
||||
}
|
||||
unsubscribe = off;
|
||||
};
|
||||
toast.error(
|
||||
t("settings.webdavSync.autoSyncFailedToast", {
|
||||
error: statusPayload.error || t("common.unknown"),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
void setup();
|
||||
return () => {
|
||||
active = false;
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, [t]);
|
||||
useTauriEvent<{ appType: string; providerName: string }>(
|
||||
"proxy-official-warning",
|
||||
(payload) => {
|
||||
toast.warning(
|
||||
t("notifications.proxyOfficialWarning", {
|
||||
name: payload.providerName,
|
||||
defaultValue: `当前供应商 ${payload.providerName} 是官方供应商,建议切换到第三方供应商后再使用代理接管`,
|
||||
}),
|
||||
{ duration: 8000 },
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -744,13 +671,11 @@ function App() {
|
||||
addToLive?: boolean;
|
||||
} = {
|
||||
name: `${provider.name} copy`,
|
||||
settingsConfig: JSON.parse(JSON.stringify(provider.settingsConfig)), // 深拷贝
|
||||
settingsConfig: deepClone(provider.settingsConfig),
|
||||
websiteUrl: provider.websiteUrl,
|
||||
category: provider.category,
|
||||
sortIndex: newSortIndex, // 复制原 sortIndex + 1
|
||||
meta: provider.meta
|
||||
? JSON.parse(JSON.stringify(provider.meta))
|
||||
: undefined, // 深拷贝
|
||||
meta: provider.meta ? deepClone(provider.meta) : undefined,
|
||||
icon: provider.icon,
|
||||
iconColor: provider.iconColor,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user