Reference for @deploy-your-app/electron-update-manager. The package publishes three entry points, one per Electron process.
import { ElectronUpdateManager, IPC_CHANNELS } from '@deploy-your-app/electron-update-manager/main';
import '@deploy-your-app/electron-update-manager/preload';
import { getUpdaterApi, autoUpdate } from '@deploy-your-app/electron-update-manager/renderer';
The root entry (@deploy-your-app/electron-update-manager) re-exports all four names for convenience.
Main process
new ElectronUpdateManager(config, builtinBundlePath)
| Parameter | Type | Description |
|---|---|---|
config |
UpdaterConfig |
Options table below. appId is required; the constructor throws without it. |
builtinBundlePath |
string |
Directory containing the index.html shipped inside the app package. Resolved to an absolute path. |
The constructor reads Electron's userData path and creates the device ID file, so construct it inside app.whenReady().
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
appId |
string |
required | App ID registered on DeployYourApp |
updateUrl |
string |
https://api.deployyour.app/api/update |
Update check endpoint |
statsUrl |
string |
https://api.deployyour.app/api/stats |
Analytics endpoint |
channel |
string |
'production' |
Update channel. A channel persisted by setChannel() overrides this at startup. |
autoUpdate |
boolean |
true |
Run the full check/download/apply cycle automatically |
appReadyTimeout |
number |
10000 |
Milliseconds to wait for notifyAppReady() before rolling back |
checkInterval |
number |
600 |
Seconds between automatic checks |
publicKey |
string |
— | PEM public key. When set, every bundle's RSA signature is verified. When unset, signature verification is skipped. |
encryptionPrivateKey |
string |
— | PEM private key used to unwrap the AES session key of an encrypted bundle |
analyticsEnabled |
boolean |
true |
Buffer and send analytics, including the automatic update_* events |
analyticsBatchSize |
number |
20 |
Buffered events before an automatic flush |
analyticsFlushInterval |
number |
30 |
Seconds between automatic flushes |
applyMode |
string |
'whenIdle' |
When a downloaded bundle is activated. See below. |
resetWhenUpdate |
boolean |
true |
Drop back to the built-in bundle when the native app version changes |
autoDeleteFailed |
boolean |
true |
Delete a rolled-back bundle's files. The version stays quarantined either way; this only reclaims disk space. |
allowedDownloadHosts |
string[] |
['storage.deployyour.app'] |
Extra hosts allowed to serve bundles. The updateUrl and statsUrl hosts are always allowed, so this is only needed when you serve bundles from your own storage or CDN. Downloads are https-only; http is accepted from localhost alone. |
directUpdate |
string |
— | Deprecated. Alias for applyMode, honoured only when applyMode is not set. 'immediate' maps to 'immediate'; any other value maps to 'whenIdle'. |
applyMode
| Value | Behaviour after a successful download |
|---|---|
whenIdle |
Record the bundle as current without reloading. It loads on the next launch. Default. |
immediate |
Apply and reload the window now |
onLaunch |
Hold as pending. Applied by the first check after the next launch. |
background |
Download only. Call apply() yourself. |
The server can override the mode per update, force it with mandatory, and postpone it with delayUntil. See Update lifecycle.
Lifecycle methods
initialize(options?)
Creates the bundle directory, loads persisted state and settings, runs the crash-loop check, registers the IPC handlers, and starts the auto-update and analytics timers. Call it before creating the window.
| Option | Type | Default | Description |
|---|---|---|---|
window |
BrowserWindow |
— | Window to reload on apply and rollback. Equivalent to calling setWindow(). |
Returns: Promise<void>
setWindow(window)
Sets the window the updater reloads when a bundle is applied or rolled back. Without a window, applying a bundle only emits reload, and your app must load getBundlePath() itself.
getBundlePath()
Returns: string — the absolute path to the active bundle's index.html, or the built-in bundle's index.html when no update is active or the recorded bundle is missing from disk.
checkForUpdate()
Applies any due pending bundle, then queries the update server.
Returns: Promise<UpdateCheckResult>
| Property | Type | Description |
|---|---|---|
available |
boolean |
Whether a bundle is offered |
version |
string |
Version of the offered bundle |
url |
string |
Download URL |
checksum |
string |
Expected SHA-256 of the bytes to download |
signature |
string |
Base64 RSA signature, or null for unsigned bundles |
sessionKey |
string |
Encrypted AES session key, or null when the bundle is not encrypted |
applyMode |
string |
Server-dictated mode for this update |
mandatory |
boolean |
When true, the update is applied immediately regardless of applyMode |
delayUntil |
string |
ISO 8601 timestamp, or null |
This method never rejects. Network failures, non-2xx responses, and malformed bodies emit updateFailed with code CHECK_FAILED and resolve to { available: false }. A 204 or 404 response emits noUpdateAvailable and resolves to { available: false }.
download(options)
Downloads, verifies, decrypts, and extracts a bundle. Extraction happens in a hidden staging directory and is swapped into place only after the archive validates, so a failure never leaves a half-written bundle.
| Option | Type | Description |
|---|---|---|
url |
string |
Download URL |
version |
string |
Bundle version. Used as a directory name, so it must match ^[0-9A-Za-z.\-+]{1,64}$. |
checksum |
string |
Expected SHA-256 hex digest |
signature |
string |
Base64 RSA signature, verified only when publicKey is configured |
sessionKey |
string (optional) |
Encrypted AES session key. Triggers decryption. |
Returns: Promise<{ id: string, version: string }>
Rejects when the version string is invalid, when the version is already the active bundle, when another download is in flight, on HTTP failure, on checksum or signature mismatch, on decryption failure, on path traversal in the archive, or when the archive has no index.html at its root.
apply(options)
Records a downloaded bundle as current and marks it unconfirmed.
| Option | Type | Default | Description |
|---|---|---|---|
id |
string |
required | Bundle version to activate |
immediate |
boolean |
true |
Reload the window now and start the ready-check timer. When false, the bundle is recorded and takes effect on the next launch. |
Returns: Promise<void> — rejects if the bundle is not on disk.
notifyAppReady()
Confirms the running bundle, clears the ready-check timer, resets the crash-loop counter, and emits appReady. Synchronous, returns undefined.
startReadyCheck()
Starts the appReadyTimeout timer. apply({ immediate: true }) calls it for you; call it manually only if you activate a bundle yourself.
reset()
Factory reset. Clears the current and pending bundle, deletes every downloaded bundle from disk, clears the quarantine list, clears any version override, then reloads the window with the built-in bundle.
Returns: Promise<void>
getFailedVersions()
Versions that were rolled back on this device and will not be installed again. Cleared by reset().
Returns: string[]
resolveApplyMode(params)
Resolves the effective apply mode for an update.
| Parameter | Type | Default | Description |
|---|---|---|---|
serverApplyMode |
string |
— | applyMode from the check response. Wins over the configured mode. |
mandatory |
boolean |
false |
When true, returns immediate and ignores delayUntil |
delayUntil |
string |
— | ISO 8601 timestamp. A future value sets the pending delay and marks the result deferred. |
Returns: { mode: string, deferred: boolean }
setPendingBundle(id)
Stores a bundle as pending and persists it. The next checkForUpdate() applies it once any delay has elapsed.
destroy()
Clears the check, analytics, and ready timers, and flushes buffered analytics. Call it from before-quit.
Bundle management
| Method | Returns | Description |
|---|---|---|
getCurrentBundle() |
{ id, version, isBuiltin } |
Active bundle. id and version are null when the built-in bundle is active. |
listBundles() |
Promise<Array<{ id, version, size }>> |
Bundle directories on disk. Hidden staging directories are excluded. |
deleteBundle(id) |
Promise<void> |
Removes a bundle. Rejects when id is the active bundle. |
Device, channel, and version
| Method | Returns | Description |
|---|---|---|
getDeviceId() |
string |
Random UUID generated on first run and persisted to dya-device-id |
getChannel() |
string |
Active channel |
setChannel(channel) |
Promise<void> |
Switches channel and persists it to dya-settings.json |
getVersionOverride() |
string | null |
Current override |
setVersionOverride(version) |
Promise<void> |
Reports this version to the server instead of the real bundle version. Pass null or '' to clear. Persisted. |
Analytics
| Method | Description |
|---|---|
trackEvent(name, properties?) |
Buffers a custom event |
trackPageView(path, title?) |
Buffers a page_view event |
trackError(message, stack?, fatal?) |
Buffers an error event |
flushAnalytics() |
Posts the buffer now. On failure the events are put back and the buffer is re-capped. |
The buffer holds at most 500 events; the oldest are dropped first. Every buffered event carries eventType, eventData, bundleVersion ('builtin' when no update is active), and an ISO timestamp.
When analyticsEnabled is true, the updater emits these events on its own:
update_check, update_available, update_download_start, update_download_complete, update_download_fail, update_verify_pass, update_verify_fail, update_apply, update_rollback, update_app_ready.
Setting analyticsEnabled: false suppresses these too. It is read at construction — there is no runtime toggle.
Main process events
ElectronUpdateManager extends EventEmitter.
| Event | Payload | Description |
|---|---|---|
updateAvailable |
{ version, message } |
A bundle is offered |
noUpdateAvailable |
— | The server has nothing for this device |
downloadProgress |
{ percent, bytesDownloaded, totalBytes } |
percent is 0 when the response has no content-length |
downloadComplete |
{ id, version } |
Bundle extracted and staged into place |
downloadFailed |
{ message, version } |
Download, verification, decryption, or extraction failed |
updateApplied |
{ id, version } |
Bundle recorded as current |
updateFailed |
{ message, code } |
See error codes below |
rollback |
{ from, to, reason } |
Reverted to the previous bundle. reason is appReadyTimeout or crashLoop; from is quarantined. |
updateSkipped |
{ version, reason } |
An offered update was not installed. reason is previouslyRolledBack. |
reload |
— | The active bundle changed |
appReady |
— | notifyAppReady() was called |
appVersionChange |
{ previousVersion, currentVersion } |
The native app version changed and resetWhenUpdate reset to the built-in bundle |
appReady and appVersionChange are main-process only. Every other event is forwarded to all renderer processes.
IPC channels
Exported as IPC_CHANNELS from the /main entry. initialize() registers an ipcMain.handle for each request channel; event channels are sent with webContents.send to every window.
Request channels (renderer to main)
| Constant | Channel | Maps to |
|---|---|---|
CHECK_FOR_UPDATE |
dya:check-for-update |
checkForUpdate() |
DOWNLOAD |
dya:download |
download(opts) |
APPLY |
dya:apply |
apply(opts) |
NOTIFY_APP_READY |
dya:notify-app-ready |
notifyAppReady() |
RESET |
dya:reset |
reset() |
GET_CURRENT_BUNDLE |
dya:get-current-bundle |
getCurrentBundle() |
LIST_BUNDLES |
dya:list-bundles |
listBundles() |
DELETE_BUNDLE |
dya:delete-bundle |
deleteBundle(id) |
GET_DEVICE_ID |
dya:get-device-id |
getDeviceId() |
GET_CHANNEL |
dya:get-channel |
getChannel() |
SET_CHANNEL |
dya:set-channel |
setChannel(channel) |
GET_VERSION_OVERRIDE |
dya:get-version-override |
getVersionOverride() |
SET_VERSION_OVERRIDE |
dya:set-version-override |
setVersionOverride(version) |
TRACK_EVENT |
dya:track-event |
trackEvent(name, props) |
TRACK_PAGE_VIEW |
dya:track-page-view |
trackPageView(path, title) |
TRACK_ERROR |
dya:track-error |
trackError(msg, stack, fatal) |
FLUSH_ANALYTICS |
dya:flush-analytics |
flushAnalytics() |
dya:set-channel, dya:get-version-override, and dya:set-version-override are handled by the main process but are not exposed on the preload bridge. To reach them from the renderer, add your own contextBridge method in a preload script.
Event channels (main to renderer)
| Constant | Channel | Emitter event |
|---|---|---|
ON_UPDATE_AVAILABLE |
dya:on-update-available |
updateAvailable |
ON_DOWNLOAD_PROGRESS |
dya:on-download-progress |
downloadProgress |
ON_DOWNLOAD_COMPLETE |
dya:on-download-complete |
downloadComplete |
ON_DOWNLOAD_FAILED |
dya:on-download-failed |
downloadFailed |
ON_UPDATE_APPLIED |
dya:on-update-applied |
updateApplied |
ON_UPDATE_FAILED |
dya:on-update-failed |
updateFailed |
ON_ROLLBACK |
dya:on-rollback |
rollback |
ON_NO_UPDATE |
dya:on-no-update |
noUpdateAvailable |
ON_RELOAD |
dya:on-reload |
reload |
Preload bridge
Importing @deploy-your-app/electron-update-manager/preload exposes window.deployYourApp through contextBridge. Every method returns a promise; every on* method returns an unsubscribe function.
| Member | Signature |
|---|---|
checkForUpdate |
() => Promise<UpdateCheckResult> |
download |
(opts) => Promise<{ id, version }> |
apply |
(opts) => Promise<void> |
notifyAppReady |
() => Promise<void> |
reset |
() => Promise<void> |
getCurrentBundle |
() => Promise<BundleInfo> |
listBundles |
() => Promise<BundleEntry[]> |
deleteBundle |
(id) => Promise<void> |
getDeviceId |
() => Promise<string> |
getChannel |
() => Promise<string> |
setChannel |
(channel) => Promise<void> |
getVersionOverride |
() => Promise<string> |
setVersionOverride |
(version) => Promise<void> |
trackEvent |
(name, properties?) => Promise<void> |
trackPageView |
(path, title?) => Promise<void> |
trackError |
(message, stack?, fatal?) => Promise<void> |
flushAnalytics |
() => Promise<void> |
onUpdateAvailable |
(cb: ({ version, message }) => void) => () => void |
onDownloadProgress |
(cb: ({ percent, bytesDownloaded, totalBytes }) => void) => () => void |
onDownloadComplete |
(cb: ({ id, version }) => void) => () => void |
onDownloadFailed |
(cb: ({ message, version }) => void) => () => void |
onUpdateApplied |
(cb: ({ id, version }) => void) => () => void |
onUpdateFailed |
(cb: ({ message, code }) => void) => () => void |
onRollback |
(cb: ({ from, to, reason }) => void) => () => void — reason is appReadyTimeout or crashLoop |
onNoUpdate |
(cb: () => void) => () => void |
onReload |
(cb: () => void) => () => void |
The bridge exposes no channel or version-override setters. setChannel and setVersionOverride are main-process only.
Renderer helpers
getUpdaterApi()
Returns: the window.deployYourApp object. Throws when the preload bridge is not registered.
import { getUpdaterApi } from '@deploy-your-app/electron-update-manager/renderer';
const api = getUpdaterApi();
const { id, version, isBuiltin } = await api.getCurrentBundle();
autoUpdate(options?)
Runs check, download, and apply in one call.
| Option | Type | Default | Description |
|---|---|---|---|
onProgress |
function |
— | Called with { percent, bytesDownloaded, totalBytes }. Unsubscribed when the call settles. |
immediate |
boolean |
true |
Passed through to apply(). Set false to defer activation to the next launch. |
Returns: Promise<{ updated: boolean, version?: string }> — { updated: false } when no update is available. Rejects if the download or apply step fails.
This helper is for apps running with autoUpdate: false. With the main-process auto-update loop enabled, both would race for the same bundle directory and one would fail the single-flight guard.
Error codes
The Electron updater reports failures two ways: a code on the updateFailed event, and rejection messages from download() and apply().
updateFailed codes
| Code | Cause |
|---|---|
CHECK_FAILED |
The update check threw — network failure, timeout, non-2xx status, or an unparseable body |
LOAD_FAILED |
BrowserWindow.loadFile() rejected while activating a bundle |
Rejection messages
download() and apply() reject with plain Error objects that carry no code. Branch on the message when you need to distinguish them.
| Message | Cause |
|---|---|
Invalid bundle version string: <version> |
Version does not match ^[0-9A-Za-z.\-+]{1,64}$ |
Version <version> is already the active bundle |
Re-downloading the live bundle was blocked |
A bundle download is already in progress (version <version>) |
Single-flight guard |
Download failed: <status> |
Non-2xx HTTP status on the bundle URL |
Checksum mismatch — bundle may be corrupted |
SHA-256 of the downloaded bytes does not match |
Invalid bundle signature |
RSA verification failed against the configured publicKey |
Encryption private key required for E2E decryption. Set config.encryptionPrivateKey |
Encrypted bundle with no encryptionPrivateKey |
Unrecognized encrypted bundle format. Re-deploy the bundle with the current CLI. |
The payload is not in the DYA1 wire format |
Path traversal detected in extracted ZIP: <name> |
An extracted entry resolved outside the bundle directory |
Bundle <version> does not contain an index.html at its root |
Archive layout is wrong |
Bundle <id> not found on disk |
apply() was given an id that was never downloaded |
The same failures also emit downloadFailed with the message, and are recorded as update_download_fail or update_verify_fail analytics events.
Timeouts and limits
| Behaviour | Value |
|---|---|
| Update check timeout | 30 seconds |
| Bundle download timeout | 300 seconds |
| Analytics POST timeout | 15 seconds |
| Buffered analytics cap | 500 events |
| Unconfirmed launches before rollback | 3 |
First automatic check after initialize() |
3 seconds |