diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1a51fdd8a7..07e089fb66 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6957,7 +6957,7 @@ dependencies = [ [[package]] name = "tauri-runtime-cef" version = "0.1.0" -source = "git+https://github.com/SableClient/tauri-runtime-cef?branch=main#c0d9ff8ecf0eac8504c1947932274e7fe7d9fb8c" +source = "git+https://github.com/SableClient/tauri-runtime-cef?branch=main#6aee1cdbe137b53cb28201417ec302b75a93330a" dependencies = [ "base64 0.22.1", "cef", diff --git a/src-tauri/src/desktop/diagnostics.rs b/src-tauri/src/desktop/diagnostics.rs index c741e2a4e8..6d93b35551 100644 --- a/src-tauri/src/desktop/diagnostics.rs +++ b/src-tauri/src/desktop/diagnostics.rs @@ -7,16 +7,22 @@ use tauri_plugin_dialog::DialogExt; const DEFAULT_ARCHIVE_NAME: &str = "sable-diagnostics.zip"; #[tauri::command] -pub fn export_diagnostics( +pub async fn export_diagnostics( app: AppHandle, frontend_logs: Option, ) -> Result, String> { - let Some(path) = app - .dialog() + let (sender, receiver) = tokio::sync::oneshot::channel(); + app.dialog() .file() .set_file_name(DEFAULT_ARCHIVE_NAME) .add_filter("ZIP archive", &["zip"]) - .blocking_save_file() + .save_file(move |path| { + let _ = sender.send(path); + }); + + let Some(path) = receiver + .await + .map_err(|error| format!("Save dialog did not return a result: {error}"))? else { return Ok(None); }; diff --git a/src-tauri/src/desktop/download.rs b/src-tauri/src/desktop/download.rs index 30078f801a..cba83d6b36 100644 --- a/src-tauri/src/desktop/download.rs +++ b/src-tauri/src/desktop/download.rs @@ -2,16 +2,22 @@ use tauri::AppHandle; use tauri_plugin_dialog::DialogExt; #[tauri::command] -pub fn save_download( +pub async fn save_download( app: AppHandle, filename: String, bytes: Vec, ) -> Result { - let Some(path) = app - .dialog() + let (sender, receiver) = tokio::sync::oneshot::channel(); + app.dialog() .file() .set_file_name(&filename) - .blocking_save_file() + .save_file(move |path| { + let _ = sender.send(path); + }); + + let Some(path) = receiver + .await + .map_err(|error| format!("Save dialog did not return a result: {error}"))? else { return Ok(false); }; diff --git a/src/app/generated/tauri/commands.ts b/src/app/generated/tauri/commands.ts index 1dd132bbba..a712d062e2 100644 --- a/src/app/generated/tauri/commands.ts +++ b/src/app/generated/tauri/commands.ts @@ -1,7 +1,7 @@ /** * Auto-generated TypeScript bindings for Tauri commands * Generated by tauri-typegen v0.5.0 - * Generated at: 2026-07-31T11:14:58.599659+00:00 + * Generated at: 2026-08-05T12:41:11.731056+00:00 * Generator: none * * Do not edit manually - regenerate using: cargo tauri-typegen generate diff --git a/src/app/generated/tauri/events.ts b/src/app/generated/tauri/events.ts index 0260d39ed0..ff0d638558 100644 --- a/src/app/generated/tauri/events.ts +++ b/src/app/generated/tauri/events.ts @@ -1,7 +1,7 @@ /** * Auto-generated TypeScript bindings for Tauri commands * Generated by tauri-typegen v0.5.0 - * Generated at: 2026-07-31T11:14:58.602356+00:00 + * Generated at: 2026-08-05T12:41:11.732167+00:00 * Generator: none * * Do not edit manually - regenerate using: cargo tauri-typegen generate diff --git a/src/app/generated/tauri/index.ts b/src/app/generated/tauri/index.ts index 727bb5d08e..0b97ef4c53 100644 --- a/src/app/generated/tauri/index.ts +++ b/src/app/generated/tauri/index.ts @@ -1,7 +1,7 @@ /** * Auto-generated TypeScript bindings for Tauri commands * Generated by tauri-typegen v0.5.0 - * Generated at: 2026-07-31T11:14:58.603222+00:00 + * Generated at: 2026-08-05T12:41:11.732351+00:00 * Generator: none * * Do not edit manually - regenerate using: cargo tauri-typegen generate diff --git a/src/app/generated/tauri/types.ts b/src/app/generated/tauri/types.ts index 5c41032aff..8bd687fe82 100644 --- a/src/app/generated/tauri/types.ts +++ b/src/app/generated/tauri/types.ts @@ -1,7 +1,7 @@ /** * Auto-generated TypeScript bindings for Tauri commands * Generated by tauri-typegen v0.5.0 - * Generated at: 2026-07-31T11:14:58.596290+00:00 + * Generated at: 2026-08-05T12:41:11.729365+00:00 * Generator: none * * Do not edit manually - regenerate using: cargo tauri-typegen generate diff --git a/src/app/pages/client/DesktopUpdater.test.tsx b/src/app/pages/client/DesktopUpdater.test.tsx index dcf3f8a6d1..ccae75e22c 100644 --- a/src/app/pages/client/DesktopUpdater.test.tsx +++ b/src/app/pages/client/DesktopUpdater.test.tsx @@ -4,6 +4,7 @@ import { useAtomValue, useSetAtom, Provider } from 'jotai'; import { globalBannersAtom } from '$state/globalBanners'; import { triggerUpdateCheckAtom } from '$state/desktopUpdate'; import { DesktopUpdatePill } from '$components/tauri/DesktopUpdatePill'; +import { getDebugLogger } from '$utils/debugLogger'; import { DesktopUpdater } from './DesktopUpdater'; const { checkFn } = vi.hoisted(() => ({ checkFn: vi.fn<() => Promise>() })); @@ -156,8 +157,9 @@ describe('DesktopUpdater', () => { it('retries when downloadAndInstall fails', async () => { const update = makeUpdate('4.0.0'); + const debugLog = vi.spyOn(getDebugLogger(), 'log'); update.downloadAndInstall - .mockRejectedValueOnce(new Error('eacces')) + .mockRejectedValueOnce({ message: 'permission denied' }) .mockResolvedValueOnce(undefined); checkFn.mockResolvedValue(update); @@ -178,6 +180,12 @@ describe('DesktopUpdater', () => { await waitFor(() => { expect(screen.getByRole('button', { name: 'Download & Install' })).toBeInTheDocument(); }); + expect(debugLog).toHaveBeenCalledWith( + 'error', + 'network', + 'DesktopUpdater', + 'Failed to install update: permission denied' + ); fireEvent.click(screen.getByRole('button', { name: 'Download & Install' })); await waitFor(() => expect(update.downloadAndInstall).toHaveBeenCalledTimes(2)); diff --git a/src/app/pages/client/DesktopUpdater.tsx b/src/app/pages/client/DesktopUpdater.tsx index 80a279d9c8..fe02f69c09 100644 --- a/src/app/pages/client/DesktopUpdater.tsx +++ b/src/app/pages/client/DesktopUpdater.tsx @@ -4,6 +4,7 @@ import type { Update } from '@tauri-apps/plugin-updater'; import { isDesktopTauri } from '$utils/platform'; import { autoUpdateCheckAtom } from '$state/autoUpdateCheck'; import { createLogger } from '$utils/debug'; +import { getDebugLogger } from '$utils/debugLogger'; import { hasCustomDesktopTitlebar } from '$utils/tauriTitlebar'; import { useDesktopSetting } from '$state/hooks/desktopSettings'; import { ArrowUp } from '$components/icons/phosphor'; @@ -19,6 +20,17 @@ import { const log = createLogger('DesktopUpdater'); const UPDATE_POLL_INTERVAL_MS = 300_000; // 5 minutes +const logUpdaterError = (message: string, error: unknown): void => { + log.error(message, error); + const detail = + error instanceof Error + ? error.message + : typeof error === 'object' && error !== null && 'message' in error + ? String(error.message) + : String(error); + getDebugLogger().log('error', 'network', 'DesktopUpdater', `${message}: ${detail}`); +}; + export function DesktopUpdater() { const autoUpdateCheck = useAtomValue(autoUpdateCheckAtom); const triggerCount = useAtomValue(triggerUpdateCheckAtom); @@ -92,7 +104,7 @@ export function DesktopUpdater() { setLastChecked(new Date().toISOString()); if (!hasCustomDesktopTitlebar(useCustomTitleBar)) setBannerVisible(true); } catch (err) { - log.error('Desktop update check failed', err); + logUpdaterError('Desktop update check failed', err); if (mounted) { if (!hasUpdateRef.current) { setPhase({ type: 'idle' }); @@ -179,7 +191,7 @@ export function DesktopUpdater() { pendingUpdateRef.current = null; closePendingUpdate(updateInfo); } catch (err) { - log.error('Failed to install update', err); + logUpdaterError('Failed to install update', err); setIsDownloading(false); setIsInstalling(false); installStartedRef.current = false;