import { useCallback } from 'react'; import { atom, useAtom } from 'jotai'; type TauriInternals = { invoke: (cmd: string, args?: Record) => Promise }; const tauriInvoke = (): TauriInternals['invoke'] | undefined => (window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke; type UpdateStatus = | { state: 'idle' } | { state: 'checking' } | { state: 'up-to-date' } | { state: 'available'; version: string } | { state: 'installing' } | { state: 'error'; message: string }; // Module-level so the result of a manual "Check for updates" survives closing // and reopening Settings (it used to be component state, so the "update // available" answer was thrown away with the tab and had to be re-checked). // A transient 'checking'/'installing' state is reset if the tab unmounts // mid-request by the resolve/reject below, so nothing gets stuck. const updateStatusAtom = atom({ state: 'idle' }); export function useTauriUpdater() { const isTauri = !!tauriInvoke(); const [status, setStatus] = useAtom(updateStatusAtom); const check = useCallback(async () => { const invoke = tauriInvoke(); if (!invoke) return; setStatus({ state: 'checking' }); try { const result = (await invoke('check_for_update')) as { available: boolean; version?: string }; setStatus( result.available && result.version ? { state: 'available', version: result.version } : { state: 'up-to-date' }, ); } catch (e) { setStatus({ state: 'error', message: String(e) }); } }, [setStatus]); const install = useCallback(async () => { const invoke = tauriInvoke(); if (!invoke) return; setStatus({ state: 'installing' }); try { await invoke('install_update'); // On a successful install the native side calls app.restart(), so this // resolve is only reached when nothing was installed (no update found) — // don't leave the UI stuck on "installing". setStatus({ state: 'up-to-date' }); } catch (e) { setStatus({ state: 'error', message: String(e) }); } }, [setStatus]); return { isTauri, status, check, install }; }