From feb35427285ab4b3936d89361ce6c842a43ad2fe Mon Sep 17 00:00:00 2001 From: PANDA-JSR Date: Tue, 17 Mar 2026 22:22:45 +0800 Subject: [PATCH] fix(frontend): stabilize tauri listeners and logging payloads --- index.html | 1 + public/favicon.svg | 11 +++++++++ src-tauri/src/main.rs | 2 +- src/App.tsx | 36 +++++++++++++++++++++------- src/components/Settings.tsx | 40 +++++++++++++++++++++---------- src/components/Sidebar.tsx | 31 +++++++++++++++++------- src/components/TitleBar.tsx | 33 ++++++++++++++++++++----- src/components/WindowControls.tsx | 6 +++++ src/contexts/ThemeContext.tsx | 33 +++++++++++++++++-------- src/main.tsx | 2 +- src/preload/types.ts | 10 +++++--- vite.config.ts | 4 ++++ 12 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 public/favicon.svg diff --git a/index.html b/index.html index 63d0740..5dc77af 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + SecScore diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..ae889c5 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 774c263..e47aa28 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -9,9 +9,9 @@ fn main() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .setup(|app| { - setup_app(app)?; let state = AppState::new(app.handle().clone()); app.manage(Arc::new(RwLock::new(state))); + setup_app(app)?; Ok(()) }) .invoke_handler(tauri::generate_handler![ diff --git a/src/App.tsx b/src/App.tsx index a825e06..38a99c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,16 +15,34 @@ function MainContent(): React.JSX.Element { const [messageApi, contextHolder] = message.useMessage() useEffect(() => { - if (!(window as any).api) return - const unlisten = (window as any).api.onNavigate((route: string) => { - const currentPath = location.pathname === "/" ? "/home" : location.pathname - const targetPath = route === "/" ? "/home" : route + const api = (window as any).api + if (!api) return - if (currentPath !== targetPath) { - navigate(route) - } - }) - return () => unlisten() + let disposed = false + let unlisten: (() => void) | null = null + + api + .onNavigate((route: string) => { + const currentPath = location.pathname === "/" ? "/home" : location.pathname + const targetPath = route === "/" ? "/home" : route + + if (currentPath !== targetPath) { + navigate(route) + } + }) + .then((fn: () => void) => { + if (disposed) { + fn() + return + } + unlisten = fn + }) + .catch(() => void 0) + + return () => { + disposed = true + if (unlisten) unlisten() + } }, [navigate, location.pathname]) const [wizardVisible, setWizardVisible] = useState(false) diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index ccc231c..25f6ee7 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -95,20 +95,36 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission useEffect(() => { loadAll() - if (!(window as any).api) return - const unsubscribe = (window as any).api.onSettingChanged((change: any) => { - setSettings((prev) => { - if (change?.key === "log_level") return { ...prev, log_level: change.value } - if (change?.key === "is_wizard_completed") - return { ...prev, is_wizard_completed: change.value } - if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value } - if (change?.key === "auto_score_enabled") - return { ...prev, auto_score_enabled: change.value } - return prev + const api = (window as any).api + if (!api) return + + let disposed = false + let unlisten: (() => void) | null = null + + api + .onSettingChanged((change: any) => { + setSettings((prev) => { + if (change?.key === "log_level") return { ...prev, log_level: change.value } + if (change?.key === "is_wizard_completed") + return { ...prev, is_wizard_completed: change.value } + if (change?.key === "window_zoom") return { ...prev, window_zoom: change.value } + if (change?.key === "auto_score_enabled") + return { ...prev, auto_score_enabled: change.value } + return prev + }) }) - }) + .then((fn: () => void) => { + if (disposed) { + fn() + return + } + unlisten = fn + }) + .catch(() => void 0) + return () => { - if (typeof unsubscribe === "function") unsubscribe() + disposed = true + if (unlisten) unlisten() } }, []) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 4d34745..a3a508d 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -39,15 +39,30 @@ export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): const handleStatusChange = () => { loadDbStatus() } - if ((window as any).api) { - const unsubscribe = (window as any).api.onSettingChanged( - (change: { key: string; value: any }) => { - if (change.key === "pg_connection_status") { - handleStatusChange() - } + const api = (window as any).api + if (!api) return + + let disposed = false + let unlisten: (() => void) | null = null + + api + .onSettingChanged((change: { key: string; value: any }) => { + if (change.key === "pg_connection_status") { + handleStatusChange() } - ) - return unsubscribe + }) + .then((fn: () => void) => { + if (disposed) { + fn() + return + } + unlisten = fn + }) + .catch(() => void 0) + + return () => { + disposed = true + if (unlisten) unlisten() } }, []) diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index 1b333eb..75e592f 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -16,13 +16,34 @@ export function TitleBar({ children }: TitleBarProps): React.JSX.Element { const [isMaximized, setIsMaximized] = useState(false) useEffect(() => { - if (!(window as any).api) return - ;(window as any).api.windowIsMaximized().then((v: boolean) => setIsMaximized(v)) + const api = (window as any).api + if (!api) return - const cleanup = (window as any).api.onWindowMaximizedChanged((maximized: boolean) => { - setIsMaximized(maximized) - }) - return cleanup + let disposed = false + let unlisten: (() => void) | null = null + + api + .windowIsMaximized() + .then((v: boolean) => setIsMaximized(v)) + .catch(() => void 0) + + api + .onWindowMaximizedChanged((maximized: boolean) => { + setIsMaximized(maximized) + }) + .then((fn: () => void) => { + if (disposed) { + fn() + return + } + unlisten = fn + }) + .catch(() => void 0) + + return () => { + disposed = true + if (unlisten) unlisten() + } }, []) const minimize = () => { diff --git a/src/components/WindowControls.tsx b/src/components/WindowControls.tsx index b5eeba9..f24d01e 100644 --- a/src/components/WindowControls.tsx +++ b/src/components/WindowControls.tsx @@ -14,6 +14,7 @@ export function WindowControls(): React.JSX.Element { const api = (window as any).api if (!api) return + let disposed = false let unlisten: (() => void) | null = null api @@ -26,11 +27,16 @@ export function WindowControls(): React.JSX.Element { setIsMaximized(maximized) }) .then((fn: () => void) => { + if (disposed) { + fn() + return + } unlisten = fn }) .catch(() => void 0) return () => { + disposed = true if (unlisten) unlisten() } }, []) diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx index 23b3e59..3f9b4e0 100644 --- a/src/contexts/ThemeContext.tsx +++ b/src/contexts/ThemeContext.tsx @@ -120,23 +120,36 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre }, [applyThemeConfig]) useEffect(() => { - if (!(window as any).api) return + const api = (window as any).api + if (!api) return + ;(async () => { await loadThemes() await loadCurrentTheme() })() - const unsubscribe = (window as any).api.onThemeChanged((theme: themeConfig) => { - setCurrentTheme(theme) - currentThemeRef.current = theme - applyThemeConfig(theme) - loadThemes() - }) + let disposed = false + let unlisten: (() => void) | null = null + + api + .onThemeChanged((theme: themeConfig) => { + setCurrentTheme(theme) + currentThemeRef.current = theme + applyThemeConfig(theme) + loadThemes() + }) + .then((fn: () => void) => { + if (disposed) { + fn() + return + } + unlisten = fn + }) + .catch(() => void 0) return () => { - if (typeof unsubscribe === "function") { - unsubscribe() - } + disposed = true + if (unlisten) unlisten() } }, [applyThemeConfig, loadCurrentTheme, loadThemes]) diff --git a/src/main.tsx b/src/main.tsx index 03fb68f..a0d52c0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -24,7 +24,7 @@ const safeWriteLog = (payload: { try { const api = (window as any).api if (!api?.writeLog) return - api.writeLog(payload) + Promise.resolve(api.writeLog(payload)).catch(() => void 0) } catch { return } diff --git a/src/preload/types.ts b/src/preload/types.ts index 3d97cf4..908cd63 100644 --- a/src/preload/types.ts +++ b/src/preload/types.ts @@ -188,15 +188,19 @@ const api = { }, // Logger - queryLogs: (params?: { lines?: number }): Promise<{ success: boolean; data: string[] }> => - invoke("log_query", { params }), + queryLogs: ( + input?: number | { lines?: number } + ): Promise<{ success: boolean; data: string[] }> => { + const lines = typeof input === "number" ? input : input?.lines + return invoke("log_query", { lines }) + }, clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"), setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }), writeLog: (payload: { level: string message: string meta?: any - }): Promise<{ success: boolean }> => invoke("log_write", payload), + }): Promise<{ success: boolean }> => invoke("log_write", { payload }), // Database Connection dbTestConnection: ( diff --git a/vite.config.ts b/vite.config.ts index de7e70b..efac6a8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,10 @@ export default defineConfig({ "@": resolve(__dirname, "src"), }, }, + optimizeDeps: { + // Avoid scanning legacy `old-ss` entries under project root. + entries: ["index.html"], + }, build: { outDir: "dist", emptyOutDir: true,