How the Modly Desktop App Checks for and Applies Updates with the Auto‑Updater
The Modly desktop client uses electron‑updater to automatically download patch updates on macOS and Windows, while prompting users manually for major or minor version upgrades.
The Modly auto-updater balances seamless background updates with user control by distinguishing between patch releases (applied silently) and feature releases (deferred to the user). This article examines the complete implementation in the lightningpixel/modly repository, from initialization through renderer notification.
Initializing the Auto‑Updater in the Main Process
When the Electron main process starts, electron/main/index.ts initializes the updater with a reference to the main window:
// electron/main/index.ts – line 108
initAutoUpdater(() => mainWindow);
This wiring enables the updater to send IPC messages to the renderer. On macOS, auto-download is disabled entirely because Modly's builds are unsigned, preventing automatic gatekeeper bypasses.
Configuring the Updater Behavior
The core configuration resides in electron/main/updater.ts (lines 19‑23):
autoUpdater.logger = createAppLogger();
autoUpdater.autoDownload = false; // patches download only after confirmation
autoUpdater.autoInstallOnAppQuit = true; // install when app exits normally
autoUpdater.disableWebInstaller = true;
Key design decisions:
autoDownload: false— Prevents immediate downloads, allowing the app to inspect version semantics firstautoInstallOnAppQuit: true— Ensures updates apply without forcing an immediate restart- Web installer disabled — Keeps all update traffic within the native auto-updater channels
How Modly Checks for Updates
The updater employs a three-trigger strategy defined in electron/main/updater.ts:
1. Startup Check (Lines 59‑62)
autoUpdater.checkForUpdates().catch(err => {
logger.error('Initial update check failed', err);
});
Executes immediately when the app launches, ensuring users receive critical patches promptly.
2. Periodic Re‑check (Lines 64‑67)
setInterval(() => {
autoUpdater.checkForUpdates().catch(() => {});
}, 2 * 60 * 60 * 1000); // 2 hours
Background polling catches updates published while the app remains open for extended sessions.
3. Manual Check via IPC (Lines 1437‑1443)
The renderer can force an immediate check through electron/main/ipc-handlers.ts:
ipcMain.handle('updater:check', async () => {
const result = await autoUpdater.checkForUpdates();
return { success: true, versionInfo: result?.updateInfo };
});
Differentiating Patch vs. Major/Minor Updates
When update-available fires, electron/main/updater.ts (lines 24‑40) performs semantic version analysis:
autoUpdater.on('update-available', (info) => {
const current = app.getVersion(); // e.g., "1.2.3"
const incoming = info.version; // e.g., "1.2.4" or "1.3.0"
const currentParts = current.split('.').map(Number);
const incomingParts = incoming.split('.').map(Number);
const isPatch = (
currentParts[0] === incomingParts[0] && // same major
currentParts[1] === incomingParts[1] // same minor
);
if (isPatch) {
autoUpdater.downloadUpdate(); // silent background download
} else {
mainWindow?.webContents.send('updater:major-minor-available', {
version: incoming,
currentVersion: current
});
}
});
Patch updates (1.2.3 → 1.2.4) download automatically. Major/minor updates (1.2.3 → 1.3.0) surface in the UI for explicit user action.
Downloading and Applying Patch Updates
Once a patch downloads, the update-downloaded event triggers (lines 42‑49):
autoUpdater.on('update-downloaded', (info) => {
mainWindow?.webContents.send('updater:applying', {
version: info.version
});
setTimeout(() => {
autoUpdater.quitAndInstall(true, true);
// first true = silent, second true = force run after update
}, 800); // brief delay for UI transition
});
The 800ms timeout allows the renderer to render an "Applying update..." panel before the app terminates.
Renderer‑Side API for Update Control
The preload script in electron/preload/electron-api.ts (lines 63‑76) exposes a typed updater object to the React frontend:
updater: {
check: () => ipcRenderer.invoke('updater:check'),
quitAndInstall: () => ipcRenderer.invoke('updater:quitAndInstall'),
onApplying: (callback) =>
ipcRenderer.on('updater:applying', (_e, data) => callback(data)),
offApplying: () =>
ipcRenderer.removeAllListeners('updater:applying'),
onMajorMinorAvailable: (callback) =>
ipcRenderer.on('updater:major-minor-available', (_e, data) => callback(data)),
offMajorMinorAvailable: () =>
ipcRenderer.removeAllListeners('updater:major-minor-available')
}
Triggering a Manual Check from React
import { api } from '@/electron/preload';
const checkForUpdates = async () => {
const result = await api.updater.check();
if (result.success) {
console.log('Update check initiated');
}
};
Displaying the Applying State
useEffect(() => {
api.updater.onApplying(({ version }) => {
setApplyingVersion(version);
setShowApplyingPanel(true);
});
return () => api.updater.offApplying();
}, []);
Handling Major/Minor Version Notifications
useEffect(() => {
api.updater.onMajorMinorAvailable(({ version }) => {
openDownloadDialog(version); // user-controlled download flow
});
return () => api.updater.offMajorMinorAvailable();
}, []);
Platform-Specific Considerations
| Platform | Behavior |
|---|---|
| macOS | Auto-download disabled (autoDownload: false) due to unsigned builds; manual download required for all updates |
| Windows | Full auto-updater support; patches download and install automatically |
| Linux | Not configured in current implementation; typically distributed via package managers |
Summary
- Initialization occurs in
electron/main/index.tsviainitAutoUpdater(), binding the updater to the main window - Configuration in
electron/main/updater.tsdisables auto-download and enables quit-time installation - Three triggers check for updates: startup, 2‑hour interval, and manual IPC invocation
- Semantic versioning differentiates patches (auto-downloaded) from major/minor releases (renderer notification)
- Patch application uses
quitAndInstall(true, true)with an 800ms UI grace period - Renderer API in
electron/preload/electron-api.tsprovidescheck(),onApplying(), andonMajorMinorAvailable()for complete UI control
Frequently Asked Questions
How often does Modly check for updates automatically?
The app checks immediately on startup and then every 2 hours via setInterval in electron/main/updater.ts (lines 64‑67). Users can also trigger manual checks through the updater:check IPC handler.
Why doesn't Modly auto-download updates on macOS?
macOS builds are unsigned, so autoDownload remains false to prevent Gatekeeper warnings and security prompts. Users must manually download and replace the application for macOS updates.
What happens when a major version update is available?
The main process sends updater:major-minor-available to the renderer with version details. The UI typically displays a dialog directing users to the GitHub releases page rather than applying the update automatically.
Can users cancel an update that's already downloading?
Once downloadUpdate() begins for a patch, cancellation isn't exposed through the current API. However, major/minor updates require explicit user action before any download starts, providing natural cancellation.
Where is the update server configured?
The electron-updater module reads update metadata from latest.yml (Windows) or latest-mac.yml (macOS) hosted alongside the release artifacts. The feed URL derives from the publish configuration in package.json, not hardcoded in the source files examined.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →