@electron-ota/electron
v0.1.0
Published
Electron main-process SDK for electron-ota renderer OTA updates
Downloads
96
Maintainers
Readme
@electron-ota/electron
Electron main-process SDK for electron-ota renderer OTA updates: check → download → verify → stage → activate on next launch → health-check → promote, with automatic rollback and blacklisting when a staged update cannot boot.
Usage
// main.ts — create BEFORE app is ready (the privileged app:// scheme must
// register early), start after.
import path from 'node:path';
import { app, BrowserWindow } from 'electron';
import { createElectronOtaUpdater } from '@electron-ota/electron';
const updater = createElectronOtaUpdater({
appVersion: app.getVersion(),
runtimeVersion: 'runtime-7',
channel: 'production',
updateUrl: 'https://updates.example.com/my-app',
publicKey: 'BASE64_ED25519_PUBLIC_KEY',
embeddedBundlePath: path.join(process.resourcesPath, 'renderer'),
currentRendererVersion: '3.4.2-ui.8',
});
app.whenReady().then(async () => {
const { url } = await updater.start(); // e.g. app://bundle/index.html
const win = new BrowserWindow({
webPreferences: {
preload: require.resolve('@electron-ota/electron/preload'),
contextIsolation: true,
nodeIntegration: false,
},
});
await win.loadURL(url);
const check = await updater.checkForRendererUpdate();
if (check.status === 'update-available') {
await updater.downloadRendererUpdate();
await updater.activateOnNextLaunch(); // applied on next restart
}
});// renderer — signal health after your first meaningful successful action:
await window.electronOta.markHealthy();Safety model (and its tradeoffs)
- Nothing executes unverified. sha256 streams during download and the manifest signature is re-verified before staging; a mismatch deletes the artifact. Compatibility is gated at check time AND re-checked at stage time.
- Activation is a pointer switch. Bundles are immutable directories under
userData/electron-ota/updates/<id>/; onlystate.jsonchanges, via write-temp → fsync → rename. Corruptstate.jsonquarantines itself and boots the embedded bundle — the app always starts. - Crash counting happens before pending code runs.
bootAttemptsis persisted, then the pending bundle loads under a watchdog (healthCheckTimeoutMs, default 30s). Promotion requiresmarkHealthy(). AftermaxBootAttempts(default 2) failed boots the update is discarded, blacklisted, and the previous/embedded bundle boots. Tradeoff: a user sees up to N failed launches before automatic recovery. - Probation failure relaunches the app (
probationFailureAction: 'relaunch'). Tradeoff: a visible restart — but the alternative is a white screen forever. Set'quit'if you orchestrate relaunch yourself. promoteOnDidFinishLoadis off by default because "the page painted" does not mean "the app works". Turning it on trades rollback fidelity for zero renderer integration.- Stale-pending protection is best-effort offline. Before activating a
pending update, the channel is re-checked (budget:
stalePendingCheckTimeoutMs, default 3s). Offline, the update was verified active at stage time, so it MAY activate; a developer rollback cannot reach a device that never comes online. - The renderer can only signal, never install. The preload bridge exposes
exactly
markHealthy(),onUpdateStaged(),getVersions(). OTA-delivered renderer code cannot influence what gets installed. Run renderers withcontextIsolation: trueandnodeIntegration: false— OTA content runs with whatever privileges you grant renderers. - Bundles are opaque bytes. Minified, code-split, obfuscated output is
packaged/hashed/signed/extracted without parsing. The compatibility boundary
is
runtimeVersion(preload/main/native contract), not bundle contents. - Scheme registration owns the list.
registerScheme: true(default) callsprotocol.registerSchemesAsPrivileged, which Electron replaces wholesale per call. If your app registers its own schemes, passregisterScheme: falseand include the OTA scheme yourself.
