import { check as checkUpdate } from '@tauri-apps/plugin-updater'; import { isTauriRuntime } from './globalShortcut'; export interface UpdateState { available: boolean; version: string | null; notes: string | null; downloading: boolean; downloaded: boolean; error: string | null; } export const IDLE_UPDATE_STATE: UpdateState = { available: false, version: null, notes: null, downloading: false, downloaded: false, error: null, }; type UpdateHandle = Awaited>; let cachedUpdate: UpdateHandle | null = null; export async function checkForUpdate(): Promise { if (!isTauriRuntime()) { return { ...IDLE_UPDATE_STATE }; } try { const update = await checkUpdate(); if (!update) { cachedUpdate = null; return { ...IDLE_UPDATE_STATE }; } cachedUpdate = update; return { available: true, version: update.version ?? null, notes: update.body ?? null, downloading: false, downloaded: false, error: null, }; } catch (err: unknown) { // Swallow "no release on GitHub yet" / network-unreachable cases silently. // The updater endpoint serves `latest.json` from GitHub releases; a fresh // repo or offline machine produces a generic "Could not fetch a valid // release JSON" error that has no actionable information for the user — // logging it on every launch just pollutes the console. const msg = err instanceof Error ? err.message : String(err); const benign = /could not fetch a valid release json|network|timed? out|failed to fetch|connection/i.test( msg, ); if (!benign) { console.warn('checkForUpdate failed', err); } return { ...IDLE_UPDATE_STATE, error: benign ? null : msg, }; } } // Downloads + installs the previously checked update. On Windows the app // quits during install (passive installer); on macOS/Linux tauri triggers // a relaunch automatically. export async function installUpdate( onProgress?: (downloaded: number, total: number | null) => void, ): Promise { if (!cachedUpdate) { throw new Error('no pending update — call checkForUpdate() first'); } let total: number | null = null; let downloaded = 0; await cachedUpdate.downloadAndInstall((event) => { if (event.event === 'Started') { total = event.data.contentLength ?? null; downloaded = 0; } else if (event.event === 'Progress') { downloaded += event.data.chunkLength; } onProgress?.(downloaded, total); }); cachedUpdate = null; }