Реалізація автообновлення (Auto-Update) десктоп-приложення
Автообновлення — критична функція для десктоп-приложень. Без неї користувачі месяцями працюють зі старими версіями. З поганою реалізацією — втрачають дані або отримують сломані оновлення. Розглянемо для Electron та Tauri.
Electron: electron-updater
electron-updater з пакета electron-builder — стандартний інструмент. Підтримує GitHub Releases, S3, власні сервери.
npm install electron-updater
// main/updater.js
const { autoUpdater } = require('electron-updater');
const { app, BrowserWindow } = require('electron');
const log = require('electron-log');
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
function setupAutoUpdater(mainWindow) {
autoUpdater.on('update-available', (info) => {
mainWindow.webContents.send('update:available', {
version: info.version,
releaseNotes: info.releaseNotes,
releaseDate: info.releaseDate
});
});
autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('update:progress', {
percent: Math.round(progress.percent)
});
});
autoUpdater.on('update-downloaded', (info) => {
mainWindow.webContents.send('update:downloaded', {
version: info.version
});
});
const { ipcMain } = require('electron');
ipcMain.handle('updater:check', () => autoUpdater.checkForUpdates());
ipcMain.handle('updater:download', () => autoUpdater.downloadUpdate());
ipcMain.handle('updater:install', () => autoUpdater.quitAndInstall(false, true));
setInterval(() => autoUpdater.checkForUpdates(), 4 * 60 * 60 * 1000);
setTimeout(() => autoUpdater.checkForUpdates(), 10000);
}
module.exports = { setupAutoUpdater };
Конфігурація для GitHub Releases
# electron-builder.yml
publish:
provider: github
owner: your-github-username
repo: your-repo-name
private: false
При сборці electron-builder створює latest.yml з метаданими версії. autoUpdater читає її для перевірки оновлень.
UI компонент для оновлень
// renderer/components/UpdateNotification.tsx
import { useEffect, useState } from 'react';
type UpdateState =
| { status: 'idle' }
| { status: 'checking' }
| { status: 'available'; version: string }
| { status: 'downloading'; percent: number }
| { status: 'ready'; version: string }
| { status: 'error'; message: string };
export function UpdateNotification() {
const [state, setState] = useState<UpdateState>({ status: 'idle' });
useEffect(() => {
const unsubscribers = [
window.electronAPI.onUpdateAvailable((info) =>
setState({ status: 'available', version: info.version })
),
window.electronAPI.onUpdateProgress((p) =>
setState({ status: 'downloading', percent: p.percent })
),
window.electronAPI.onUpdateDownloaded((info) =>
setState({ status: 'ready', version: info.version })
),
];
return () => unsubscribers.forEach(fn => fn?.());
}, []);
if (state.status === 'available') {
return (
<div className="update-banner">
<span>Версія {state.version} доступна</span>
<button onClick={() => window.electronAPI.downloadUpdate()}>Завантажити</button>
</div>
);
}
if (state.status === 'ready') {
return (
<div className="update-banner">
<span>Версія {state.version} готова</span>
<button onClick={() => window.electronAPI.installUpdate()}>Перезапустити та встановити</button>
</div>
);
}
return null;
}
Tauri: вбудоване оновлення
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-updater = "2"
// src-tauri/src/lib.rs
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::default().build())
.invoke_handler(tauri::generate_handler![check_for_updates])
.run(tauri::generate_context!())
.expect("error running app");
}
#[tauri::command]
async fn check_for_updates(app: tauri::AppHandle) -> Result<(), String> {
let updater = app.updater().map_err(|e| e.to_string())?;
let response = updater.check().await.map_err(|e| e.to_string())?;
if let Some(update) = response {
app.emit("update:available", &update.version).unwrap();
update.download_and_install(
|chunk, total| {
if let Some(total) = total {
let percent = (chunk * 100 / total) as u8;
app.emit("update:progress", percent).unwrap();
}
},
|| { app.emit("update:installed", ()).unwrap(); }
).await.map_err(|e| e.to_string())?;
}
Ok(())
}
Tauri вимагає підписаних оновлень — не можна встановити неподписаний апдейт.
Оновлення без перезапуску
Повністю безшовне оновлення технічно неможливе для бінарника, який працює. Але мінімізуйте дискомфорт:
- Завантажувати оновлення у фоні тихо
- Пропонувати встановлення при наступному закритті
- Показувати нотифікацію у треї
- Зберігати стан перед перезапуском та відновлювати після







