// Auto-updater. Wraps electron-updater; configuration (feed URL) lives in // package.json's `build.publish`. In dev (`app.isPackaged === false`) we // short-circuit everything — the feed server is production-only and // hitting it every launch from a dev machine just adds noise. import { app, BrowserWindow, ipcMain } from 'electron'; import electronUpdater, { type ProgressInfo, type UpdateInfo as BuilderUpdateInfo, } from 'electron-updater'; // electron-updater is a CJS module; named ESM imports don't work. Pull // autoUpdater off the default export instead. const { autoUpdater } = electronUpdater; import { CHANNELS, type UpdateCheckResult, type UpdateInfo, type UpdateProgress, } from '../ipc-types'; let cached: BuilderUpdateInfo | null = null; function toPublicInfo(info: BuilderUpdateInfo | null): UpdateInfo | null { if (!info) return null; const notes = info.releaseNotes; let releaseNotes: string | null = null; if (typeof notes === 'string') releaseNotes = notes; else if (Array.isArray(notes)) releaseNotes = notes.map((r) => r.note).join('\n\n'); return { version: info.version ?? '', releaseNotes, }; } export function register(mainWindow: BrowserWindow): void { autoUpdater.autoDownload = false; autoUpdater.autoInstallOnAppQuit = false; autoUpdater.on('download-progress', (p: ProgressInfo) => { if (mainWindow.isDestroyed()) return; const payload: UpdateProgress = { percent: p.percent ?? 0, transferred: p.transferred ?? 0, total: p.total ?? 0, }; mainWindow.webContents.send(CHANNELS.UPDATER_EVT_PROGRESS, payload); }); ipcMain.handle(CHANNELS.UPDATER_CHECK, async (): Promise => { if (!app.isPackaged) { return { available: false, info: null }; } try { const result = await autoUpdater.checkForUpdates(); if (!result || !result.updateInfo) { cached = null; return { available: false, info: null }; } const current = app.getVersion(); const remote = result.updateInfo.version; if (!remote || remote === current) { cached = null; return { available: false, info: null }; } cached = result.updateInfo; return { available: true, info: toPublicInfo(cached) }; } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); if (!/ENOTFOUND|ETIMEDOUT|ECONNRESET|404/i.test(msg)) { console.warn('[updater] check failed', err); } return { available: false, info: null }; } }); ipcMain.handle(CHANNELS.UPDATER_DOWNLOAD_INSTALL, async (): Promise => { if (!app.isPackaged) return; if (!cached) throw new Error('no pending update — call check first'); await autoUpdater.downloadUpdate(); autoUpdater.quitAndInstall(); }); }