feat(electron): add auto-update functionality with electron-updater

This commit is contained in:
benzntech 2026-03-06 18:54:00 +05:30
parent f96ee44213
commit 3342d5b931
3 changed files with 144 additions and 3 deletions

View file

@ -26,10 +26,13 @@ const {
nativeImage,
shell,
session,
dialog,
Notification,
} = require("electron");
const path = require("path");
const { spawn } = require("child_process");
const fs = require("fs");
const { autoUpdater } = require("electron-updater");
// ── Single Instance Lock ───────────────────────────────────
const gotTheLock = app.requestSingleInstanceLock();
@ -62,6 +65,11 @@ let serverPort = 20128;
const getServerUrl = () => `http://localhost:${serverPort}`;
// ── Auto-Updater Configuration ──────────────────────────────
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.logger = console;
// ── Helper: Send IPC event to renderer (#5) ────────────────
function sendToRenderer(channel, data) {
if (mainWindow && !mainWindow.isDestroyed()) {
@ -103,6 +111,85 @@ async function waitForServerExit(proc, timeoutMs = 5000) {
]);
}
// ── Auto-Updater Event Handlers ─────────────────────────────
function setupAutoUpdater() {
autoUpdater.on("checking-for-update", () => {
sendToRenderer("update-status", { status: "checking" });
console.log("[Electron] Checking for updates...");
});
autoUpdater.on("update-available", (info) => {
sendToRenderer("update-status", { status: "available", version: info.version });
console.log("[Electron] Update available:", info.version);
});
autoUpdater.on("update-not-available", (info) => {
sendToRenderer("update-status", { status: "not-available", version: info.version });
console.log("[Electron] No update available");
});
autoUpdater.on("download-progress", (progress) => {
sendToRenderer("update-status", {
status: "downloading",
percent: Math.round(progress.percent),
transferred: progress.transferred,
total: progress.total,
});
});
autoUpdater.on("update-downloaded", (info) => {
sendToRenderer("update-status", { status: "downloaded", version: info.version });
console.log("[Electron] Update downloaded:", info.version);
if (Notification.isSupported()) {
const notification = new Notification({
title: "OmniRoute Update Ready",
body: `Version ${info.version} is ready to install. Click to restart.`,
});
notification.on("click", () => {
autoUpdater.quitAndInstall();
});
notification.show();
}
});
autoUpdater.on("error", (error) => {
sendToRenderer("update-status", { status: "error", message: error.message });
console.error("[Electron] Update error:", error);
});
}
async function checkForUpdates(silent = false) {
if (isDev) {
console.log("[Electron] Dev mode — skipping auto-update");
if (!silent) {
sendToRenderer("update-status", { status: "error", message: "Updates disabled in dev mode" });
}
return;
}
try {
await autoUpdater.checkForUpdates();
} catch (error) {
console.error("[Electron] Check for updates failed:", error);
if (!silent) {
sendToRenderer("update-status", { status: "error", message: error.message });
}
}
}
async function downloadUpdate() {
try {
await autoUpdater.downloadUpdate();
} catch (error) {
console.error("[Electron] Download update failed:", error);
sendToRenderer("update-status", { status: "error", message: error.message });
}
}
function installUpdate() {
autoUpdater.quitAndInstall();
}
// ── Content Security Policy (#15) ──────────────────────────
function setupContentSecurityPolicy() {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
@ -236,6 +323,11 @@ function createTray() {
],
},
{ type: "separator" },
{
label: "Check for Updates",
click: () => checkForUpdates(false),
},
{ type: "separator" },
{
label: "Quit",
click: () => {
@ -391,6 +483,24 @@ function setupIpcHandlers() {
});
ipcMain.on("window-close", () => mainWindow?.close());
// Auto-update IPC handlers
ipcMain.handle("check-for-updates", async () => {
await checkForUpdates(false);
return { success: true };
});
ipcMain.handle("download-update", async () => {
await downloadUpdate();
return { success: true };
});
ipcMain.handle("install-update", () => {
installUpdate();
return { success: true };
});
ipcMain.handle("get-app-version", () => app.getVersion());
}
// ── App Lifecycle ──────────────────────────────────────────
@ -407,6 +517,14 @@ app.whenReady().then(async () => {
createWindow();
createTray();
setupIpcHandlers();
setupAutoUpdater();
// Check for updates after a short delay (don't block startup)
if (!isDev) {
setTimeout(() => {
checkForUpdates(true);
}, 3000);
}
// macOS: recreate window when dock icon clicked
app.on("activate", () => {

View file

@ -15,7 +15,9 @@
"build:linux": "electron-builder --linux",
"pack": "electron-builder --dir"
},
"dependencies": {},
"dependencies": {
"electron-updater": "^6.8.3"
},
"devDependencies": {
"electron": "^40.6.1",
"electron-builder": "^25.1.8"
@ -28,6 +30,11 @@
"output": "dist-electron",
"buildResources": "assets"
},
"publish": {
"provider": "github",
"owner": "diegosouzapw",
"repo": "OmniRoute"
},
"files": [
"main.js",
"preload.js",

View file

@ -13,9 +13,18 @@ const { contextBridge, ipcRenderer } = require("electron");
// ── Channel Whitelist ──────────────────────────────────────
const VALID_CHANNELS = {
invoke: ["get-app-info", "open-external", "get-data-dir", "restart-server"],
invoke: [
"get-app-info",
"open-external",
"get-data-dir",
"restart-server",
"check-for-updates",
"download-update",
"install-update",
"get-app-version",
],
send: ["window-minimize", "window-maximize", "window-close"],
receive: ["server-status", "port-changed"],
receive: ["server-status", "port-changed", "update-status"],
};
// ── Fix #16: Generic IPC wrappers ──────────────────────────
@ -48,6 +57,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
openExternal: (url) => safeInvoke("open-external", url),
getDataDir: () => safeInvoke("get-data-dir"),
restartServer: () => safeInvoke("restart-server"),
getAppVersion: () => safeInvoke("get-app-version"),
// ── Auto-Update ──────────────────────────────────────────
checkForUpdates: () => safeInvoke("check-for-updates"),
downloadUpdate: () => safeInvoke("download-update"),
installUpdate: () => safeInvoke("install-update"),
// ── Send (fire-and-forget) ───────────────────────────────
minimizeWindow: () => safeSend("window-minimize"),
@ -58,6 +73,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
// Fix #6: Returns a disposer function for precise cleanup
onServerStatus: (callback) => safeOn("server-status", callback),
onPortChanged: (callback) => safeOn("port-changed", callback),
onUpdateStatus: (callback) => safeOn("update-status", callback),
// ── Static Properties ────────────────────────────────────
isElectron: true,